feat: 图片水印+提示词精简+收起按钮

This commit is contained in:
2026-07-16 12:15:19 +08:00
parent e7a9cd23ad
commit 08a831b806
5 changed files with 74 additions and 61 deletions
+15 -3
View File
@@ -30,6 +30,10 @@
<span class="btn-icon">📜</span>
<span class="btn-label">历史</span>
</el-button>
<el-button class="toolbar-btn collapse-btn" @click="showMenu = false">
<span class="btn-icon"></span>
<span class="btn-label">收起</span>
</el-button>
</template>
<el-button class="toolbar-btn" @click="openSettings">
@@ -46,6 +50,7 @@ import { ElMessage } from 'element-plus'
import { useAppStore } from '@/stores/app'
import { useGradingStore } from '@/stores/grading'
import { useSettingsStore } from '@/stores/settings'
import { addWatermark } from '@/utils/image'
const appStore = useAppStore()
const gradingStore = useGradingStore()
@@ -69,9 +74,9 @@ let cleanupScreenshotListener: (() => void) | null = null
onMounted(() => {
resizeWindow()
if (window.electronAPI) {
cleanupScreenshotListener = window.electronAPI.screenshot.onCompleted((base64: string) => {
cleanupScreenshotListener = window.electronAPI.screenshot.onCompleted(async (base64: string) => {
if (!appStore.isGrading) {
gradingStore.setImage(base64)
gradingStore.setImage(await addWatermark(base64, '学生答案'))
startGrading()
}
})
@@ -90,7 +95,7 @@ async function selectImage() {
if (!window.electronAPI) return
const data = await window.electronAPI.dialog.openImage()
if (data) {
gradingStore.setImage(data.base64)
gradingStore.setImage(await addWatermark(data.base64, '学生答案'))
startGrading()
}
}
@@ -206,6 +211,13 @@ function openSettings() { window.electronAPI?.dialog.open('settings') }
.sub-btn {
padding: 4px 8px !important;
}
.collapse-btn {
padding: 4px 6px !important;
color: rgba(255, 255, 255, 0.5) !important;
}
.collapse-btn:hover {
color: rgba(255, 255, 255, 0.9) !important;
}
.toolbar-btn {
-webkit-app-region: no-drag;
+9 -3
View File
@@ -25,9 +25,15 @@
<div class="section-title">当前评分标准</div>
<div class="rubric-text">{{ currentQuestion.rubric }}</div>
</div>
<div class="result-section" v-if="currentQuestion.referenceAnswer">
<div class="result-section" v-if="gradingStore.currentImage">
<div class="section-title">学生答案图片</div>
<img class="student-image" :src="'data:image/png;base64,' + gradingStore.currentImage" />
</div>
<div class="result-section" v-if="currentQuestion.referenceAnswer || currentQuestion.referenceAnswerImage">
<div class="section-title">参考答案</div>
<div class="reference-text">{{ currentQuestion.referenceAnswer }}</div>
<div class="reference-text" v-if="currentQuestion.referenceAnswer">{{ currentQuestion.referenceAnswer }}</div>
<img v-if="currentQuestion.referenceAnswerImage" class="ref-image" :src="currentQuestion.referenceAnswerImage" />
</div>
@@ -355,7 +361,7 @@ async function confirmScore() {
border-radius: 6px;
white-space: pre-wrap;
}
.ref-image {
.ref-image, .student-image {
max-width: 100%;
max-height: 300px;
border-radius: 6px;
+3 -2
View File
@@ -28,7 +28,7 @@
<div class="ref-image-section">
<template v-if="questions[activeIndex].referenceAnswerImage">
<div class="ref-image-preview">
<img :src="'data:image/png;base64,' + questions[activeIndex].referenceAnswerImage" />
<img :src="questions[activeIndex].referenceAnswerImage" />
<el-button size="small" type="danger" text @click="removeRefImage(activeIndex)">删除图片</el-button>
</div>
</template>
@@ -61,6 +61,7 @@ import { ElMessage } from 'element-plus'
import { useAppStore } from '@/stores/app'
import { useGradingStore } from '@/stores/grading'
import type { QuestionItem } from '@/types'
import { addWatermark } from '@/utils/image'
const appStore = useAppStore()
const gradingStore = useGradingStore()
@@ -142,7 +143,7 @@ async function uploadRefImage(idx: number) {
if (!window.electronAPI) return
const data = await window.electronAPI.dialog.openImage()
if (data) {
questions.value[idx].referenceAnswerImage = `data:${data.mime};base64,${data.base64}`
questions.value[idx].referenceAnswerImage = `data:image/${data.mime};base64,${await addWatermark(data.base64, '参考答案')}`
}
}
+21
View File
@@ -0,0 +1,21 @@
export function addWatermark(base64: string, text: string): Promise<string> {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = () => {
const canvas = document.createElement('canvas')
canvas.width = img.width
canvas.height = img.height
const ctx = canvas.getContext('2d')
if (!ctx) return reject(new Error('Canvas 2D context not available'))
ctx.drawImage(img, 0, 0)
ctx.font = `bold ${Math.max(28, Math.round(img.width / 20))}px sans-serif`
ctx.fillStyle = 'rgba(255, 0, 0, 0.85)'
ctx.textBaseline = 'top'
const padding = Math.max(8, Math.round(img.width / 60))
ctx.fillText(text, padding, padding)
resolve(canvas.toDataURL('image/png').replace(/^data:image\/png;base64,/, ''))
}
img.onerror = () => reject(new Error('Failed to load image for watermark'))
img.src = `data:image/png;base64,${base64}`
})
}