mirror of
https://github.com/ialley-workshop-open/uni-halo.git
synced 2026-06-12 13:19:31 +08:00
feat: 新增数据可视化看板、删除多余的文件
This commit is contained in:
@@ -0,0 +1,468 @@
|
||||
<template>
|
||||
<view class="contribution-heatmap pa-24">
|
||||
<view class="header">
|
||||
<view class="title">
|
||||
<view v-if="false" style="color: #7e7e7f">数据范围:</view>
|
||||
<view>{{ dataRangeYears }}</view>
|
||||
</view>
|
||||
<view class="controls">
|
||||
<tm-stepper v-model="currentYear" :width="220" :height="48" :min="1999" :max="2099" :shadow="0" :round="2" color="light-blue" @change="changeYear"></tm-stepper>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="heatmap-container">
|
||||
<view class="weeks">
|
||||
<view class="week-label" v-for="week in weeks" :key="week">{{ week }}</view>
|
||||
</view>
|
||||
<view class="heatmap-content">
|
||||
<view class="months" :style="[calcContentStyle]">
|
||||
<view class="month-label" v-for="month in monthLabels" :key="month.index">
|
||||
{{ month.name }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="days-container" :style="[calcContentStyle]">
|
||||
<view
|
||||
v-for="(day, index) in displayDays"
|
||||
:key="index"
|
||||
class="day-cell"
|
||||
:style="{ backgroundColor: getDayColor(day) }"
|
||||
@click="handleDayClick(day, index)"
|
||||
></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer">
|
||||
<view class="releases-count">
|
||||
<text>累计 {{ calcAllYearCount }} 篇</text>
|
||||
<text>丨</text>
|
||||
<text>本年 {{ calcCurrentYearCount }} 篇</text>
|
||||
</view>
|
||||
<view class="legend">
|
||||
<text class="legend-text">少</text>
|
||||
<view v-for="(color, index) in intensityColors" :key="index" class="day-cell legend-day-cell" :style="{ backgroundColor: color }"></view>
|
||||
<text class="legend-text">多</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import tmStepper from '@/tm-vuetify/components/tm-stepper/tm-stepper.vue';
|
||||
export default {
|
||||
name: 'Heatmap',
|
||||
components: {
|
||||
tmStepper
|
||||
},
|
||||
props: {
|
||||
year: {
|
||||
type: Number,
|
||||
default: () => new Date().getFullYear()
|
||||
},
|
||||
chartData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
weeks: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
|
||||
intensityColors: [
|
||||
'#ebedf0', // 无贡献
|
||||
'#dbeafe', // 1-2次
|
||||
'#93c5fd', // 3-4次
|
||||
'#3b82f6', // 5-6次
|
||||
'#1e40af' // 7次以上
|
||||
],
|
||||
displayDays: [],
|
||||
showTooltip: null,
|
||||
currentYear: '1900',
|
||||
currentYearData: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dataRangeYears() {
|
||||
const arr = this.chartData;
|
||||
const dateField = 'name';
|
||||
|
||||
if (!arr || !Array.isArray(arr) || arr.length === 0) {
|
||||
return { minDate: null, maxDate: null };
|
||||
}
|
||||
|
||||
// 提取所有有效日期
|
||||
const validDates = arr
|
||||
.map((item) => {
|
||||
if (item && item[dateField]) {
|
||||
const date = new Date(item[dateField]);
|
||||
return isNaN(date.getTime()) ? null : date;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter((date) => date !== null);
|
||||
|
||||
if (validDates.length === 0) {
|
||||
return { minDate: null, maxDate: null };
|
||||
}
|
||||
|
||||
// 找到最小和最大日期
|
||||
const minDate = new Date(Math.min(...validDates.map((date) => date.getTime())));
|
||||
const maxDate = new Date(Math.max(...validDates.map((date) => date.getTime())));
|
||||
|
||||
const result = {
|
||||
minDate: this.formatDate(minDate),
|
||||
maxDate: this.formatDate(maxDate)
|
||||
};
|
||||
|
||||
return `${result.minDate} 至 ${result.maxDate}`;
|
||||
},
|
||||
// 计算内容宽度
|
||||
calcContentStyle() {
|
||||
const rowCount = Math.ceil(this.displayDays.length / 7);
|
||||
const singleWidth = 36;
|
||||
return {
|
||||
width: rowCount * singleWidth + 'rpx'
|
||||
};
|
||||
},
|
||||
//累计的发文次数
|
||||
calcAllYearCount() {
|
||||
return this.chartData.reduce((acc, cur) => {
|
||||
return acc + cur.total;
|
||||
}, 0);
|
||||
},
|
||||
// 统计当前年累计的发文次数
|
||||
calcCurrentYearCount() {
|
||||
return this.currentYearData.reduce((acc, cur) => {
|
||||
return acc + cur.total;
|
||||
}, 0);
|
||||
},
|
||||
monthLabels() {
|
||||
const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'];
|
||||
const labels = [];
|
||||
|
||||
if (this.displayDays.length === 0) return labels;
|
||||
|
||||
// 计算每个格子的总宽度(包括间距)
|
||||
const cellTotalWidth = 15 + 1.5 * 2; // width + margin * 2
|
||||
|
||||
let currentMonth = -1;
|
||||
let monthStartWeek = 0;
|
||||
|
||||
// 遍历所有周
|
||||
const totalWeeks = Math.ceil(this.displayDays.length / 7);
|
||||
|
||||
for (let week = 0; week < totalWeeks; week++) {
|
||||
// 找到这一周的第一个有效日期
|
||||
let weekMonth = -1;
|
||||
for (let day = 0; day < 7; day++) {
|
||||
const dayIndex = week * 7 + day;
|
||||
if (dayIndex < this.displayDays.length && this.displayDays[dayIndex].date) {
|
||||
weekMonth = this.displayDays[dayIndex].date.getMonth();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果找到了有效月份
|
||||
if (weekMonth !== -1) {
|
||||
// 如果是新的月份
|
||||
if (weekMonth !== currentMonth) {
|
||||
// 如果不是第一个月份,先计算前一个月份的宽度
|
||||
if (currentMonth !== -1) {
|
||||
const weeksInMonth = week - monthStartWeek;
|
||||
const width = weeksInMonth * cellTotalWidth;
|
||||
labels.push({
|
||||
name: months[currentMonth],
|
||||
width: width,
|
||||
marginLeft: monthStartWeek === 0 ? 0 : 2,
|
||||
index: currentMonth
|
||||
});
|
||||
}
|
||||
|
||||
currentMonth = weekMonth;
|
||||
monthStartWeek = week;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理最后一个月份
|
||||
if (currentMonth !== -1) {
|
||||
const weeksInMonth = totalWeeks - monthStartWeek;
|
||||
const width = weeksInMonth * cellTotalWidth;
|
||||
labels.push({
|
||||
name: months[currentMonth],
|
||||
width: width,
|
||||
marginLeft: monthStartWeek === 0 ? 0 : 2,
|
||||
index: currentMonth
|
||||
});
|
||||
}
|
||||
|
||||
return labels;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
year: {
|
||||
immediate: true,
|
||||
handler(newYear) {
|
||||
this.generateDisplayData(newYear);
|
||||
}
|
||||
},
|
||||
chartData: {
|
||||
deep: true,
|
||||
handler(newData) {
|
||||
this.mergeContributionData(newData);
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.filterAndSetCurrentYearData(this.year);
|
||||
},
|
||||
methods: {
|
||||
filterAndSetCurrentYearData(year) {
|
||||
this.currentYear = year;
|
||||
this.currentYearData = this.chartData.filter((x) => x.name.includes(this.currentYear));
|
||||
this.generateDisplayData(year);
|
||||
},
|
||||
generateDisplayData(year) {
|
||||
const startDate = new Date(year, 0, 1);
|
||||
const endDate = new Date(year, 11, 31);
|
||||
const days = [];
|
||||
|
||||
// 计算第一周的偏移量(确保周一开始)
|
||||
let firstDay = startDate.getDay();
|
||||
firstDay = firstDay === 0 ? 6 : firstDay - 1; // 将周日(0)转换为6,周一(1)转换为0
|
||||
|
||||
// 添加空白填充,使第一周从周一开始
|
||||
for (let i = 0; i < firstDay; i++) {
|
||||
days.push({ date: null, count: 0 });
|
||||
}
|
||||
|
||||
// 生成一年的日期
|
||||
let currentDate = new Date(startDate);
|
||||
while (currentDate <= endDate) {
|
||||
// 默认无贡献
|
||||
days.push({
|
||||
date: new Date(currentDate),
|
||||
count: 0
|
||||
});
|
||||
currentDate.setDate(currentDate.getDate() + 1);
|
||||
}
|
||||
|
||||
this.displayDays = days;
|
||||
this.mergeContributionData(this.currentYearData);
|
||||
},
|
||||
mergeContributionData(contributionData) {
|
||||
if (!contributionData || contributionData.length === 0) return;
|
||||
|
||||
// 将传入的数据合并到显示数据中
|
||||
const dataMap = new Map();
|
||||
contributionData.forEach((item) => {
|
||||
const dateStr = this.formatDate(item.date);
|
||||
dataMap.set(dateStr, item.total);
|
||||
});
|
||||
|
||||
this.displayDays.forEach((day) => {
|
||||
if (day.date) {
|
||||
const dateStr = this.formatDate(day.date);
|
||||
if (dataMap.has(dateStr)) {
|
||||
day.count = dataMap.get(dateStr);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
formatDate(date) {
|
||||
date = new Date(date);
|
||||
// 将日期格式化为 YYYY-MM-DD
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
},
|
||||
formatDisplayDate(date) {
|
||||
// 将日期格式化为中文显示格式
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
return `${year}年${month}月${day}日`;
|
||||
},
|
||||
getDayColor(day) {
|
||||
if (day.count === 0) return this.intensityColors[0];
|
||||
if (day.count <= 2) return this.intensityColors[1];
|
||||
if (day.count <= 4) return this.intensityColors[2];
|
||||
if (day.count <= 6) return this.intensityColors[3];
|
||||
return this.intensityColors[4];
|
||||
},
|
||||
getTooltipText(day) {
|
||||
if (!day.date) return '无数据';
|
||||
|
||||
const dateStr = this.formatDisplayDate(day.date);
|
||||
if (day.count === 0) {
|
||||
return `${dateStr}: 发布 0 篇文章`;
|
||||
} else {
|
||||
return `${dateStr}: 发布 ${day.count} 篇文章`;
|
||||
}
|
||||
},
|
||||
handleDayClick(day, index) {
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: this.getTooltipText(day)
|
||||
});
|
||||
},
|
||||
changeYear(value) {
|
||||
this.filterAndSetCurrentYearData(value);
|
||||
this.$emit('year-change', value);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.contribution-heatmap {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.12), 0 2rpx 4rpx rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
color: #000;
|
||||
border: 2rpx solid #ebedf0;
|
||||
border-radius: 10rpx;
|
||||
padding: 8rpx 12rpx;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 30rpx;
|
||||
}
|
||||
|
||||
.year-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.year-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: #0969da;
|
||||
font-size: 28rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
.year-btn:hover {
|
||||
background-color: #f6f8fa;
|
||||
}
|
||||
|
||||
.year-display {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
min-width: 120rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.legend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2rpx;
|
||||
font-size: 24rpx;
|
||||
border: 2rpx solid #ebedf0;
|
||||
border-radius: 10rpx;
|
||||
padding: 8rpx 3rpx;
|
||||
}
|
||||
|
||||
.legend-text {
|
||||
margin: 0 10rpx;
|
||||
}
|
||||
.legend-day-cell {
|
||||
width: 24rpx!important;
|
||||
height: 24rpx!important;
|
||||
}
|
||||
.heatmap-container {
|
||||
display: flex;
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.weeks {
|
||||
margin-top: -2rpx;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-right: 10rpx;
|
||||
padding-top: 48rpx;
|
||||
}
|
||||
|
||||
.week-label {
|
||||
font-size: 24rpx;
|
||||
color: #7e7e7f;
|
||||
height: 30rpx;
|
||||
line-height: 30rpx;
|
||||
margin-bottom: 6rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.months {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.month-label {
|
||||
flex: 1;
|
||||
flex-shrink: 0;
|
||||
font-size: 24rpx;
|
||||
color: #7e7e7f;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.heatmap-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.days-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
height: 252rpx; /* 7行 * 30rpx + 7行 * 6rpx间距 */
|
||||
}
|
||||
|
||||
.day-cell {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
border-radius: 4rpx;
|
||||
margin: 3rpx;
|
||||
background-color: #ebedf0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 12rpx;
|
||||
font-size: 24rpx;
|
||||
color: #586069;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.releases-count{
|
||||
display: flex;
|
||||
gap: 0 6rpx;
|
||||
box-sizing: border-box;
|
||||
border: 2rpx solid #ebedf0;
|
||||
border-radius: 10rpx;
|
||||
padding: 8rpx 12rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -1,735 +0,0 @@
|
||||
export default{
|
||||
data(){
|
||||
return{
|
||||
system_info:{}, //system info
|
||||
canvas_width:0, //canvas width px
|
||||
canvas_height:0, //canvas height px
|
||||
ctx:null, //canvas object
|
||||
canvas_id:null, //canvas id
|
||||
hidden:false,//Whether to hide canvas
|
||||
scale:1,//canvas scale
|
||||
r_canvas_scale:1,
|
||||
if_ctx:true
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
/**
|
||||
* save r-canvas.vue object
|
||||
* @param {Object} that
|
||||
*/
|
||||
// saveThis(that){
|
||||
// rCanvasThis = that
|
||||
// },
|
||||
/**
|
||||
* Draw round rect text
|
||||
* @param {Object} config
|
||||
* @param {Number} config.x x坐标
|
||||
* @param {Number} config.y y坐标
|
||||
* @param {Number} config.w 宽度
|
||||
* @param {Number} config.h 高度
|
||||
* @param {Number} config.radius 圆角弧度
|
||||
* @param {String} config.fill_color 矩形颜色
|
||||
*/
|
||||
fillRoundRect(config) {
|
||||
return new Promise((resolve,reject)=>{
|
||||
let x = this.compatibilitySize(parseFloat(config.x)*this.scale)
|
||||
let y = this.compatibilitySize(parseFloat(config.y)*this.scale)
|
||||
let w = this.compatibilitySize(parseFloat(config.w)*this.scale)
|
||||
let h = this.compatibilitySize(parseFloat(config.h)*this.scale)
|
||||
let radius = config.radius?parseFloat(config.radius)*this.scale:10*this.scale
|
||||
|
||||
let fill_color = config.fill_color || "black"
|
||||
// The diameter of the circle must be less than the width and height of the rectangle
|
||||
if (2 * radius > w || 2 * radius > h) {
|
||||
reject("The diameter of the circle must be less than the width and height of the rectangle")
|
||||
return false;
|
||||
}
|
||||
this.ctx.save();
|
||||
this.ctx.translate(x, y);
|
||||
//
|
||||
this.drawRoundRectPath({
|
||||
w: w,
|
||||
h: h,
|
||||
radius: radius
|
||||
});
|
||||
this.ctx.fillStyle = fill_color
|
||||
this.ctx.fill();
|
||||
this.ctx.restore();
|
||||
resolve()
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Draws the sides of a rounded rectangle
|
||||
* @param {Object} config
|
||||
* @param {Number} config.w 宽度
|
||||
* @param {Number} config.h 高度
|
||||
* @param {Number} config.radius 圆角弧度
|
||||
*/
|
||||
drawRoundRectPath(config) {
|
||||
this.ctx.beginPath(0);
|
||||
this.ctx.arc(config.w - config.radius, config.h - config.radius, config.radius, 0, Math.PI / 2);
|
||||
this.ctx.lineTo(config.radius, config.h);
|
||||
this.ctx.arc(config.radius, config.h - config.radius, config.radius, Math.PI / 2, Math.PI);
|
||||
this.ctx.lineTo(0, config.radius);
|
||||
this.ctx.arc(config.radius, config.radius, config.radius, Math.PI, Math.PI * 3 / 2);
|
||||
this.ctx.lineTo(config.w - config.radius, 0);
|
||||
this.ctx.arc(config.w - config.radius, config.radius, config.radius, Math.PI * 3 / 2, Math.PI * 2);
|
||||
this.ctx.lineTo(config.w, config.h - config.radius);
|
||||
this.ctx.closePath();
|
||||
},
|
||||
/**
|
||||
* Draw special Text,line wrapping is not supported
|
||||
* @param {Object} config
|
||||
* @param {String} config.text 文字
|
||||
* @param {Number} config.x x坐标
|
||||
* @param {Number} config.y y坐标
|
||||
* @param {String} config.font_color 文字颜色
|
||||
* @param {String} config.font_family 文字字体
|
||||
* @param {Number} config.font_size 文字大小(px)
|
||||
*/
|
||||
drawSpecialText(params){
|
||||
let general = params.general
|
||||
let list = params.list
|
||||
return new Promise(async (resolve,reject)=>{
|
||||
if(!general){
|
||||
reject("general cannot be empty:101")
|
||||
return;
|
||||
}else if(list && list.length>0){
|
||||
for(let i in list){
|
||||
if(i != 0){
|
||||
let font_size = list[i-1].font_size?parseFloat(list[i-1].font_size):20
|
||||
this.ctx.setFontSize(font_size)
|
||||
general.x = parseFloat(general.x) + this.ctx.measureText(list[i-1].text).width
|
||||
}
|
||||
list[i].x = general.x
|
||||
list[i].y = general.y + (list[i].margin_top?parseFloat(list[i].margin_top):0)
|
||||
await this.drawText(list[i])
|
||||
}
|
||||
resolve()
|
||||
}else{
|
||||
reject("The length of config arr is less than 0")
|
||||
return;
|
||||
}
|
||||
|
||||
})
|
||||
},
|
||||
/**
|
||||
* array delete empty
|
||||
* @param {Object} arr
|
||||
*/
|
||||
arrDeleteEmpty(arr){
|
||||
let newArr = []
|
||||
for(let i in arr){
|
||||
if(arr[i]){
|
||||
newArr.push(arr[i])
|
||||
}
|
||||
}
|
||||
return newArr
|
||||
},
|
||||
/**
|
||||
* Draw Text,support line
|
||||
* @param {Object} config
|
||||
* @param {String} config.text 文字
|
||||
* @param {Number} config.max_width 文字最大宽度(大于宽度自动换行)
|
||||
* @param {Number} config.line_height 文字上下行间距
|
||||
* @param {Number} config.x x坐标
|
||||
* @param {Number} config.y y坐标
|
||||
* @param {String} config.font_color 文字颜色
|
||||
* @param {String} config.font_family 文字字体 默认值:Arial
|
||||
* @param {String} config.text_align 文字对齐方式(left/center/right)
|
||||
* @param {Number} config.font_size 文字大小(px)
|
||||
* @param {Boolean} config.line_through_height 中划线大小
|
||||
* @param {Boolean} config.line_through_color 中划线颜色
|
||||
* @param {String} config.font_style 规定文字样式
|
||||
* @param {String} config.font_variant 规定字体变体
|
||||
* @param {String} config.font_weight 规定字体粗细
|
||||
* @param {String} config.line_through_cap 线末端类型
|
||||
* @param {String} config.line_clamp 最大行数
|
||||
* @param {String} config.line_clamp_hint 超过line_clamp后,尾部显示的自定义标识 如 ...
|
||||
* @param {String} config.is_line_break 是否开启换行符换行
|
||||
*
|
||||
*/
|
||||
drawText(config,configuration = {}){
|
||||
|
||||
configuration['line_num'] = configuration.line_num?configuration.line_num:0
|
||||
configuration['text_width'] = configuration.text_width?configuration.text_width:0
|
||||
|
||||
return new Promise(async (resolve,reject)=>{
|
||||
|
||||
if(config.text){
|
||||
|
||||
let draw_width = 0,draw_height = 0,draw_x = config.x,draw_y = config.y
|
||||
let font_size = config.font_size?(parseFloat(config.font_size)*this.scale):(20*this.scale)
|
||||
let font_color = config.font_color || "#000"
|
||||
let font_family = config.font_family || "Arial"
|
||||
let line_height = config.line_height || config.font_size || 20
|
||||
let text_align = config.text_align || "left"
|
||||
let font_weight = config.font_weight || "normal"
|
||||
let font_variant = config.font_variant || "normal"
|
||||
let font_style = config.font_style || "normal"
|
||||
let line_clamp_hint = config.line_clamp_hint || '...'
|
||||
let lineBreakJoinText = ""
|
||||
let max_width = config.max_width?parseFloat(config.max_width)*this.scale:0
|
||||
// checkout is line break
|
||||
if(config.is_line_break){
|
||||
let splitTextArr = config.text.split(/[\n]/g)
|
||||
if(splitTextArr && splitTextArr.length > 0){
|
||||
let newSplitTextArr = this.arrDeleteEmpty(splitTextArr)
|
||||
if(newSplitTextArr && newSplitTextArr.length > 0){
|
||||
lineBreakJoinText = newSplitTextArr.slice(1).join("\n")
|
||||
config.text = newSplitTextArr[0]
|
||||
}else{
|
||||
reject("Text cannot be empty:103")
|
||||
return
|
||||
}
|
||||
}else{
|
||||
reject("Text cannot be empty:102")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.ctx.setFillStyle(font_color) // color
|
||||
this.ctx.textAlign = text_align;
|
||||
this.ctx.font = `${font_style} ${font_variant} ${font_weight} ${parseInt(font_size)}px ${font_family}`
|
||||
if(configuration.text_width >= this.ctx.measureText(config.text).width){
|
||||
draw_width = configuration.text_width
|
||||
}else if(max_width > 0){
|
||||
draw_width = max_width < this.ctx.measureText(config.text).width ? this.resetCompatibilitySize(max_width) : this.resetCompatibilitySize(this.ctx.measureText(config.text).width)
|
||||
}else{
|
||||
draw_width = this.ctx.measureText(config.text).width
|
||||
}
|
||||
configuration.text_width = draw_width / this.scale
|
||||
if( max_width && this.compatibilitySize(this.ctx.measureText(config.text).width) > this.compatibilitySize(max_width)){
|
||||
let current_text = ""
|
||||
let text_arr = config.text.split("")
|
||||
for(let i in text_arr){
|
||||
if( this.compatibilitySize(this.ctx.measureText(current_text+text_arr[i]).width) > this.compatibilitySize(max_width) ){
|
||||
// Hyphenation that is greater than the drawable width continues to draw
|
||||
if(config.line_clamp && parseInt(config.line_clamp) == 1){
|
||||
// Subtracting the current_text tail width from the line_clamp_hint width
|
||||
let current_text_arr = current_text.split('')
|
||||
let json_current_text = ''
|
||||
while(true){
|
||||
current_text_arr = current_text_arr.slice(1)
|
||||
json_current_text = current_text_arr.join('')
|
||||
if(this.compatibilitySize(this.ctx.measureText(json_current_text).width) <= this.compatibilitySize(this.ctx.measureText(line_clamp_hint).width)){
|
||||
current_text = current_text.replace(json_current_text,'')
|
||||
break;
|
||||
}
|
||||
}
|
||||
configuration.line_num += 1
|
||||
this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font size
|
||||
this.ctx.fillText(current_text + line_clamp_hint, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale));
|
||||
}else{
|
||||
configuration.line_num += 1
|
||||
this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font size
|
||||
this.ctx.fillText(current_text, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale));
|
||||
config.text = text_arr.slice(i).join("")
|
||||
config.y = config.y + line_height
|
||||
if(config.line_clamp){
|
||||
config.line_clamp = parseInt(config.line_clamp) - 1
|
||||
}
|
||||
await this.drawText(config,configuration)
|
||||
}
|
||||
|
||||
break;
|
||||
}else{
|
||||
current_text = current_text+text_arr[i]
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if(config.line_through_height){
|
||||
let x = parseFloat(config.x)*this.scale
|
||||
let w
|
||||
let y = parseFloat(config.y)*this.scale - (font_size / 2.6)
|
||||
if(text_align == "left"){
|
||||
w = this.ctx.measureText(config.text).width/1.1 + parseFloat(config.x)*this.scale
|
||||
}else if(text_align == "right"){
|
||||
w = parseFloat(config.x)*this.scale - this.ctx.measureText(config.text).width/1.1
|
||||
}else if(text_align == "center"){
|
||||
x = parseFloat(config.x)*this.scale - this.ctx.measureText(config.text).width / 1.1 / 2
|
||||
w = parseFloat(config.x)*this.scale + this.ctx.measureText(config.text).width / 1.1 / 2
|
||||
}
|
||||
this.drawLineTo({
|
||||
x:x,
|
||||
y:y,
|
||||
w:w,
|
||||
h:y,
|
||||
line_width:config.line_through_height,
|
||||
line_color:config.line_through_color,
|
||||
line_cap:config.line_through_cap
|
||||
})
|
||||
}
|
||||
configuration.line_num += 1
|
||||
this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font size
|
||||
this.ctx.fillText(config.text, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale));
|
||||
if(config.line_clamp){
|
||||
config.line_clamp = parseInt(config.line_clamp) - 1
|
||||
}
|
||||
}
|
||||
if(lineBreakJoinText){
|
||||
await this.drawText({...config,text:lineBreakJoinText,y:config.y + line_height},configuration)
|
||||
}
|
||||
draw_height = config.font_size * configuration.line_num
|
||||
draw_width = configuration.text_width
|
||||
resolve({draw_width,draw_height,draw_x,draw_y})
|
||||
}else{
|
||||
reject("Text cannot be empty:101")
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Draw Line
|
||||
* @param {Object} config
|
||||
* @param {Object} config.x x坐标
|
||||
* @param {Object} config.y y坐标
|
||||
* @param {Object} config.w 线的宽度
|
||||
* @param {Object} config.h 线的高度
|
||||
* @param {Object} config.line_width 线的宽度
|
||||
* @param {Object} config.line_color 线条颜色
|
||||
*/
|
||||
drawLineTo(config){
|
||||
let x = this.compatibilitySize(config.x)
|
||||
let y = this.compatibilitySize(config.y)
|
||||
let w = this.compatibilitySize(config.w)
|
||||
let h = this.compatibilitySize(config.h)
|
||||
let line_width = config.line_width?parseFloat(config.line_width)*this.scale:1*this.scale
|
||||
let line_color = config.line_color || "black"
|
||||
let line_cap = config.line_cap || "butt"
|
||||
this.ctx.beginPath()
|
||||
this.ctx.lineCap = line_cap
|
||||
this.ctx.lineWidth = line_width
|
||||
this.ctx.strokeStyle = line_color
|
||||
this.ctx.moveTo(x,y)
|
||||
this.ctx.lineTo(w,h)
|
||||
this.ctx.stroke()
|
||||
},
|
||||
/**
|
||||
* Compatibility px
|
||||
* @param {Object} size
|
||||
*/
|
||||
compatibilitySize(size) {
|
||||
let canvasSize = (parseFloat(size) / 750) * this.system_info.windowWidth
|
||||
canvasSize = parseFloat(canvasSize * 2)
|
||||
return canvasSize
|
||||
},
|
||||
/**
|
||||
* Restore compatibility px
|
||||
* @param {Object} size
|
||||
*/
|
||||
resetCompatibilitySize(size) {
|
||||
let canvasSize = (parseFloat(size/2)/this.system_info.windowWidth) * 750
|
||||
return canvasSize
|
||||
},
|
||||
/**
|
||||
* Init canvas
|
||||
*/
|
||||
init(config){
|
||||
return new Promise(async (resolve,reject)=>{
|
||||
if(!config.canvas_id){
|
||||
reject("Canvas ID cannot be empty, please refer to the usage example")
|
||||
return;
|
||||
}
|
||||
this.hidden = config.hidden
|
||||
this.canvas_id = config.canvas_id
|
||||
let system_info = await uni.getSystemInfoSync()
|
||||
this.system_info = system_info
|
||||
this.scale = config.scale&&parseFloat(config.scale)>0?parseInt(config.scale):1
|
||||
this.canvas_width = (config.canvas_width ? this.compatibilitySize(config.canvas_width) : system_info.windowWidth) * this.scale
|
||||
this.canvas_height = (config.canvas_height ? this.compatibilitySize(config.canvas_height) : system_info.windowHeight) * this.scale,
|
||||
this.r_canvas_scale = 1/this.scale
|
||||
this.ctx = uni.createCanvasContext(this.canvas_id,this)
|
||||
this.setCanvasConfig({
|
||||
global_alpha:config.global_alpha?parseFloat(config.global_alpha):1,
|
||||
backgroundColor:config.background_color?config.background_color:"#fff"
|
||||
})
|
||||
resolve()
|
||||
})
|
||||
},
|
||||
/**
|
||||
* clear canvas all path
|
||||
*/
|
||||
clearCanvas(){
|
||||
return new Promise(async (resolve,reject)=>{
|
||||
if(!this.ctx){
|
||||
reject("canvas is not initialized:101")
|
||||
return
|
||||
}else{
|
||||
this.ctx.clearRect(0,0,parseFloat(this.canvas_width)*this.scale,parseFloat(this.canvas_height)*this.scale)
|
||||
await this.draw()
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Set canvas config
|
||||
* @param {Object} config
|
||||
*/
|
||||
setCanvasConfig(config){
|
||||
this.ctx.globalAlpha = config.global_alpha
|
||||
this.ctx.fillStyle = config.backgroundColor
|
||||
this.ctx.fillRect(0, 0, parseFloat(this.canvas_width)*this.scale, parseFloat(this.canvas_height)*this.scale)
|
||||
},
|
||||
/**
|
||||
* set canvas width
|
||||
* @param {Object} width
|
||||
*/
|
||||
setCanvasWidth(width){
|
||||
if(!width){
|
||||
uni.showToast({
|
||||
title:'setCanvasWidth:width error',
|
||||
icon:'none'
|
||||
})
|
||||
}
|
||||
this.canvas_width = this.compatibilitySize(parseFloat(width)) * this.scale
|
||||
this.ctx.width = this.canvas_width
|
||||
},
|
||||
/**
|
||||
* set canvas height
|
||||
* @param {Object} height
|
||||
*/
|
||||
setCanvasHeight(height){
|
||||
if(!height){
|
||||
uni.showToast({
|
||||
title:'setCanvasWidth:height error',
|
||||
icon:'none'
|
||||
})
|
||||
}
|
||||
this.canvas_height = this.compatibilitySize(parseFloat(height)) * this.scale
|
||||
this.ctx.height = this.canvas_height
|
||||
},
|
||||
/**
|
||||
* Draw to filepath
|
||||
*/
|
||||
draw(callback){
|
||||
return new Promise((resolve,reject)=>{
|
||||
let stop = setTimeout(()=>{
|
||||
this.ctx.draw(false,setTimeout(()=>{
|
||||
uni.canvasToTempFilePath({
|
||||
canvasId: this.canvas_id,
|
||||
quality: 1,
|
||||
success: (res)=>{
|
||||
console.log('res',res)
|
||||
resolve(res)
|
||||
callback && callback(res)
|
||||
},
|
||||
fail:(err)=>{
|
||||
reject(JSON.stringify(err)|| "Failed to generate poster:101")
|
||||
}
|
||||
},this)
|
||||
},300))
|
||||
clearTimeout(stop)
|
||||
},300)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* draw rect
|
||||
* @param {Number} config.x x坐标
|
||||
* @param {Number} config.y y坐标
|
||||
* @param {Number} config.w 图形宽度(px)
|
||||
* @param {Number} config.h 图形高度(px)
|
||||
* @param {Number} config.color 图形颜色
|
||||
* @param {Number} config.is_radius 是否开启圆图(1.1.6及以下版本废弃,请使用border_radius)
|
||||
* @param {Number} config.border_width 边框大小
|
||||
* @param {Number} config.border_color 边框颜色
|
||||
*
|
||||
*/
|
||||
drawRect(config){
|
||||
return new Promise(async (resolve,reject)=>{
|
||||
if(!config.border_width || config.border_width <=0){
|
||||
config.border_width = 0
|
||||
}else{
|
||||
config.border_width = parseFloat(config.border_width)
|
||||
}
|
||||
if(parseFloat(config.border_width) > 0){
|
||||
let sub_config = JSON.parse(JSON.stringify(config))
|
||||
sub_config.border_width = 0
|
||||
sub_config.w = config.w + config.border_width
|
||||
sub_config.h = config.h + config.border_width
|
||||
sub_config.color = config.border_color || 'black'
|
||||
if(sub_config.border_radius){
|
||||
sub_config.border_radius = parseFloat(sub_config.border_radius) + parseFloat(config.border_width) / 2
|
||||
}
|
||||
await this.drawRect(sub_config)
|
||||
}
|
||||
|
||||
let color = config.color || 'white'
|
||||
config.x = (parseFloat(config.x) + config.border_width / 2)
|
||||
config.y = (parseFloat(config.y) + config.border_width / 2)
|
||||
config['color'] = color
|
||||
this.ctx.fillStyle = color;
|
||||
if(config.is_radius || config.border_radius){
|
||||
this.setNativeBorderRadius(config)
|
||||
this.ctx.fill()
|
||||
}else{
|
||||
console.log('config.border_width',config.border_width)
|
||||
this.ctx.fillRect(this.compatibilitySize(config.x*this.scale),this.compatibilitySize(config.y*this.scale),this.compatibilitySize(parseFloat(config.w)*this.scale),this.compatibilitySize(parseFloat(config.h)*this.scale))
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Draw image
|
||||
* @param {Object} config
|
||||
* @param {String} config.url 图片链接
|
||||
* @param {Number} config.x x坐标
|
||||
* @param {Number} config.y y坐标
|
||||
* @param {Number} config.w 图片宽度(px)
|
||||
* @param {Number} config.h 图片高度(px)
|
||||
* @param {Number} config.border_width 边大小
|
||||
* @param {Number} config.border_color 边颜色
|
||||
* @param {Number} config.is_radius 是否开启圆图(1.1.6及以下版本废弃,请使用border_radius)
|
||||
* @param {Number} config.border_radius 圆角弧度
|
||||
*/
|
||||
drawImage(config){
|
||||
return new Promise(async (resolve,reject)=>{
|
||||
if(config.url){
|
||||
let type = 0 // 1、network image 2、native image 3、base64 image
|
||||
let image_url
|
||||
let reg = /^https?/ig;
|
||||
if(reg.test(config.url)){
|
||||
type = 1
|
||||
}else{
|
||||
if((config.url.indexOf("data:image/png;base64") != -1) || config.url.indexOf("data:image/jpeg;base64") != -1 || config.url.indexOf("data:image/gif;base64") != -1){
|
||||
type = 3
|
||||
}else{
|
||||
type = 2
|
||||
}
|
||||
}
|
||||
if(type == 1){
|
||||
// network image
|
||||
await this.downLoadNetworkFile(config.url).then(res=>{ // two function
|
||||
image_url = res
|
||||
}).catch(err=>{
|
||||
reject(err)
|
||||
return;
|
||||
})
|
||||
}else if(type == 2){
|
||||
// native image
|
||||
const imageInfoResult = await uni.getImageInfo({
|
||||
src: config.url
|
||||
});
|
||||
try{
|
||||
if(imageInfoResult.length <= 1){
|
||||
reject(imageInfoResult[0].errMsg + ':404')
|
||||
return
|
||||
}
|
||||
}catch(e){
|
||||
reject(e+':500')
|
||||
return
|
||||
}
|
||||
let base64 = await this.urlToBase64({url:imageInfoResult[1].path})
|
||||
// #ifdef MP-WEIXIN
|
||||
await this.base64ToNative({url:base64}).then(res=>{
|
||||
image_url = res
|
||||
}).catch(err=>{
|
||||
reject(JSON.stringify(err)+":501")
|
||||
return;
|
||||
})
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
image_url = base64
|
||||
// #endif
|
||||
|
||||
}else if(type == 3){
|
||||
// #ifdef MP-WEIXIN
|
||||
await this.base64ToNative({url:config.url}).then(res=>{
|
||||
image_url = res
|
||||
}).catch(err=>{
|
||||
reject(JSON.stringify(err)+":500")
|
||||
return;
|
||||
})
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
image_url = config.url
|
||||
// #endif
|
||||
}else{
|
||||
reject("Other Type Errors:101")
|
||||
return
|
||||
}
|
||||
if(config.border_width){
|
||||
let border_radius = 0
|
||||
if(config.border_radius){
|
||||
let multiple = config.w / config.border_radius
|
||||
border_radius = (parseFloat(config.w) + parseFloat(config.border_width)) / multiple
|
||||
}
|
||||
// drawRect
|
||||
await this.drawRect({
|
||||
x:parseFloat(config.x) - parseFloat(config.border_width)/2,
|
||||
y:parseFloat(config.y) - parseFloat(config.border_width)/2,
|
||||
w:parseFloat(config.w) + parseFloat(config.border_width),
|
||||
h:parseFloat(config.h) + parseFloat(config.border_width),
|
||||
color:config.border_color,
|
||||
border_radius:border_radius,
|
||||
border_width:config.border_width,
|
||||
is_radius:config.is_radius
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
if(config.border_radius){
|
||||
config.color = config.color?config.color:'rgba(0,0,0,0)'
|
||||
|
||||
// 圆角有白边,+0.5的误差
|
||||
config.w = config.w + 0.3
|
||||
config.h = config.h + 0.3
|
||||
|
||||
this.setNativeBorderRadius(config)
|
||||
}else if(config.is_radius){
|
||||
//已废弃 is_radius
|
||||
this.ctx.setStrokeStyle("rgba(0,0,0,0)")
|
||||
this.ctx.save()
|
||||
this.ctx.beginPath()
|
||||
this.ctx.arc(this.compatibilitySize(parseFloat(config.x)*this.scale+parseFloat(config.w)*this.scale/2), this.compatibilitySize(parseFloat(config.y)*this.scale+parseFloat(config.h)*this.scale/2), this.compatibilitySize(parseFloat(config.w)*this.scale/2), 0, 2 * Math.PI, false)
|
||||
this.ctx.stroke();
|
||||
this.ctx.clip()
|
||||
}
|
||||
|
||||
await this.ctx.drawImage(image_url,this.compatibilitySize(parseFloat(config.x)*this.scale),this.compatibilitySize(parseFloat(config.y)*this.scale),this.compatibilitySize(parseFloat(config.w)*this.scale),this.compatibilitySize(parseFloat(config.h)*this.scale))
|
||||
this.ctx.restore() //Restore previously saved drawing context
|
||||
resolve()
|
||||
}else{
|
||||
let err_msg = "Links cannot be empty:101"
|
||||
reject(err_msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* base64 to native available path
|
||||
* @param {Object} config
|
||||
*/
|
||||
base64ToNative(config){
|
||||
return new Promise((resolve,reject)=>{
|
||||
let fileName = new Date().getTime()
|
||||
var filePath = `${wx.env.USER_DATA_PATH}/${fileName}_rCanvas.png`
|
||||
wx.getFileSystemManager().writeFile({
|
||||
filePath: filePath,
|
||||
data: config.url.replace(/^data:\S+\/\S+;base64,/, ''),
|
||||
encoding: 'base64',
|
||||
success: function() {
|
||||
resolve(filePath)
|
||||
},
|
||||
fail: function(error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
/**
|
||||
* native url to base64
|
||||
* @param {Object} config
|
||||
*/
|
||||
urlToBase64(config){
|
||||
return new Promise(async (resolve,reject)=>{
|
||||
if (typeof window != 'undefined') {
|
||||
await this.downLoadNetworkFile(config.url).then(res=>{ // two function
|
||||
resolve(res)
|
||||
}).catch(err=>{
|
||||
reject(err)
|
||||
})
|
||||
}else if (typeof plus != 'undefined') {
|
||||
plus.io.resolveLocalFileSystemURL(config.url,(obj)=>{
|
||||
obj.file((file)=>{
|
||||
let fileReader = new plus.io.FileReader()
|
||||
fileReader.onload = (res)=>{
|
||||
resolve(res.target.result)
|
||||
}
|
||||
fileReader.onerror = (err)=>{
|
||||
reject(err)
|
||||
}
|
||||
fileReader.readAsDataURL(file)
|
||||
}, (err)=>{
|
||||
reject(err)
|
||||
})
|
||||
},(err)=>{
|
||||
reject(err)
|
||||
})
|
||||
}else if(typeof wx != 'undefined'){
|
||||
wx.getFileSystemManager().readFile({
|
||||
filePath: config.url,
|
||||
encoding: 'base64',
|
||||
success: function(res) {
|
||||
resolve('data:image/png;base64,' + res.data)
|
||||
},
|
||||
fail: function(error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
setNativeBorderRadius(config){
|
||||
let border_radius = config.border_radius?(parseFloat(config.border_radius)*this.scale):(20*this.scale)
|
||||
if ((parseFloat(config.w)*this.scale) < 2 * border_radius) border_radius = (parseFloat(config.w)*this.scale) / 2;
|
||||
if ((parseFloat(config.h)*this.scale) < 2 * border_radius) border_radius = (parseFloat(config.h)*this.scale) / 2;
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + border_radius), this.compatibilitySize((parseFloat(config.y)*this.scale)));
|
||||
this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize(border_radius));
|
||||
this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize(border_radius));
|
||||
this.ctx.arcTo((this.compatibilitySize(parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize(border_radius));
|
||||
this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize(border_radius));
|
||||
this.ctx.closePath();
|
||||
this.ctx.strokeStyle = config.color || config.border_color || 'rgba(0,0,0,0)'; // 设置绘制边框的颜色
|
||||
this.ctx.stroke();
|
||||
this.ctx.save()
|
||||
this.ctx.clip();
|
||||
|
||||
},
|
||||
/**
|
||||
* Download network file
|
||||
* @param {Object} url : download url
|
||||
*/
|
||||
downLoadNetworkFile(url){
|
||||
return new Promise((resolve,reject)=>{
|
||||
uni.downloadFile({
|
||||
url,
|
||||
success:(res)=>{
|
||||
if(res.statusCode == 200){
|
||||
resolve(res.tempFilePath)
|
||||
}else{
|
||||
reject("Download Image Fail:102")
|
||||
}
|
||||
},
|
||||
fail:(err)=>{
|
||||
reject("Download Image Fail:101")
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Save image to natice
|
||||
* @param {Object} filePath : native imageUrl
|
||||
*/
|
||||
saveImage(filePath){
|
||||
return new Promise((resolve,reject)=>{
|
||||
if(!filePath){
|
||||
reject("FilePath cannot be null:101")
|
||||
return;
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
var createA = document.createElement("a");
|
||||
createA.download = filePath;
|
||||
createA.href = filePath;
|
||||
document.body.appendChild(createA);
|
||||
createA.click();
|
||||
createA.remove();
|
||||
resolve()
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: filePath,
|
||||
success:(res)=>{
|
||||
resolve(res)
|
||||
},
|
||||
fail:(err)=>{
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="r-canvas-component" :style="{width:canvas_width/scale+'px',height:canvas_height/scale+'px'}" :class="{'hidden':hidden}">
|
||||
<canvas class="r-canvas" v-if="canvas_id" :canvas-id="canvas_id" :id="canvas_id" :style="{width:canvas_width+'px',height:canvas_height+'px','transform': `scale(${r_canvas_scale})`}"></canvas>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import rCanvasJS from "./r-canvas.js"
|
||||
export default {
|
||||
mixins:[rCanvasJS]
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.r-canvas{
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
.r-canvas-component{
|
||||
overflow: hidden;
|
||||
}
|
||||
.r-canvas-component.hidden{
|
||||
position: fixed;
|
||||
top:-5000upx;
|
||||
}
|
||||
</style>
|
||||
@@ -1,784 +0,0 @@
|
||||
<template>
|
||||
<view v-show="show" class="t-wrapper" @touchmove.stop.prevent="moveHandle">
|
||||
<view class="t-mask" :class="{active:active}" @click.stop="close"></view>
|
||||
<view class="t-box" :class="{active:active}">
|
||||
<view class="t-header">
|
||||
<view class="t-header-button" @click="close">取消</view>
|
||||
<view class="t-header-button" @click="confirm">确认</view>
|
||||
</view>
|
||||
<view class="t-color__box" :style="{ background: 'rgb(' + bgcolor.r + ',' + bgcolor.g + ',' + bgcolor.b + ')'}">
|
||||
<view class="t-background boxs" @touchstart="touchstart($event, 0)" @touchmove="touchmove($event, 0)" @touchend="touchend($event, 0)">
|
||||
<view class="t-color-mask"></view>
|
||||
<view class="t-pointer" :style="{ top: site[0].top - 8 + 'px', left: site[0].left - 8 + 'px' }"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="t-control__box">
|
||||
<view class="t-control__color">
|
||||
<view class="t-control__color-content" :style="{ background: 'rgba(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ',' + rgba.a + ')' }"></view>
|
||||
</view>
|
||||
<view class="t-control-box__item">
|
||||
<view class="t-controller boxs" @touchstart="touchstart($event, 1)" @touchmove="touchmove($event, 1)" @touchend="touchend($event, 1)">
|
||||
<view class="t-hue">
|
||||
<view class="t-circle" :style="{ left: site[1].left - 12 + 'px' }"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="t-controller boxs" @touchstart="touchstart($event, 2)" @touchmove="touchmove($event, 2)" @touchend="touchend($event, 2)">
|
||||
<view class="t-transparency">
|
||||
<view class="t-circle" :style="{ left: site[2].left - 12 + 'px' }"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="t-result__box">
|
||||
<view v-if="mode" class="t-result__item">
|
||||
<view class="t-result__box-input">{{hex}}</view>
|
||||
<view class="t-result__box-text">HEX</view>
|
||||
</view>
|
||||
<template v-else>
|
||||
<view class="t-result__item">
|
||||
<view class="t-result__box-input">{{rgba.r}}</view>
|
||||
<view class="t-result__box-text">R</view>
|
||||
</view>
|
||||
<view class="t-result__item">
|
||||
<view class="t-result__box-input">{{rgba.g}}</view>
|
||||
<view class="t-result__box-text">G</view>
|
||||
</view>
|
||||
<view class="t-result__item">
|
||||
<view class="t-result__box-input">{{rgba.b}}</view>
|
||||
<view class="t-result__box-text">B</view>
|
||||
</view>
|
||||
<view class="t-result__item">
|
||||
<view class="t-result__box-input">{{rgba.a}}</view>
|
||||
<view class="t-result__box-text">A</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<view class="t-result__item t-select" @click="select">
|
||||
<view class="t-result__box-input">
|
||||
<view>切换</view>
|
||||
<view>模式</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="t-alternative">
|
||||
<view class="t-alternative__item" v-for="(item,index) in colorList" :key="index">
|
||||
<view class="t-alternative__item-content" :style="{ background: 'rgba(' + item.r + ',' + item.g + ',' + item.b + ',' + item.a + ')' }"
|
||||
@click="selectColor(item)">
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
color: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
spareColor: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
active: false,
|
||||
// rgba 颜色
|
||||
rgba: {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 1
|
||||
},
|
||||
// hsb 颜色
|
||||
hsb: {
|
||||
h: 0,
|
||||
s: 0,
|
||||
b: 0
|
||||
},
|
||||
site: [{
|
||||
top: 0,
|
||||
left: 0
|
||||
}, {
|
||||
left: 0
|
||||
}, {
|
||||
left: 0
|
||||
}],
|
||||
index: 0,
|
||||
bgcolor: {
|
||||
r: 255,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 1
|
||||
},
|
||||
hex: '#000000',
|
||||
mode: true,
|
||||
colorList: [{
|
||||
r: 244,
|
||||
g: 67,
|
||||
b: 54,
|
||||
a: 1
|
||||
}, {
|
||||
r: 233,
|
||||
g: 30,
|
||||
b: 99,
|
||||
a: 1
|
||||
}, {
|
||||
r: 156,
|
||||
g: 39,
|
||||
b: 176,
|
||||
a: 1
|
||||
}, {
|
||||
r: 103,
|
||||
g: 58,
|
||||
b: 183,
|
||||
a: 1
|
||||
}, {
|
||||
r: 63,
|
||||
g: 81,
|
||||
b: 181,
|
||||
a: 1
|
||||
}, {
|
||||
r: 33,
|
||||
g: 150,
|
||||
b: 243,
|
||||
a: 1
|
||||
}, {
|
||||
r: 3,
|
||||
g: 169,
|
||||
b: 244,
|
||||
a: 1
|
||||
}, {
|
||||
r: 0,
|
||||
g: 188,
|
||||
b: 212,
|
||||
a: 1
|
||||
}, {
|
||||
r: 0,
|
||||
g: 150,
|
||||
b: 136,
|
||||
a: 1
|
||||
}, {
|
||||
r: 76,
|
||||
g: 175,
|
||||
b: 80,
|
||||
a: 1
|
||||
}, {
|
||||
r: 139,
|
||||
g: 195,
|
||||
b: 74,
|
||||
a: 1
|
||||
}, {
|
||||
r: 205,
|
||||
g: 220,
|
||||
b: 57,
|
||||
a: 1
|
||||
}, {
|
||||
r: 255,
|
||||
g: 235,
|
||||
b: 59,
|
||||
a: 1
|
||||
}, {
|
||||
r: 255,
|
||||
g: 193,
|
||||
b: 7,
|
||||
a: 1
|
||||
}, {
|
||||
r: 255,
|
||||
g: 152,
|
||||
b: 0,
|
||||
a: 1
|
||||
}, {
|
||||
r: 255,
|
||||
g: 87,
|
||||
b: 34,
|
||||
a: 1
|
||||
}, {
|
||||
r: 121,
|
||||
g: 85,
|
||||
b: 72,
|
||||
a: 1
|
||||
}, {
|
||||
r: 158,
|
||||
g: 158,
|
||||
b: 158,
|
||||
a: 1
|
||||
}, {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 0.5
|
||||
}, {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 0
|
||||
}, ]
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.rgba = this.color;
|
||||
if (this.spareColor.length !== 0) {
|
||||
this.colorList = this.spareColor;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
init() {
|
||||
// hsb 颜色
|
||||
this.hsb = this.rgbToHex(this.rgba);
|
||||
// this.setColor();
|
||||
this.setValue(this.rgba);
|
||||
},
|
||||
moveHandle() {},
|
||||
open() {
|
||||
this.show = true;
|
||||
this.$nextTick(() => {
|
||||
this.init();
|
||||
setTimeout(() => {
|
||||
this.active = true;
|
||||
setTimeout(() => {
|
||||
this.getSelectorQuery();
|
||||
}, 350)
|
||||
}, 50)
|
||||
})
|
||||
|
||||
},
|
||||
close() {
|
||||
this.active = false;
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.show = false;
|
||||
}, 500)
|
||||
})
|
||||
},
|
||||
confirm() {
|
||||
this.close();
|
||||
this.$emit('confirm', {
|
||||
rgba: this.rgba,
|
||||
hex: this.hex
|
||||
})
|
||||
},
|
||||
// 选择模式
|
||||
select() {
|
||||
this.mode = !this.mode
|
||||
},
|
||||
// 常用颜色选择
|
||||
selectColor(item) {
|
||||
this.setColorBySelect(item)
|
||||
},
|
||||
touchstart(e, index) {
|
||||
const {
|
||||
pageX,
|
||||
pageY
|
||||
} = e.touches[0];
|
||||
this.pageX = pageX;
|
||||
this.pageY = pageY;
|
||||
this.setPosition(pageX, pageY, index);
|
||||
},
|
||||
touchmove(e, index) {
|
||||
const {
|
||||
pageX,
|
||||
pageY
|
||||
} = e.touches[0];
|
||||
this.moveX = pageX;
|
||||
this.moveY = pageY;
|
||||
this.setPosition(pageX, pageY, index);
|
||||
},
|
||||
touchend(e, index) {},
|
||||
/**
|
||||
* 设置位置
|
||||
*/
|
||||
setPosition(x, y, index) {
|
||||
this.index = index;
|
||||
const {
|
||||
top,
|
||||
left,
|
||||
width,
|
||||
height
|
||||
} = this.position[index];
|
||||
// 设置最大最小值
|
||||
|
||||
this.site[index].left = Math.max(0, Math.min(parseInt(x - left), width));
|
||||
if (index === 0) {
|
||||
this.site[index].top = Math.max(0, Math.min(parseInt(y - top), height));
|
||||
// 设置颜色
|
||||
this.hsb.s = parseInt((100 * this.site[index].left) / width);
|
||||
this.hsb.b = parseInt(100 - (100 * this.site[index].top) / height);
|
||||
this.setColor();
|
||||
this.setValue(this.rgba);
|
||||
} else {
|
||||
this.setControl(index, this.site[index].left);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 设置 rgb 颜色
|
||||
*/
|
||||
setColor() {
|
||||
const rgb = this.HSBToRGB(this.hsb);
|
||||
this.rgba.r = rgb.r;
|
||||
this.rgba.g = rgb.g;
|
||||
this.rgba.b = rgb.b;
|
||||
},
|
||||
/**
|
||||
* 设置二进制颜色
|
||||
* @param {Object} rgb
|
||||
*/
|
||||
setValue(rgb) {
|
||||
this.hex = '#' + this.rgbToHex(rgb);
|
||||
},
|
||||
setControl(index, x) {
|
||||
const {
|
||||
top,
|
||||
left,
|
||||
width,
|
||||
height
|
||||
} = this.position[index];
|
||||
|
||||
if (index === 1) {
|
||||
this.hsb.h = parseInt((360 * x) / width);
|
||||
this.bgcolor = this.HSBToRGB({
|
||||
h: this.hsb.h,
|
||||
s: 100,
|
||||
b: 100
|
||||
});
|
||||
this.setColor()
|
||||
} else {
|
||||
this.rgba.a = (x / width).toFixed(1);
|
||||
}
|
||||
this.setValue(this.rgba);
|
||||
},
|
||||
/**
|
||||
* rgb 转 二进制 hex
|
||||
* @param {Object} rgb
|
||||
*/
|
||||
rgbToHex(rgb) {
|
||||
let hex = [rgb.r.toString(16), rgb.g.toString(16), rgb.b.toString(16)];
|
||||
hex.map(function(str, i) {
|
||||
if (str.length == 1) {
|
||||
hex[i] = '0' + str;
|
||||
}
|
||||
});
|
||||
return hex.join('');
|
||||
},
|
||||
setColorBySelect(getrgb) {
|
||||
const {
|
||||
r,
|
||||
g,
|
||||
b,
|
||||
a
|
||||
} = getrgb;
|
||||
let rgb = {}
|
||||
rgb = {
|
||||
r: r ? parseInt(r) : 0,
|
||||
g: g ? parseInt(g) : 0,
|
||||
b: b ? parseInt(b) : 0,
|
||||
a: a ? a : 0,
|
||||
};
|
||||
this.rgba = rgb;
|
||||
this.hsb = this.rgbToHsb(rgb);
|
||||
this.changeViewByHsb();
|
||||
},
|
||||
changeViewByHsb() {
|
||||
const [a, b, c] = this.position;
|
||||
this.site[0].left = parseInt(this.hsb.s * a.width / 100);
|
||||
this.site[0].top = parseInt((100 - this.hsb.b) * a.height / 100);
|
||||
this.setColor(this.hsb.h);
|
||||
this.setValue(this.rgba);
|
||||
this.bgcolor = this.HSBToRGB({
|
||||
h: this.hsb.h,
|
||||
s: 100,
|
||||
b: 100
|
||||
});
|
||||
|
||||
this.site[1].left = this.hsb.h / 360 * b.width;
|
||||
this.site[2].left = this.rgba.a * c.width;
|
||||
|
||||
},
|
||||
/**
|
||||
* hsb 转 rgb
|
||||
* @param {Object} 颜色模式 H(hues)表示色相,S(saturation)表示饱和度,B(brightness)表示亮度
|
||||
*/
|
||||
HSBToRGB(hsb) {
|
||||
let rgb = {};
|
||||
let h = Math.round(hsb.h);
|
||||
let s = Math.round((hsb.s * 255) / 100);
|
||||
let v = Math.round((hsb.b * 255) / 100);
|
||||
if (s == 0) {
|
||||
rgb.r = rgb.g = rgb.b = v;
|
||||
} else {
|
||||
let t1 = v;
|
||||
let t2 = ((255 - s) * v) / 255;
|
||||
let t3 = ((t1 - t2) * (h % 60)) / 60;
|
||||
if (h == 360) h = 0;
|
||||
if (h < 60) {
|
||||
rgb.r = t1;
|
||||
rgb.b = t2;
|
||||
rgb.g = t2 + t3;
|
||||
} else if (h < 120) {
|
||||
rgb.g = t1;
|
||||
rgb.b = t2;
|
||||
rgb.r = t1 - t3;
|
||||
} else if (h < 180) {
|
||||
rgb.g = t1;
|
||||
rgb.r = t2;
|
||||
rgb.b = t2 + t3;
|
||||
} else if (h < 240) {
|
||||
rgb.b = t1;
|
||||
rgb.r = t2;
|
||||
rgb.g = t1 - t3;
|
||||
} else if (h < 300) {
|
||||
rgb.b = t1;
|
||||
rgb.g = t2;
|
||||
rgb.r = t2 + t3;
|
||||
} else if (h < 360) {
|
||||
rgb.r = t1;
|
||||
rgb.g = t2;
|
||||
rgb.b = t1 - t3;
|
||||
} else {
|
||||
rgb.r = 0;
|
||||
rgb.g = 0;
|
||||
rgb.b = 0;
|
||||
}
|
||||
}
|
||||
return {
|
||||
r: Math.round(rgb.r),
|
||||
g: Math.round(rgb.g),
|
||||
b: Math.round(rgb.b)
|
||||
};
|
||||
},
|
||||
rgbToHsb(rgb) {
|
||||
let hsb = {
|
||||
h: 0,
|
||||
s: 0,
|
||||
b: 0
|
||||
};
|
||||
let min = Math.min(rgb.r, rgb.g, rgb.b);
|
||||
let max = Math.max(rgb.r, rgb.g, rgb.b);
|
||||
let delta = max - min;
|
||||
hsb.b = max;
|
||||
hsb.s = max != 0 ? 255 * delta / max : 0;
|
||||
if (hsb.s != 0) {
|
||||
if (rgb.r == max) hsb.h = (rgb.g - rgb.b) / delta;
|
||||
else if (rgb.g == max) hsb.h = 2 + (rgb.b - rgb.r) / delta;
|
||||
else hsb.h = 4 + (rgb.r - rgb.g) / delta;
|
||||
} else hsb.h = -1;
|
||||
hsb.h *= 60;
|
||||
if (hsb.h < 0) hsb.h = 0;
|
||||
hsb.s *= 100 / 255;
|
||||
hsb.b *= 100 / 255;
|
||||
return hsb;
|
||||
},
|
||||
getSelectorQuery() {
|
||||
const views = uni.createSelectorQuery().in(this);
|
||||
views
|
||||
.selectAll('.boxs')
|
||||
.boundingClientRect(data => {
|
||||
if (!data || data.length === 0) {
|
||||
setTimeout(() => this.getSelectorQuery(), 20)
|
||||
return
|
||||
}
|
||||
this.position = data;
|
||||
// this.site[0].top = data[0].height;
|
||||
// this.site[0].left = 0;
|
||||
// this.site[1].left = data[1].width;
|
||||
// this.site[2].left = data[2].width;
|
||||
this.setColorBySelect(this.rgba);
|
||||
})
|
||||
.exec();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
spareColor(newVal) {
|
||||
this.colorList = newVal;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.t-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.t-box {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
padding: 30upx 0;
|
||||
padding-top: 0;
|
||||
background: #fff;
|
||||
transition: all 0.3s;
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
.t-box.active {
|
||||
transform: translateY(0%);
|
||||
}
|
||||
|
||||
.t-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 100upx;
|
||||
border-bottom: 1px #eee solid;
|
||||
box-shadow: 1px 0 2px rgba(0, 0, 0, 0.1);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.t-header-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 150upx;
|
||||
height: 100upx;
|
||||
font-size: 30upx;
|
||||
color: #666;
|
||||
padding-left: 20upx;
|
||||
}
|
||||
|
||||
.t-header-button:last-child {
|
||||
justify-content: flex-end;
|
||||
padding-right: 20upx;
|
||||
}
|
||||
|
||||
.t-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
z-index: -1;
|
||||
transition: all 0.3s;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.t-mask.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.t-color__box {
|
||||
position: relative;
|
||||
height: 400upx;
|
||||
background: rgb(255, 0, 0);
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
margin: 0 20upx;
|
||||
margin-top: 20upx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.t-background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0));
|
||||
}
|
||||
|
||||
.t-color-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 400upx;
|
||||
background: linear-gradient(to top, #000, rgba(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
.t-pointer {
|
||||
position: absolute;
|
||||
bottom: -8px;
|
||||
left: -8px;
|
||||
z-index: 2;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border: 1px #fff solid;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.t-show-color {
|
||||
width: 100upx;
|
||||
height: 50upx;
|
||||
}
|
||||
|
||||
.t-control__box {
|
||||
margin-top: 50upx;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
padding-left: 20upx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.t-control__color {
|
||||
flex-shrink: 0;
|
||||
width: 100upx;
|
||||
height: 100upx;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
|
||||
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
|
||||
background-size: 36upx 36upx;
|
||||
background-position: 0 0, 18upx 18upx;
|
||||
border: 1px #eee solid;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.t-control__color-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.t-control-box__item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 0 30upx;
|
||||
}
|
||||
|
||||
.t-controller {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 16px;
|
||||
background-color: #fff;
|
||||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
|
||||
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
|
||||
background-size: 32upx 32upx;
|
||||
background-position: 0 0, 16upx 16upx;
|
||||
}
|
||||
|
||||
.t-hue {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
|
||||
}
|
||||
|
||||
.t-transparency {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(to right, rgba(0, 0, 0, 0) 0%, rgb(0, 0, 0));
|
||||
}
|
||||
|
||||
.t-circle {
|
||||
position: absolute;
|
||||
/* right: -10px; */
|
||||
top: -2px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.t-result__box {
|
||||
margin-top: 20upx;
|
||||
padding: 10upx;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.t-result__item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10upx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.t-result__box-input {
|
||||
padding: 10upx 0;
|
||||
width: 100%;
|
||||
font-size: 28upx;
|
||||
box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.1);
|
||||
color: #999;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.t-result__box-text {
|
||||
margin-top: 10upx;
|
||||
font-size: 28upx;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.t-select {
|
||||
flex-shrink: 0;
|
||||
width: 150upx;
|
||||
padding: 0 30upx;
|
||||
}
|
||||
|
||||
.t-select .t-result__box-input {
|
||||
border-radius: 10upx;
|
||||
border: none;
|
||||
color: #999;
|
||||
box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.1);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.t-select .t-result__box-input:active {
|
||||
box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.t-alternative {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
/* justify-content: space-between; */
|
||||
width: 100%;
|
||||
padding-right: 10upx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.t-alternative__item {
|
||||
margin-left: 12upx;
|
||||
margin-top: 10upx;
|
||||
width: 50upx;
|
||||
height: 50upx;
|
||||
border-radius: 10upx;
|
||||
background-color: #fff;
|
||||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
|
||||
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
|
||||
background-size: 36upx 36upx;
|
||||
background-position: 0 0, 18upx 18upx;
|
||||
border: 1px #eee solid;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.t-alternative__item-content {
|
||||
width: 50upx;
|
||||
height: 50upx;
|
||||
background: rgba(255, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.t-alternative__item:active {
|
||||
transition: all 0.3s;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user