24 changed files with 697 additions and 195 deletions
@ -0,0 +1,74 @@ |
|||||
|
<template> |
||||
|
<view v-if="list.length" class="flex flex-col"> |
||||
|
<view |
||||
|
v-for="(item, index) in list" |
||||
|
:key="index" |
||||
|
class="flex flex-nowrap text-center p-2" |
||||
|
:class="item.checked ? 'bg-blue-500 text-white' : 'bg-gray-100'" |
||||
|
@click="changeChecked(index)" |
||||
|
> |
||||
|
<view class="flex-1 text-left">{{ item.content }} </view> |
||||
|
<view v-if="item.checked"><u-icon name="checkbox-mark"></u-icon></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
name: 'ChooseTask', |
||||
|
props: { tasks: { type: Array, default: () => [] }, time: { type: String, default: '' }, defaultId: { type: String, default: '' } }, |
||||
|
data() { |
||||
|
return { list: [], ids: [] }; |
||||
|
}, |
||||
|
|
||||
|
watch: { |
||||
|
tasks(val) { |
||||
|
if (val) { |
||||
|
this.list = val; |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
defaultId(val) { |
||||
|
if (val) { |
||||
|
this.list.forEach(item => { |
||||
|
if (item.recordId === val) { |
||||
|
item.checked = true; |
||||
|
} else { |
||||
|
item.checked = false; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
|
||||
|
mounted() { |
||||
|
this.list = this.tasks; |
||||
|
this.list.forEach(item => { |
||||
|
if (item.recordId === this.defaultId) { |
||||
|
item.checked = true; |
||||
|
} else { |
||||
|
item.checked = false; |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
methods: { |
||||
|
// 选中某个复选框时,由checkbox时触发 |
||||
|
changeChecked(index) { |
||||
|
let arr = [...this.list]; |
||||
|
for (let i = 0; i < arr.length; i++) { |
||||
|
const item = arr[i]; |
||||
|
if (i === index) { |
||||
|
item.checked = true; |
||||
|
} else { |
||||
|
item.checked = false; |
||||
|
} |
||||
|
} |
||||
|
this.list = [...arr]; |
||||
|
this.$emit('changeDefaultId', this.list[index].recordId); |
||||
|
}, |
||||
|
}, |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped></style> |
||||
@ -0,0 +1,188 @@ |
|||||
|
<template> |
||||
|
<view class="canvas-box w-full"> |
||||
|
<canvas style="width: 100%; height: 450px" canvas-id="firstCanvas"></canvas> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
props: { trainResult: { type: Object, default: () => {} } }, |
||||
|
|
||||
|
data() { |
||||
|
return { |
||||
|
canvas: null, |
||||
|
context: null, |
||||
|
points: [], |
||||
|
timers: [], |
||||
|
width: '', |
||||
|
}; |
||||
|
}, |
||||
|
|
||||
|
watch: { |
||||
|
trainResult(val) { |
||||
|
if (val) { |
||||
|
this.init(); |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
|
||||
|
mounted() { |
||||
|
this.$nextTick(() => { |
||||
|
const query = uni.createSelectorQuery().in(this); |
||||
|
query |
||||
|
.selectAll('.canvas-box') |
||||
|
.boundingClientRect(data => { |
||||
|
if (data && data.length) { |
||||
|
this.width = data[0].width; |
||||
|
this.init(); |
||||
|
} |
||||
|
}) |
||||
|
.exec(); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
methods: { |
||||
|
init() { |
||||
|
this.clearTimers(); |
||||
|
this.initCanvas(); |
||||
|
this.drawImage(this.width); |
||||
|
}, |
||||
|
|
||||
|
// 清除定时器 |
||||
|
clearTimers() { |
||||
|
if (this.timers.length) { |
||||
|
this.timers.forEach(item => { |
||||
|
item && clearTimeout(item); |
||||
|
}); |
||||
|
this.timers = []; |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 初始化canvas元素 ctx数据 |
||||
|
initCanvas() { |
||||
|
this.canvas = this.$refs['reduceCanvas']; |
||||
|
this.context = uni.createCanvasContext('firstCanvas', this); |
||||
|
}, |
||||
|
|
||||
|
// 绘制图片 |
||||
|
drawImage(width) { |
||||
|
const { context } = this; |
||||
|
const { cardUrl, lines } = this.trainResult; |
||||
|
let that = this; |
||||
|
|
||||
|
uni.downloadFile({ |
||||
|
url: cardUrl, |
||||
|
success: function (res) { |
||||
|
context.drawImage(res.tempFilePath, 0, 0, width, 450); |
||||
|
context.draw(true); |
||||
|
// 绘制路径 |
||||
|
lines.forEach(item => { |
||||
|
that.drawPath(item, width); |
||||
|
}); |
||||
|
}, |
||||
|
fail(error) { |
||||
|
console.error('图片加载失败:', error); |
||||
|
}, |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 绘制路径 |
||||
|
* 图片坐标是155,194 |
||||
|
*/ |
||||
|
drawPath(str, width) { |
||||
|
const data = this.generatePathData(str); |
||||
|
const ctx = this.context; |
||||
|
|
||||
|
this.timers.push( |
||||
|
setTimeout(() => { |
||||
|
if (!this.trainResult.lines || this.trainResult.lines.length === 0) { |
||||
|
return; |
||||
|
} |
||||
|
ctx.beginPath(); |
||||
|
ctx.setLineWidth(10); |
||||
|
ctx.setStrokeStyle('#1890ff'); |
||||
|
const x = (+data[0][0] * width) / 155; |
||||
|
const y = (+data[0][1] * 450) / 194; |
||||
|
ctx.moveTo(x, y); |
||||
|
if (data.length === 1) { |
||||
|
// 如果只有1个点 就画个小圆圈 |
||||
|
ctx.arc(x, y, 2, 0, Math.PI * 2, false); |
||||
|
ctx.fill(); |
||||
|
} |
||||
|
}, +data[0][2]), |
||||
|
); |
||||
|
|
||||
|
for (let i = 1, len = data.length; i < len; i++) { |
||||
|
if (!this.trainResult.lines || this.trainResult.lines.length === 0) return; |
||||
|
this.timers.push( |
||||
|
setTimeout(() => { |
||||
|
if (!this.trainResult.lines || this.trainResult.lines.length === 0) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (i > 1) { |
||||
|
this.clearAndDraw(data, i, width); |
||||
|
} else { |
||||
|
const x = (+data[i][0] * width) / 155; |
||||
|
const y = (+data[i][1] * 450) / 194; |
||||
|
ctx.lineTo(x, y); |
||||
|
ctx.stroke(); |
||||
|
ctx.draw(true); |
||||
|
} |
||||
|
|
||||
|
ctx.closePath(); |
||||
|
// }, +data[i][2]), |
||||
|
}, this.getIntervalTime(+data[i][2], +str.operateTime, +data[0][2])), |
||||
|
); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
clearAndDraw(data, i, width) { |
||||
|
if (!data || data.length < 1 || i >= data.length) return; |
||||
|
const ctx = this.context; |
||||
|
const x = (+data[0][0] * width) / 155; |
||||
|
const y = (+data[0][1] * 450) / 194; |
||||
|
ctx.moveTo(x, y); |
||||
|
for (let j = 1; j < i + 1; j++) { |
||||
|
const x = (+data[j][0] * width) / 155; |
||||
|
const y = (+data[j][1] * 450) / 194; |
||||
|
ctx.lineTo(x, y); |
||||
|
if (j === i) { |
||||
|
ctx.stroke(); |
||||
|
ctx.draw(true); |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 计算间隔时间 |
||||
|
* 第一条线的操作时间 |
||||
|
* 每条线的第一个点的时间 |
||||
|
* 时间 = (当前线条的操作时间 - 第一条线的操作时间)+ (当前点的时间-同一条线的第一个点的时间) |
||||
|
*/ |
||||
|
getIntervalTime(pointTime, operateTime, firstPointTime) { |
||||
|
const { lines } = this.trainResult; |
||||
|
let time = 0; |
||||
|
time = operateTime - lines[0].operateTime + (pointTime - firstPointTime); |
||||
|
return time; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生成绘制每段路径的数据 |
||||
|
* @param {string} str 每条路径的数据"x,y,time;x,y,time" |
||||
|
* @return {array} result [[x,y,time]] |
||||
|
*/ |
||||
|
generatePathData(str) { |
||||
|
if (!str || !str.line) return; |
||||
|
let result = []; |
||||
|
str.line.split(';').forEach(point => { |
||||
|
result.push(point.split(',')); |
||||
|
}); |
||||
|
return result; |
||||
|
}, |
||||
|
}, |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style scoped lang="scss"></style> |
||||
@ -0,0 +1,170 @@ |
|||||
|
<template> |
||||
|
<view> |
||||
|
<view class="flex flex-nowrap mt-12 rounded-3xl relative" v-if="finishResult"> |
||||
|
<view |
||||
|
class="bubble" |
||||
|
:style="{ |
||||
|
left: level.left || 0, |
||||
|
color: level.color || '#EDEDED', |
||||
|
backgroundImage: `url(${level.bgPic || 'https://www.tall.wiki/staticrec/yanyuan/bubble-gray.png'})`, |
||||
|
backgroundSize: '100% 100%', |
||||
|
backgroundRepeat: 'no-repeat', |
||||
|
}" |
||||
|
> |
||||
|
{{ finishResult === 1 ? '轻松完成' : finishResult === 2 ? '略有困难' : '非常困难' }} |
||||
|
</view> |
||||
|
<view v-for="i in levels" :key="i.value" class="flex-1" :class="i.value === 3 ? '' : 'box'"> |
||||
|
<view class="w-full h-2" :class="i.checked ? level.boxBg : 'bg-gray-100'"></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
props: { finishResult: { type: Number, default: null } }, |
||||
|
|
||||
|
data() { |
||||
|
return { |
||||
|
levels: [ |
||||
|
{ |
||||
|
value: 0.5, |
||||
|
checked: false, |
||||
|
}, |
||||
|
{ |
||||
|
value: 1, |
||||
|
checked: false, |
||||
|
}, |
||||
|
{ |
||||
|
value: 1.5, |
||||
|
checked: false, |
||||
|
}, |
||||
|
{ |
||||
|
value: 2, |
||||
|
checked: false, |
||||
|
}, |
||||
|
{ |
||||
|
value: 2.5, |
||||
|
checked: false, |
||||
|
}, |
||||
|
{ |
||||
|
value: 3, |
||||
|
checked: false, |
||||
|
}, |
||||
|
], |
||||
|
width: 0, |
||||
|
level: {}, |
||||
|
}; |
||||
|
}, |
||||
|
|
||||
|
watch: { |
||||
|
finishResult(val) { |
||||
|
if (val) { |
||||
|
this.setLevel(); |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
|
||||
|
mounted() { |
||||
|
this.$nextTick(() => { |
||||
|
const query = uni.createSelectorQuery().in(this); |
||||
|
query |
||||
|
.selectAll('.box') |
||||
|
.boundingClientRect(data => { |
||||
|
if (data && data.length) { |
||||
|
this.width = data[0].width; |
||||
|
this.setLevel(); |
||||
|
} |
||||
|
}) |
||||
|
.exec(); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
methods: { |
||||
|
setLevel() { |
||||
|
if (!this.finishResult) return; |
||||
|
|
||||
|
if (this.finishResult <= 0.5) { |
||||
|
this.setParam(1, 0); |
||||
|
} else if (this.finishResult <= 1) { |
||||
|
this.setParam(1, 1); |
||||
|
} else if (this.finishResult <= 1.5) { |
||||
|
this.setParam(2, 2); |
||||
|
} else if (this.finishResult <= 2) { |
||||
|
this.setParam(2, 3); |
||||
|
} else if (this.finishResult <= 2.5) { |
||||
|
this.setParam(3, 4); |
||||
|
} else { |
||||
|
this.setParam(3, 5); |
||||
|
} |
||||
|
this.setBox(); |
||||
|
}, |
||||
|
|
||||
|
setParam(type, value) { |
||||
|
const { level, width } = this; |
||||
|
if (type == 1) { |
||||
|
level.color = '#1890FF'; |
||||
|
level.value = '轻松完成'; |
||||
|
level.bgPic = 'https://www.tall.wiki/staticrec/yanyuan/bubble-blue.png'; |
||||
|
level.boxBg = 'bg-blue-500'; |
||||
|
} |
||||
|
if (type == 2) { |
||||
|
level.color = '#F4C130'; |
||||
|
level.value = '略有困难'; |
||||
|
level.bgPic = 'https://www.tall.wiki/staticrec/yanyuan/bubble-yellow.png'; |
||||
|
level.boxBg = 'bg-yellow-500'; |
||||
|
} |
||||
|
if (type == 3) { |
||||
|
level.color = '#EB5A0D'; |
||||
|
level.value = '非常困难'; |
||||
|
level.bgPic = 'https://www.tall.wiki/staticrec/yanyuan/bubble-red.png'; |
||||
|
level.boxBg = 'bg-red-500'; |
||||
|
} |
||||
|
level.left = value * width + 'px'; |
||||
|
}, |
||||
|
|
||||
|
setBox() { |
||||
|
let index = 0; |
||||
|
this.levels.forEach((line, i) => { |
||||
|
if (this.finishResult > i / 2) { |
||||
|
index = i + 1; |
||||
|
} |
||||
|
if (this.finishResult === i / 2) { |
||||
|
index = i; |
||||
|
} |
||||
|
if (i < index) { |
||||
|
line.checked = true; |
||||
|
} else { |
||||
|
line.checked = false; |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
}, |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.box { |
||||
|
margin-right: 2px; |
||||
|
} |
||||
|
|
||||
|
.bubble { |
||||
|
position: absolute; |
||||
|
top: -40px; |
||||
|
width: 70px; |
||||
|
height: 30px; |
||||
|
line-height: 26px; |
||||
|
text-align: center; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
|
||||
|
.num { |
||||
|
width: 30rpx; |
||||
|
height: 30rpx; |
||||
|
text-align: center; |
||||
|
line-height: 30rpx; |
||||
|
border-radius: 50%; |
||||
|
border: 1px solid $uni-bg-color-blue; |
||||
|
color: $uni-bg-color-blue; |
||||
|
} |
||||
|
</style> |
||||
Loading…
Reference in new issue