refactor: 一些优化
continuous-integration/drone/push Build is passing
Details
|
|
@ -170,30 +170,13 @@ function toggleCollapse() {
|
||||||
|
|
||||||
// 选择分数
|
// 选择分数
|
||||||
function selectScore(score: number) {
|
function selectScore(score: number) {
|
||||||
if (scoreMode.value === 'onekey') {
|
currentScore.value = score
|
||||||
// 一键打分模式:直接设置分数并提交
|
emit('score-selected', score)
|
||||||
currentScore.value = score
|
|
||||||
emit('score-selected', score)
|
|
||||||
submitScore(score)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// 快捷打分模式:激活点击模式
|
|
||||||
enableQuickScoreClickMode(score)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 增加分数
|
// 增加分数
|
||||||
function addToScore(addValue: number) {
|
function addToScore(addValue: number) {
|
||||||
if (scoreMode.value === 'onekey') {
|
enableQuickScoreClickMode(Math.abs(addValue))
|
||||||
const newScore = Math.min(Math.max(currentScore.value + addValue, 0), props.fullScore)
|
|
||||||
currentScore.value = newScore
|
|
||||||
emit('score-selected', newScore)
|
|
||||||
submitScore(newScore)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// 快捷打分模式:激活点击模式
|
|
||||||
enableQuickScoreClickMode(Math.abs(addValue))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -221,53 +204,21 @@ function enableQuickScoreClickMode(value: number) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交分数
|
|
||||||
async function submitScore(score: number) {
|
|
||||||
try {
|
|
||||||
await markingData.submitRecord()
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('提交分数失败:', error)
|
|
||||||
uni.showToast({
|
|
||||||
title: '提交失败',
|
|
||||||
icon: 'none',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交当前分数
|
// 提交当前分数
|
||||||
async function submitCurrentScore() {
|
async function submitCurrentScore() {
|
||||||
try {
|
// 如果当前分数是-1(未打分),根据模式设置默认分数
|
||||||
// 如果当前分数是-1(未打分),根据模式设置默认分数
|
if (currentScore.value === -1) {
|
||||||
if (currentScore.value === -1) {
|
if (currentMode.value === 'subtract') {
|
||||||
if (currentMode.value === 'subtract') {
|
// 减分模式:不打分提交就是满分
|
||||||
// 减分模式:不打分提交就是满分
|
currentScore.value = props.fullScore
|
||||||
currentScore.value = props.fullScore
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// 加分模式:不打分提交就是0分
|
|
||||||
currentScore.value = 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// 加分模式:不打分提交就是0分
|
||||||
|
currentScore.value = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await markingData.submitRecord()
|
emit('score-selected', currentScore.value)
|
||||||
uni.showToast({
|
|
||||||
title: '提交成功',
|
|
||||||
icon: 'success',
|
|
||||||
})
|
|
||||||
// 关闭快捷打分点击模式
|
|
||||||
if (settings.value.quickScoreClickMode) {
|
|
||||||
settings.value.quickScoreClickMode = false
|
|
||||||
settings.value.quickScoreClickValue = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('提交分数失败:', error)
|
|
||||||
uni.showToast({
|
|
||||||
title: '提交失败',
|
|
||||||
icon: 'none',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
import type { DomMarkingData } from '../../composables/renderer/useMarkingDom'
|
import type { DomMarkingData } from '../../composables/renderer/useMarkingDom'
|
||||||
import type { ExamStudentMarkingQuestionResponse } from '@/api'
|
import type { ExamStudentMarkingQuestionResponse } from '@/api'
|
||||||
import { parseOSSImageSize } from '@/utils/image'
|
import { parseOSSImageSize } from '@/utils/image'
|
||||||
|
import { MarkingTool } from '../../composables/renderer/useMarkingDom'
|
||||||
import { markingSettings } from '../../composables/useMarkingSettings'
|
import { markingSettings } from '../../composables/useMarkingSettings'
|
||||||
import { useSimpleMarkingTool } from '../../composables/useSimpleMarkingTool'
|
import { useSimpleMarkingTool } from '../../composables/useSimpleMarkingTool'
|
||||||
import { MarkingTool } from '../../composables/renderer/useMarkingDom'
|
|
||||||
import QuestionRenderer from './QuestionRenderer.vue'
|
import QuestionRenderer from './QuestionRenderer.vue'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -46,10 +46,16 @@ const firstImageSize = ref({ width: 0, height: 0 })
|
||||||
// 滚动容器ID(用于 uni.createSelectorQuery)
|
// 滚动容器ID(用于 uni.createSelectorQuery)
|
||||||
const scrollContainerId = `scroll-container-${Math.random().toString(36).slice(2)}`
|
const scrollContainerId = `scroll-container-${Math.random().toString(36).slice(2)}`
|
||||||
|
|
||||||
|
const onTouchScale = ref(false)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 手势事件透传
|
* 手势事件透传
|
||||||
*/
|
*/
|
||||||
function handleTouchStart(e: TouchEvent) {
|
function handleTouchStart(e: TouchEvent) {
|
||||||
|
// 如果双指 则禁用 Y 轴滚动
|
||||||
|
if (e.touches.length >= 2) {
|
||||||
|
onTouchScale.value = true
|
||||||
|
}
|
||||||
emit('touch-start', e)
|
emit('touch-start', e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,6 +64,7 @@ function handleTouchMove(e: TouchEvent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTouchEnd(e: TouchEvent) {
|
function handleTouchEnd(e: TouchEvent) {
|
||||||
|
onTouchScale.value = false
|
||||||
emit('touch-end', e)
|
emit('touch-end', e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -194,8 +201,8 @@ defineExpose({
|
||||||
<scroll-view
|
<scroll-view
|
||||||
:id="scrollContainerId"
|
:id="scrollContainerId"
|
||||||
class="h-full w-full"
|
class="h-full w-full"
|
||||||
:scroll-y="scrollEnabled"
|
:scroll-y="scrollEnabled && !onTouchScale"
|
||||||
:scroll-x="scrollEnabled"
|
:scroll-x="scrollEnabled && !onTouchScale"
|
||||||
@scroll="handleScroll"
|
@scroll="handleScroll"
|
||||||
@touchstart="handleTouchStart"
|
@touchstart="handleTouchStart"
|
||||||
@touchmove="handleTouchMove"
|
@touchmove="handleTouchMove"
|
||||||
|
|
@ -209,7 +216,7 @@ defineExpose({
|
||||||
'items-center': markingSettings.imagePosition === 'center',
|
'items-center': markingSettings.imagePosition === 'center',
|
||||||
'items-end': markingSettings.imagePosition === 'right',
|
'items-end': markingSettings.imagePosition === 'right',
|
||||||
}"
|
}"
|
||||||
:style="{ gap: '24px', padding: '12px' }"
|
:style="{ gap: '24px', padding: '12px 12px 48px' }"
|
||||||
>
|
>
|
||||||
<QuestionRenderer
|
<QuestionRenderer
|
||||||
v-for="(question, questionIndex) in questionDataList"
|
v-for="(question, questionIndex) in questionDataList"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { MarkingSubmitData } from '../../composables/useMarkingData'
|
import type { MarkingSubmitData } from '../../composables/useMarkingData'
|
||||||
import { useSessionStorage } from '@vueuse/core'
|
|
||||||
import { DictCode, useDict } from '@/composables/useDict'
|
import { DictCode, useDict } from '@/composables/useDict'
|
||||||
|
import { svgToDataURL } from '@/utils/dom'
|
||||||
import { MarkingTool } from '../../composables/renderer/useMarkingDom'
|
import { MarkingTool } from '../../composables/renderer/useMarkingDom'
|
||||||
import { markingSettings as settings } from '../../composables/useMarkingSettings'
|
import { markingSettings as settings } from '../../composables/useMarkingSettings'
|
||||||
|
|
||||||
|
|
@ -41,36 +41,146 @@ const emit = defineEmits<{
|
||||||
}>()
|
}>()
|
||||||
const currentTool = defineModel<MarkingTool>('currentTool', { required: true })
|
const currentTool = defineModel<MarkingTool>('currentTool', { required: true })
|
||||||
|
|
||||||
// 工具栏引用
|
|
||||||
const toolbarRef = ref<HTMLElement>()
|
|
||||||
|
|
||||||
// 收起状态
|
// 收起状态
|
||||||
const isCollapsed = ref(uni.getStorageSync('marking-toolbar-collapsed') || false)
|
const isCollapsed = ref(uni.getStorageSync('marking-toolbar-collapsed') || false)
|
||||||
|
// 用于控制动画,只有用户主动展开时才显示动画
|
||||||
|
const shouldAnimate = ref(false)
|
||||||
|
|
||||||
watch(isCollapsed, (newVal) => {
|
watch(isCollapsed, (newVal) => {
|
||||||
uni.setStorageSync('marking-toolbar-collapsed', newVal)
|
uni.setStorageSync('marking-toolbar-collapsed', newVal)
|
||||||
|
// 只有从收起到展开时才需要动画
|
||||||
|
if (!newVal) {
|
||||||
|
shouldAnimate.value = true
|
||||||
|
}
|
||||||
|
// 收起/展开后重新计算位置,或者保持位置不变(保持 top/left 不变自然就是 anchored to top-left)
|
||||||
})
|
})
|
||||||
|
|
||||||
const { getDictOptionsComputed } = useDict()
|
const { getDictOptionsComputed } = useDict()
|
||||||
const { options: problemTypeOptions } = getDictOptionsComputed(DictCode.SCAN_ANOMALY_STATUS)
|
const { options: problemTypeOptions } = getDictOptionsComputed(DictCode.SCAN_ANOMALY_STATUS)
|
||||||
|
|
||||||
/**
|
// --- Position & Dragging Logic ---
|
||||||
* 将 SVG 转换为 data URL(用于小程序)
|
const position = ref<{ x: number, y: number } | null>(null)
|
||||||
*/
|
const storageKey = computed(() => `marking-toolbar-pos-${props.isLandscape ? 'landscape' : 'portrait'}`)
|
||||||
function svgToDataURL(svg: string): string {
|
|
||||||
svg = svg.replace(/data-(.*?=(['"]).*?\2)/g, '$1')
|
// 初始化或重置位置
|
||||||
svg = svg.replace(/xlink-href=/g, 'xlink:href=')
|
function loadPosition() {
|
||||||
svg = svg.replace(/view-box=/g, 'viewBox=')
|
const stored = uni.getStorageSync(storageKey.value)
|
||||||
svg = svg.replace(/<(title|desc|defs)>[\s\S]*?<\/\1>/g, '')
|
if (stored) {
|
||||||
if (!/xmlns=/.test(svg))
|
position.value = stored
|
||||||
svg = svg.replace(/<svg/, '<svg xmlns=\'http://www.w3.org/2000/svg\'')
|
}
|
||||||
svg = svg.replace(/\d+\.\d+/g, match => Number.parseFloat(Number.parseFloat(match).toFixed(2)) as any)
|
else {
|
||||||
svg = svg.replace(/<!--[\s\S]*?-->/g, '')
|
// 如果没有存储的位置,设置默认位置为左下角
|
||||||
svg = svg.replace(/\s+/g, ' ')
|
const { windowHeight } = uni.getSystemInfoSync()
|
||||||
svg = svg.replace(/[{}|\\^~[\]`"<>#%]/g, (match) => {
|
const offset = props.isLandscape ? 12 : 48 // 与 CSS 中的 bottom/left 保持一致
|
||||||
return `%${match[0].charCodeAt(0).toString(16).toUpperCase()}`
|
position.value = {
|
||||||
})
|
x: offset,
|
||||||
svg = svg.replace(/'/g, '\\\'')
|
y: windowHeight - offset - (props.isLandscape ? 48 : 80), // 减去按钮高度
|
||||||
return `data:image/svg+xml,${svg.trim()}`
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.isLandscape, () => {
|
||||||
|
loadPosition()
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
// 拖拽状态
|
||||||
|
let isDragging = false
|
||||||
|
let hasMoved = false // 是否有实际移动
|
||||||
|
let startTouch = { x: 0, y: 0 }
|
||||||
|
let startPos = { x: 0, y: 0 }
|
||||||
|
let touchStartTime = 0 // 触摸开始时间
|
||||||
|
|
||||||
|
// 判断是否为点击的阈值
|
||||||
|
const CLICK_DISTANCE_THRESHOLD = 20 // 移动距离小于20px认为是点击
|
||||||
|
const CLICK_TIME_THRESHOLD = 300 // 时间小于300ms认为是点击
|
||||||
|
|
||||||
|
function onTouchStart(e: TouchEvent) {
|
||||||
|
if (e.touches.length !== 1)
|
||||||
|
return
|
||||||
|
|
||||||
|
// 阻止冒泡,防止触发画布点击等
|
||||||
|
// e.stopPropagation() // 在模板中使用 .stop
|
||||||
|
|
||||||
|
const touch = e.touches[0]
|
||||||
|
startTouch = { x: touch.clientX, y: touch.clientY }
|
||||||
|
touchStartTime = Date.now()
|
||||||
|
isDragging = true
|
||||||
|
hasMoved = false
|
||||||
|
|
||||||
|
if (position.value) {
|
||||||
|
startPos = { ...position.value }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 如果当前是默认位置(CSS定位),需要先获取当前实际位置
|
||||||
|
// 使用 uni.createSelectorQuery 兼容小程序
|
||||||
|
const query = uni.createSelectorQuery().in(getCurrentInstance())
|
||||||
|
query.select('.trace-toolbar').boundingClientRect((data) => {
|
||||||
|
const rect = data as UniApp.NodeInfo
|
||||||
|
if (rect) {
|
||||||
|
position.value = { x: rect.left || 0, y: rect.top || 0 }
|
||||||
|
startPos = { x: rect.left || 0, y: rect.top || 0 }
|
||||||
|
}
|
||||||
|
}).exec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTouchMove(e: TouchEvent) {
|
||||||
|
if (!isDragging || e.touches.length !== 1)
|
||||||
|
return
|
||||||
|
|
||||||
|
const touch = e.touches[0]
|
||||||
|
const dx = touch.clientX - startTouch.x
|
||||||
|
const dy = touch.clientY - startTouch.y
|
||||||
|
const distance = Math.sqrt(dx * dx + dy * dy)
|
||||||
|
|
||||||
|
// 如果移动距离超过阈值,标记为已移动
|
||||||
|
if (distance > CLICK_DISTANCE_THRESHOLD) {
|
||||||
|
hasMoved = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果起始位置还没获取到(异步Query),则跳过
|
||||||
|
if (!position.value && dx === 0 && dy === 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
// 简单的防抖或阈值可以加在这里,但为了流畅性直接更新
|
||||||
|
// 如果 position 刚被 query 设置,可能在这里会突变?
|
||||||
|
// 应该基于 startPos 更新
|
||||||
|
|
||||||
|
// 如果 startPos 还是 0,0 且 position 也是 null (Query 还没回来),忽略
|
||||||
|
// 这里简化处理,假设 Query 很快或下一次 move 生效
|
||||||
|
|
||||||
|
if (position.value) {
|
||||||
|
const newX = startPos.x + dx
|
||||||
|
const newY = startPos.y + dy
|
||||||
|
|
||||||
|
// 边界限制(可选,这里暂不严格限制,允许拖到边缘)
|
||||||
|
const { windowWidth, windowHeight } = uni.getSystemInfoSync()
|
||||||
|
// 简单限制在屏幕内
|
||||||
|
// position.value = {
|
||||||
|
// x: Math.max(0, Math.min(newX, windowWidth - 40)),
|
||||||
|
// y: Math.max(0, Math.min(newY, windowHeight - 40))
|
||||||
|
// }
|
||||||
|
position.value = { x: newX, y: newY }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTouchEnd() {
|
||||||
|
const touchDuration = Date.now() - touchStartTime
|
||||||
|
|
||||||
|
// 判断是否为点击:移动距离短 且 时间短
|
||||||
|
const isClick = !hasMoved && touchDuration < CLICK_TIME_THRESHOLD
|
||||||
|
|
||||||
|
if (isDragging && position.value && !isClick) {
|
||||||
|
// 只有在真正拖拽时才保存位置
|
||||||
|
uni.setStorageSync(storageKey.value, position.value)
|
||||||
|
}
|
||||||
|
else if (isClick) {
|
||||||
|
// 如果是点击,恢复原始位置
|
||||||
|
position.value = startPos.x !== 0 || startPos.y !== 0 ? { ...startPos } : position.value
|
||||||
|
}
|
||||||
|
|
||||||
|
isDragging = false
|
||||||
|
hasMoved = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -179,195 +289,180 @@ defineExpose({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<wd-root-portal>
|
||||||
ref="toolbarRef"
|
|
||||||
class="fixed z-5 flex flex-col items-start transition-all duration-300 ease-out"
|
|
||||||
:class="[
|
|
||||||
isLandscape
|
|
||||||
? 'bottom-12px left-1/2 -translate-x-1/2 items-center'
|
|
||||||
: 'bottom-48rpx left-12rpx items-start max-w-[75vw]',
|
|
||||||
]"
|
|
||||||
@touchend.stop
|
|
||||||
@touchmove.stop
|
|
||||||
@touchstart.stop
|
|
||||||
>
|
|
||||||
<!-- 收起状态 (FAB) -->
|
|
||||||
<div
|
<div
|
||||||
v-if="isCollapsed"
|
class="trace-toolbar fixed z-15 flex flex-col items-start transition-opacity duration-300 ease-out"
|
||||||
class="flex cursor-pointer items-center justify-center rounded-full bg-white shadow-lg transition-all active:scale-95"
|
|
||||||
:class="isLandscape ? 'size-48px' : 'size-80rpx'"
|
|
||||||
@click="toggleCollapse"
|
|
||||||
>
|
|
||||||
<div class="i-fluent:edit-24-regular text-blue-600" :class="isLandscape ? 'size-24px' : 'size-40rpx'" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 展开状态 -->
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="flex items-center rounded-full bg-white shadow-xl ring-1 ring-slate-900 ring-opacity-5 transition-all"
|
|
||||||
:class="[
|
:class="[
|
||||||
isLandscape ? 'px-8px py-4px gap-4px' : 'pl-12rpx pr-6rpx py-10rpx gap-4rpx',
|
!position && (isLandscape
|
||||||
{ 'animate-fade-in-up': !isCollapsed },
|
? 'bottom-12px left-1/2 -translate-x-1/2 items-center'
|
||||||
|
: 'bottom-48rpx left-12rpx items-start'), // Removed max-w-[75vw]
|
||||||
]"
|
]"
|
||||||
|
:style="position ? { left: `${position.x}px`, top: `${position.y}px` } : {}"
|
||||||
>
|
>
|
||||||
<!-- 收起按钮 -->
|
<!-- 收起状态 (FAB) - 可以拖拽 -->
|
||||||
<div
|
<div
|
||||||
class="flex flex-shrink-0 cursor-pointer items-center justify-center rounded-full text-slate-400 active:scale-95 hover:bg-slate-100"
|
v-if="isCollapsed"
|
||||||
:class="isLandscape ? 'size-32px' : 'size-60rpx'"
|
class="flex cursor-pointer items-center justify-center rounded-full bg-white shadow-lg transition-all active:scale-95"
|
||||||
@click="toggleCollapse"
|
:class="isLandscape ? 'size-48px' : 'size-80rpx'"
|
||||||
|
@click.stop="toggleCollapse"
|
||||||
|
@touchend.stop="onTouchEnd"
|
||||||
|
@touchmove.stop.prevent="onTouchMove"
|
||||||
|
@touchstart.stop="onTouchStart"
|
||||||
>
|
>
|
||||||
<div
|
<div class="i-fluent:edit-24-regular text-blue-600" :class="isLandscape ? 'size-24px' : 'size-40rpx'" />
|
||||||
:class="[
|
|
||||||
isLandscape ? 'i-carbon:chevron-down size-20px' : 'i-carbon:chevron-left size-36rpx',
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 分隔线 -->
|
<!-- 展开状态 - 不可拖拽 -->
|
||||||
<div class="h-4/5 w-1px flex-shrink-0 bg-slate-200" />
|
<div
|
||||||
|
v-else
|
||||||
<!-- 工具内容区域 (竖屏时可滚动) -->
|
class="flex items-center rounded-full bg-white shadow-xl ring-1 ring-slate-900 ring-opacity-5 transition-all"
|
||||||
<scroll-view
|
:class="[
|
||||||
:scroll-x="true"
|
isLandscape ? 'px-8px py-4px gap-4px' : 'pl-8rpx pr-4rpx py-8rpx gap-2rpx', // 竖屏减小内边距和间距
|
||||||
:show-scrollbar="false"
|
]"
|
||||||
class="flex whitespace-nowrap"
|
|
||||||
:class="isLandscape ? 'w-auto' : 'max-w-[65vw]'"
|
|
||||||
>
|
>
|
||||||
<div class="flex items-center" :class="isLandscape ? 'gap-6px' : 'gap-16rpx'">
|
<!-- 收起按钮 -->
|
||||||
<!-- 标记组 -->
|
<div
|
||||||
<div class="flex items-center rounded-full bg-slate-50" :class="isLandscape ? 'p-4px gap-4px' : 'p-6rpx gap-8rpx'">
|
class="flex flex-shrink-0 cursor-pointer items-center justify-center rounded-full text-slate-400 active:scale-95 hover:bg-slate-100"
|
||||||
<wd-tooltip :content="isLandscape ? '正确' : ''" placement="top">
|
:class="isLandscape ? 'size-32px' : 'size-48rpx'"
|
||||||
|
@click.stop="toggleCollapse"
|
||||||
|
>
|
||||||
|
<!-- 竖屏尺寸减小 size-60rpx -> size-48rpx -->
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
isLandscape ? 'i-carbon:chevron-down size-20px' : 'i-carbon:chevron-left size-32rpx', // 竖屏 icon 减小
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分隔线 -->
|
||||||
|
<div class="h-4/5 w-1px flex-shrink-0 bg-slate-200" />
|
||||||
|
|
||||||
|
<!-- 工具内容区域 (竖屏时不滚动,全显示) -->
|
||||||
|
<!-- 移除 scroll-view,改为 div flex -->
|
||||||
|
<div
|
||||||
|
class="flex whitespace-nowrap"
|
||||||
|
:class="isLandscape ? 'w-auto' : ''"
|
||||||
|
>
|
||||||
|
<div class="flex items-center" :class="isLandscape ? 'gap-6px' : 'gap-8rpx'">
|
||||||
|
<!-- 标记组 -->
|
||||||
|
<div class="flex items-center rounded-full bg-slate-50" :class="isLandscape ? 'p-4px gap-4px' : 'p-4rpx gap-4rpx'">
|
||||||
|
<!-- 竖屏 padding/gap 减小 -->
|
||||||
|
<wd-tooltip :content="isLandscape ? '正确' : ''" placement="top">
|
||||||
|
<div
|
||||||
|
class="flex cursor-pointer items-center justify-center rounded-full transition-all active:scale-95"
|
||||||
|
:class="[
|
||||||
|
currentTool === MarkingTool.CORRECT ? 'bg-green-500 text-white shadow-md' : 'text-slate-500 hover:bg-white hover:text-green-600',
|
||||||
|
isLandscape ? 'size-32px' : 'size-48rpx', // 竖屏尺寸减小
|
||||||
|
]"
|
||||||
|
@click.stop="handleMarkTool('correct')"
|
||||||
|
>
|
||||||
|
<div class="i-fluent:checkmark-24-regular" :class="isLandscape ? 'size-20px' : 'size-32rpx'" />
|
||||||
|
</div>
|
||||||
|
</wd-tooltip>
|
||||||
|
|
||||||
|
<wd-tooltip :content="isLandscape ? '错误' : ''" placement="top">
|
||||||
|
<div
|
||||||
|
class="flex cursor-pointer items-center justify-center rounded-full transition-all active:scale-95"
|
||||||
|
:class="[
|
||||||
|
currentTool === MarkingTool.WRONG ? 'bg-red-500 text-white shadow-md' : 'text-slate-500 hover:bg-white hover:text-red-600',
|
||||||
|
isLandscape ? 'size-32px' : 'size-48rpx',
|
||||||
|
]"
|
||||||
|
@click.stop="handleMarkTool('wrong')"
|
||||||
|
>
|
||||||
|
<div class="i-fluent:dismiss-24-regular" :class="isLandscape ? 'size-20px' : 'size-32rpx'" />
|
||||||
|
</div>
|
||||||
|
</wd-tooltip>
|
||||||
|
|
||||||
|
<wd-tooltip :content="isLandscape ? '半对' : ''" placement="top">
|
||||||
|
<div
|
||||||
|
class="flex cursor-pointer items-center justify-center rounded-full transition-all active:scale-95"
|
||||||
|
:class="[
|
||||||
|
currentTool === MarkingTool.HALF ? 'bg-yellow-500 text-white shadow-md' : 'text-slate-500 hover:bg-white hover:text-yellow-600',
|
||||||
|
isLandscape ? 'size-32px' : 'size-48rpx',
|
||||||
|
]"
|
||||||
|
@click.stop="handleMarkTool('half')"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
:class="isLandscape ? 'size-20px' : 'size-32rpx'"
|
||||||
|
:style="{
|
||||||
|
backgroundImage: `url('${halfMarkSvg}')`,
|
||||||
|
backgroundSize: 'contain',
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</wd-tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分隔线 -->
|
||||||
|
<div class="h-20px w-1px flex-shrink-0 bg-slate-200" />
|
||||||
|
|
||||||
|
<!-- 绘图组 -->
|
||||||
|
<div class="flex items-center rounded-full bg-slate-50" :class="isLandscape ? 'p-4px gap-4px' : 'p-4rpx gap-4rpx'">
|
||||||
|
<wd-tooltip :content="isLandscape ? '画笔' : ''" placement="top">
|
||||||
|
<div
|
||||||
|
class="flex cursor-pointer items-center justify-center rounded-full transition-all active:scale-95"
|
||||||
|
:class="[
|
||||||
|
currentTool === MarkingTool.PEN ? 'bg-blue-600 text-white shadow-md' : 'text-slate-500 hover:bg-white hover:text-blue-600',
|
||||||
|
isLandscape ? 'size-32px' : 'size-48rpx',
|
||||||
|
]"
|
||||||
|
@click.stop="switchTool(MarkingTool.PEN)"
|
||||||
|
>
|
||||||
|
<div class="i-fluent:pen-24-regular" :class="isLandscape ? 'size-20px' : 'size-32rpx'" />
|
||||||
|
</div>
|
||||||
|
</wd-tooltip>
|
||||||
|
|
||||||
|
<wd-tooltip :content="isLandscape ? '文字' : ''" placement="top">
|
||||||
|
<div
|
||||||
|
class="flex cursor-pointer items-center justify-center rounded-full transition-all active:scale-95"
|
||||||
|
:class="[
|
||||||
|
currentTool === MarkingTool.TEXT ? 'bg-blue-600 text-white shadow-md' : 'text-slate-500 hover:bg-white hover:text-blue-600',
|
||||||
|
isLandscape ? 'size-32px' : 'size-48rpx',
|
||||||
|
]"
|
||||||
|
@click.stop="switchTool(MarkingTool.TEXT)"
|
||||||
|
>
|
||||||
|
<div class="i-fluent:text-24-regular" :class="isLandscape ? 'size-20px' : 'size-32rpx'" />
|
||||||
|
</div>
|
||||||
|
</wd-tooltip>
|
||||||
|
|
||||||
|
<wd-tooltip :content="isLandscape ? '擦除' : ''" placement="top">
|
||||||
|
<div
|
||||||
|
class="flex cursor-pointer items-center justify-center rounded-full transition-all active:scale-95"
|
||||||
|
:class="[
|
||||||
|
currentTool === MarkingTool.ERASER ? 'bg-slate-600 text-white shadow-md' : 'text-slate-500 hover:bg-white hover:text-slate-700',
|
||||||
|
isLandscape ? 'size-32px' : 'size-48rpx',
|
||||||
|
]"
|
||||||
|
@click.stop="switchTool(MarkingTool.ERASER)"
|
||||||
|
>
|
||||||
|
<div class="i-fluent:eraser-24-regular" :class="isLandscape ? 'size-20px' : 'size-32rpx'" />
|
||||||
|
</div>
|
||||||
|
</wd-tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分隔线 -->
|
||||||
|
<div class="h-20px w-1px flex-shrink-0 bg-slate-200" />
|
||||||
|
|
||||||
|
<!-- 特殊标记组 -->
|
||||||
|
<div class="flex items-center" :class="isLandscape ? 'gap-6px' : 'gap-6rpx'">
|
||||||
<div
|
<div
|
||||||
class="flex cursor-pointer items-center justify-center rounded-full transition-all active:scale-95"
|
v-for="type in ['excellent', 'typical', 'problem']"
|
||||||
|
:key="type"
|
||||||
|
class="flex flex-shrink-0 cursor-pointer items-center justify-center rounded-full font-medium transition-all active:scale-95"
|
||||||
:class="[
|
:class="[
|
||||||
currentTool === MarkingTool.CORRECT ? 'bg-green-500 text-white shadow-md' : 'text-slate-500 hover:bg-white hover:text-green-600',
|
specialMarks[type as keyof typeof specialMarks]
|
||||||
isLandscape ? 'size-32px' : 'size-64rpx',
|
? (type === 'excellent' ? 'bg-yellow-100 text-yellow-700' : type === 'typical' ? 'bg-blue-100 text-blue-700' : 'bg-red-100 text-red-700')
|
||||||
|
: 'bg-slate-50 text-slate-500 hover:bg-slate-100',
|
||||||
|
isLandscape ? 'h-32px px-12px text-12px' : 'h-48rpx px-12rpx text-20rpx', // 竖屏减小
|
||||||
]"
|
]"
|
||||||
@click="handleMarkTool('correct')"
|
@click.stop="toggleSpecialMark(type as any)"
|
||||||
>
|
>
|
||||||
<div class="i-fluent:checkmark-24-regular" :class="isLandscape ? 'size-20px' : 'size-40rpx'" />
|
{{ type === 'excellent' ? '优秀' : type === 'typical' ? '典例' : '问题' }}
|
||||||
</div>
|
</div>
|
||||||
</wd-tooltip>
|
|
||||||
|
|
||||||
<wd-tooltip :content="isLandscape ? '错误' : ''" placement="top">
|
|
||||||
<div
|
|
||||||
class="flex cursor-pointer items-center justify-center rounded-full transition-all active:scale-95"
|
|
||||||
:class="[
|
|
||||||
currentTool === MarkingTool.WRONG ? 'bg-red-500 text-white shadow-md' : 'text-slate-500 hover:bg-white hover:text-red-600',
|
|
||||||
isLandscape ? 'size-32px' : 'size-64rpx',
|
|
||||||
]"
|
|
||||||
@click="handleMarkTool('wrong')"
|
|
||||||
>
|
|
||||||
<div class="i-fluent:dismiss-24-regular" :class="isLandscape ? 'size-20px' : 'size-40rpx'" />
|
|
||||||
</div>
|
|
||||||
</wd-tooltip>
|
|
||||||
|
|
||||||
<wd-tooltip :content="isLandscape ? '半对' : ''" placement="top">
|
|
||||||
<div
|
|
||||||
class="flex cursor-pointer items-center justify-center rounded-full transition-all active:scale-95"
|
|
||||||
:class="[
|
|
||||||
currentTool === MarkingTool.HALF ? 'bg-yellow-500 text-white shadow-md' : 'text-slate-500 hover:bg-white hover:text-yellow-600',
|
|
||||||
isLandscape ? 'size-32px' : 'size-64rpx',
|
|
||||||
]"
|
|
||||||
@click="handleMarkTool('half')"
|
|
||||||
>
|
|
||||||
<view
|
|
||||||
:class="isLandscape ? 'size-20px' : 'size-40rpx'"
|
|
||||||
:style="{
|
|
||||||
backgroundImage: `url('${halfMarkSvg}')`,
|
|
||||||
backgroundSize: 'contain',
|
|
||||||
backgroundRepeat: 'no-repeat',
|
|
||||||
backgroundPosition: 'center',
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</wd-tooltip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 分隔线 -->
|
|
||||||
<div class="h-20px w-1px flex-shrink-0 bg-slate-200" />
|
|
||||||
|
|
||||||
<!-- 绘图组 -->
|
|
||||||
<div class="flex items-center rounded-full bg-slate-50" :class="isLandscape ? 'p-4px gap-4px' : 'p-6rpx gap-8rpx'">
|
|
||||||
<wd-tooltip :content="isLandscape ? '画笔' : ''" placement="top">
|
|
||||||
<div
|
|
||||||
class="flex cursor-pointer items-center justify-center rounded-full transition-all active:scale-95"
|
|
||||||
:class="[
|
|
||||||
currentTool === MarkingTool.PEN ? 'bg-blue-600 text-white shadow-md' : 'text-slate-500 hover:bg-white hover:text-blue-600',
|
|
||||||
isLandscape ? 'size-32px' : 'size-64rpx',
|
|
||||||
]"
|
|
||||||
@click="switchTool(MarkingTool.PEN)"
|
|
||||||
>
|
|
||||||
<div class="i-fluent:pen-24-regular" :class="isLandscape ? 'size-20px' : 'size-40rpx'" />
|
|
||||||
</div>
|
|
||||||
</wd-tooltip>
|
|
||||||
|
|
||||||
<wd-tooltip :content="isLandscape ? '文字' : ''" placement="top">
|
|
||||||
<div
|
|
||||||
class="flex cursor-pointer items-center justify-center rounded-full transition-all active:scale-95"
|
|
||||||
:class="[
|
|
||||||
currentTool === MarkingTool.TEXT ? 'bg-blue-600 text-white shadow-md' : 'text-slate-500 hover:bg-white hover:text-blue-600',
|
|
||||||
isLandscape ? 'size-32px' : 'size-64rpx',
|
|
||||||
]"
|
|
||||||
@click="switchTool(MarkingTool.TEXT)"
|
|
||||||
>
|
|
||||||
<div class="i-fluent:text-24-regular" :class="isLandscape ? 'size-20px' : 'size-40rpx'" />
|
|
||||||
</div>
|
|
||||||
</wd-tooltip>
|
|
||||||
|
|
||||||
<wd-tooltip :content="isLandscape ? '擦除' : ''" placement="top">
|
|
||||||
<div
|
|
||||||
class="flex cursor-pointer items-center justify-center rounded-full transition-all active:scale-95"
|
|
||||||
:class="[
|
|
||||||
currentTool === MarkingTool.ERASER ? 'bg-slate-600 text-white shadow-md' : 'text-slate-500 hover:bg-white hover:text-slate-700',
|
|
||||||
isLandscape ? 'size-32px' : 'size-64rpx',
|
|
||||||
]"
|
|
||||||
@click="switchTool(MarkingTool.ERASER)"
|
|
||||||
>
|
|
||||||
<div class="i-fluent:eraser-24-regular" :class="isLandscape ? 'size-20px' : 'size-40rpx'" />
|
|
||||||
</div>
|
|
||||||
</wd-tooltip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 分隔线 -->
|
|
||||||
<div class="h-20px w-1px flex-shrink-0 bg-slate-200" />
|
|
||||||
|
|
||||||
<!-- 特殊标记组 -->
|
|
||||||
<div class="flex items-center" :class="isLandscape ? 'gap-6px' : 'gap-12rpx'">
|
|
||||||
<div
|
|
||||||
v-for="type in ['excellent', 'typical', 'problem']"
|
|
||||||
:key="type"
|
|
||||||
class="flex flex-shrink-0 cursor-pointer items-center justify-center rounded-full font-medium transition-all active:scale-95"
|
|
||||||
:class="[
|
|
||||||
specialMarks[type as keyof typeof specialMarks]
|
|
||||||
? (type === 'excellent' ? 'bg-yellow-100 text-yellow-700' : type === 'typical' ? 'bg-blue-100 text-blue-700' : 'bg-red-100 text-red-700')
|
|
||||||
: 'bg-slate-50 text-slate-500 hover:bg-slate-100',
|
|
||||||
isLandscape ? 'h-32px px-12px text-12px' : 'h-64rpx px-20rpx text-24rpx',
|
|
||||||
]"
|
|
||||||
@click="toggleSpecialMark(type as any)"
|
|
||||||
>
|
|
||||||
{{ type === 'excellent' ? '优秀' : type === 'typical' ? '典例' : '问题' }}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</scroll-view>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</wd-root-portal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.animate-fade-in-up {
|
|
||||||
animation: fadeInUp 0.2s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeInUp {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(10px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage, watchDeep } from '@vueuse/core'
|
||||||
|
|
||||||
const SETTINGS_VERSION = 2 // 当前版本号
|
const SETTINGS_VERSION = 2 // 当前版本号
|
||||||
|
|
||||||
|
|
@ -100,7 +100,7 @@ const defaultSettings: MarkingSettings = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawSettings = ref<MarkingSettings>(uni.getStorageSync('marking_settings') || defaultSettings)
|
const rawSettings = ref<MarkingSettings>(uni.getStorageSync('marking_settings') || defaultSettings)
|
||||||
watch(rawSettings, (newVal) => {
|
watchDeep(rawSettings, (newVal) => {
|
||||||
uni.setStorageSync('marking_settings', newVal)
|
uni.setStorageSync('marking_settings', newVal)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import { cachedImages, DefaultMarkingDataProvider, DefaultMarkingHistoryProvider
|
||||||
import { provideMarkingHistory } from '@/composables/marking/useMarkingHistory'
|
import { provideMarkingHistory } from '@/composables/marking/useMarkingHistory'
|
||||||
import { provideMarkingNavigation } from '@/composables/marking/useMarkingNavigation'
|
import { provideMarkingNavigation } from '@/composables/marking/useMarkingNavigation'
|
||||||
import { useSafeArea } from '@/composables/useSafeArea'
|
import { useSafeArea } from '@/composables/useSafeArea'
|
||||||
|
import { svgToDataURL } from '@/utils/dom'
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'GradingPage',
|
name: 'GradingPage',
|
||||||
|
|
@ -399,9 +400,41 @@ whenever(currentTask, (task, oldTask) => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['marking-question', task.id] })
|
queryClient.invalidateQueries({ queryKey: ['marking-question', task.id] })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 提交分数反馈状态
|
||||||
|
const showScoreFeedback = ref(false)
|
||||||
|
const feedbackScore = ref(0)
|
||||||
|
const doubleUnderlineSvg = `<svg width="100%" height="100%" viewBox="0 0 120 40" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none">
|
||||||
|
<path d="M5 12 Q 60 5 115 12" stroke="red" stroke-width="5" stroke-linecap="round" fill="none" />
|
||||||
|
<path d="M10 28 Q 60 22 110 30" stroke="red" stroke-width="5" stroke-linecap="round" fill="none" />
|
||||||
|
</svg>`
|
||||||
|
|
||||||
// 快捷打分选择
|
// 快捷打分选择
|
||||||
function handleQuickScoreSelect(score: number) {
|
async function handleQuickScoreSelect(score: number) {
|
||||||
console.log('选择分数:', score)
|
console.log('选择分数:', score)
|
||||||
|
|
||||||
|
// 设置当前分数
|
||||||
|
markingData.currentScore.value = score
|
||||||
|
|
||||||
|
// 显示反馈动画
|
||||||
|
feedbackScore.value = score
|
||||||
|
showScoreFeedback.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await markingData.submitSingleRecord()
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('提交失败:', error)
|
||||||
|
uni.showToast({
|
||||||
|
title: error.message || '提交失败',
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
// 延迟隐藏反馈
|
||||||
|
setTimeout(() => {
|
||||||
|
showScoreFeedback.value = false
|
||||||
|
}, 400)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 历史查看模式提示
|
// 历史查看模式提示
|
||||||
|
|
@ -511,7 +544,7 @@ const nextQuestionImages = computed(() => {
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="relative h-screen w-100vw flex flex-col touch-pan-x touch-pan-y overflow-hidden overscroll-none"
|
class="relative h-screen w-100vw flex flex-col touch-pan-x touch-pan-y overflow-hidden overscroll-none"
|
||||||
:style="{ paddingTop: `${(safeAreaInsets?.top || 0)}px` }"
|
:style="{ paddingTop: `${(isLandscape ? 0 : safeAreaInsets?.top || 0)}px` }"
|
||||||
@touchstart="handleGlobalTouchStart"
|
@touchstart="handleGlobalTouchStart"
|
||||||
@touchmove="handleGlobalTouchMove"
|
@touchmove="handleGlobalTouchMove"
|
||||||
@touchend="handleGlobalTouchEnd"
|
@touchend="handleGlobalTouchEnd"
|
||||||
|
|
@ -614,6 +647,18 @@ const nextQuestionImages = computed(() => {
|
||||||
v-model="showFullscreenImage"
|
v-model="showFullscreenImage"
|
||||||
:image-urls="currentImageUrls"
|
:image-urls="currentImageUrls"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 提交分数反馈动画 -->
|
||||||
|
<view
|
||||||
|
v-if="showScoreFeedback"
|
||||||
|
class="pointer-events-none fixed left-1/2 top-1/2 z-999 flex flex-col items-center justify-center text-red-500"
|
||||||
|
style="transform: translate(-50%, -50%) rotate(-10deg)"
|
||||||
|
>
|
||||||
|
<text class="text-96px font-bold leading-none" style="font-family: 'KaiTi', 'STKaiti', serif">
|
||||||
|
{{ feedbackScore }}
|
||||||
|
</text>
|
||||||
|
<view class="h-7 w-20" :style="{ backgroundImage: `url('${svgToDataURL(doubleUnderlineSvg)}')`, backgroundSize: 'contain', backgroundRepeat: 'no-repeat', backgroundPosition: 'center' }" />
|
||||||
|
</view>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 169 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 574 B After Width: | Height: | Size: 733 B |
|
Before Width: | Height: | Size: 780 B After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 985 B After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
|
@ -1,33 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg id="_图层_2" data-name="图层 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 113.39 113.39">
|
|
||||||
<defs>
|
|
||||||
<style>
|
|
||||||
.cls-1 {
|
|
||||||
fill: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cls-2 {
|
|
||||||
fill: #d14328;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cls-3 {
|
|
||||||
fill: #2c8d3a;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</defs>
|
|
||||||
<g id="_图层_1-2" data-name="图层 1">
|
|
||||||
<g>
|
|
||||||
<rect class="cls-1" width="113.39" height="113.39" />
|
|
||||||
<g>
|
|
||||||
<path class="cls-3"
|
|
||||||
d="M86.31,11.34H25.08c-8.14,0-14.74,6.6-14.74,14.74v61.23c0,8.14,6.6,14.74,14.74,14.74h61.23c.12,0,.24-.02,.37-.02-9.76-.2-17.64-8.18-17.64-17.99,0-.56,.03-1.12,.08-1.67H34.1c-1.57,0-2.83-1.27-2.83-2.83V32.43c0-.78,.63-1.42,1.42-1.42h9.17c.78,0,1.42,.63,1.42,1.42v36.52c0,.78,.63,1.42,1.42,1.42h22.02c.78,0,1.42-.63,1.42-1.42V32.43c0-.78,.63-1.42,1.42-1.42h9.17c.78,0,1.42,.63,1.42,1.42v34.99c2.13-.89,4.47-1.39,6.92-1.39,5.66,0,10.7,2.63,14.01,6.72V26.08c0-8.14-6.6-14.74-14.74-14.74Z" />
|
|
||||||
<g>
|
|
||||||
<path class="cls-2"
|
|
||||||
d="M87.04,68.03c-8.83,0-16.01,7.18-16.01,16.01s7.18,16.01,16.01,16.01,16.01-7.18,16.01-16.01-7.18-16.01-16.01-16.01Zm-.27,24.84h-7.2v-3h1.18v-10.48h4.58v2.81h1.42c.84,0,1.46-.16,1.88-.48s.62-.87,.62-1.64c0-.69-.25-1.17-.74-1.45s-1.19-.42-2.09-.42h-6.84v-3h7.2c2.38,0,4.15,.38,5.31,1.15,1.16,.77,1.74,1.93,1.74,3.48,0,1.71-.83,2.93-2.5,3.64,1.07,.4,1.87,.95,2.39,1.65s.79,1.56,.79,2.58c0,3.44-2.58,5.16-7.73,5.16Z" />
|
|
||||||
<path class="cls-2"
|
|
||||||
d="M86.49,85.17h-1.16v4.7h1.8c.81,0,1.46-.18,1.94-.55s.72-.95,.72-1.73c0-.86-.25-1.48-.74-1.85s-1.35-.56-2.56-.56Z" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB |
|
|
@ -145,3 +145,19 @@ export function getElementRectSync(element: HTMLElement | null): ElementRect | n
|
||||||
y: top,
|
y: top,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export function svgToDataURL(svg: string): string {
|
||||||
|
svg = svg.replace(/data-(.*?=(['"]).*?\2)/g, '$1')
|
||||||
|
svg = svg.replace(/xlink-href=/g, 'xlink:href=')
|
||||||
|
svg = svg.replace(/view-box=/g, 'viewBox=')
|
||||||
|
svg = svg.replace(/<(title|desc|defs)>[\s\S]*?<\/\1>/g, '')
|
||||||
|
if (!/xmlns=/.test(svg))
|
||||||
|
svg = svg.replace(/<svg/, '<svg xmlns=\'http://www.w3.org/2000/svg\'')
|
||||||
|
svg = svg.replace(/\d+\.\d+/g, match => Number.parseFloat(Number.parseFloat(match).toFixed(2)) as any)
|
||||||
|
svg = svg.replace(/<!--[\s\S]*?-->/g, '')
|
||||||
|
svg = svg.replace(/\s+/g, ' ')
|
||||||
|
svg = svg.replace(/[{}|\\^~[\]`"<>#%]/g, (match) => {
|
||||||
|
return `%${match[0].charCodeAt(0).toString(16).toUpperCase()}`
|
||||||
|
})
|
||||||
|
svg = svg.replace(/'/g, '\\\'')
|
||||||
|
return `data:image/svg+xml,${svg.trim()}`
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,28 @@
|
||||||
import { presetUni } from '@uni-helper/unocss-preset-uni'
|
import { presetUni } from '@uni-helper/unocss-preset-uni'
|
||||||
import {
|
import {
|
||||||
defineConfig,
|
defineConfig,
|
||||||
|
definePreset,
|
||||||
presetAttributify,
|
presetAttributify,
|
||||||
presetIcons,
|
presetIcons,
|
||||||
transformerDirectives,
|
transformerDirectives,
|
||||||
transformerVariantGroup,
|
transformerVariantGroup,
|
||||||
} from 'unocss'
|
} from 'unocss'
|
||||||
|
|
||||||
|
export const presetRemToRpx = definePreset(() => {
|
||||||
|
const baseFontSize = 16
|
||||||
|
const remRE = /(-?[.\d]+)rem/g
|
||||||
|
return {
|
||||||
|
name: '@unocss/preset-rem-to-px',
|
||||||
|
postprocess: (util) => {
|
||||||
|
util.entries.forEach((i) => {
|
||||||
|
const value = i[1]
|
||||||
|
if (typeof value === 'string' && remRE.test(value))
|
||||||
|
i[1] = value.replace(remRE, (_, p1) => `${p1 * baseFontSize * 2}rpx`)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
presets: [
|
presets: [
|
||||||
presetUni({
|
presetUni({
|
||||||
|
|
@ -26,6 +42,7 @@ export default defineConfig({
|
||||||
}),
|
}),
|
||||||
// 支持css class属性化
|
// 支持css class属性化
|
||||||
presetAttributify(),
|
presetAttributify(),
|
||||||
|
presetRemToRpx(),
|
||||||
],
|
],
|
||||||
transformers: [
|
transformers: [
|
||||||
// 启用指令功能:主要用于支持 @apply、@screen 和 theme() 等 CSS 指令
|
// 启用指令功能:主要用于支持 @apply、@screen 和 theme() 等 CSS 指令
|
||||||
|
|
|
||||||