- 新增 uuid.ts 工具函数,提供原生和兼容性 UUID 生成方案 - 更新模板详情页面,使用 generateUUID() 替代 crypto.randomUUID() - 模板列表新增页码字段显示 - 优化模板列表组件代码格式,改进可读性 - 更新 Git 提交消息规范文档,强制使用中文描述和具体要求
292 lines
7.8 KiB
Vue
292 lines
7.8 KiB
Vue
<!-- eslint-disable no-console -->
|
|
<script lang="ts" setup>
|
|
import type { GenSettings, Region, TemplateInfo } from './types'
|
|
import { Icon } from '@iconify/vue'
|
|
import { NButton, NUpload, useMessage } from 'naive-ui'
|
|
import { onMounted, ref } from 'vue'
|
|
import { useRoute } from 'vue-router'
|
|
import { useRouterPush } from '@/hooks/common/router'
|
|
import { fetchAddTemplate, fetchTemplateDetail } from '@/service/api/template'
|
|
import { generateUUID } from '@/utils/uuid'
|
|
import { useUpload } from './hooks/useUpload'
|
|
import TemplateCanvas from './modules/TemplateCanvas.vue'
|
|
import TemplateSettings from './modules/TemplateSettings.vue'
|
|
|
|
const route = useRoute()
|
|
const message = useMessage()
|
|
const { routerBack } = useRouterPush()
|
|
|
|
// 模版基础信息
|
|
const templateInfo = ref<TemplateInfo>({
|
|
name: '',
|
|
width: 2100,
|
|
height: 2997,
|
|
})
|
|
|
|
const imgSrc = ref('')
|
|
|
|
// 生成配置
|
|
const genSettings = ref<GenSettings>({
|
|
cols: 6,
|
|
rows: 1,
|
|
w: 137,
|
|
h: 133,
|
|
gapX: 20,
|
|
gapY: 0,
|
|
startX: 0,
|
|
startY: 0,
|
|
})
|
|
|
|
// 区域列表
|
|
const regions = ref<Region[]>([])
|
|
|
|
// 选中区域集合
|
|
const selectedRegionIds = ref<Set<string>>(new Set())
|
|
|
|
// 视图控制
|
|
const showGrid = ref(false)// 是否显示网格
|
|
const showRuler = ref(true)// 是否显示标尺
|
|
const showCoordinates = ref(false)// 是否显示坐标
|
|
|
|
// Use Hook
|
|
const { loading, handleUpload, handleUploadCheck } = useUpload(imgSrc, templateInfo, regions, selectedRegionIds)
|
|
|
|
const uploadRef = ref<any>(null)
|
|
|
|
function handleUploadTrigger() {
|
|
handleUploadCheck(() => {
|
|
uploadRef.value?.openOpenFileDialog()
|
|
})
|
|
}
|
|
|
|
// --- Actions ---
|
|
|
|
/**
|
|
* 处理区域生成
|
|
*/
|
|
function handleGenerate() {
|
|
const groupId = generateUUID()
|
|
const newRegions: Region[] = []
|
|
|
|
// 计算起始编号:查找现有的最大 Qx 编号
|
|
let maxNum = 0
|
|
regions.value.forEach((r) => {
|
|
const match = r.label.match(/^Q(\d+)$/)
|
|
if (match) {
|
|
const n = Number.parseInt(match[1], 10)
|
|
if (!Number.isNaN(n) && n > maxNum) {
|
|
maxNum = n
|
|
}
|
|
}
|
|
})
|
|
|
|
const currentX = genSettings.value.startX
|
|
const currentY = genSettings.value.startY
|
|
for (let r = 0; r < genSettings.value.rows; r++) {
|
|
for (let c = 0; c < genSettings.value.cols; c++) {
|
|
const index = maxNum + r * genSettings.value.cols + c + 1 // 计算当前区域的编号
|
|
newRegions.push({
|
|
id: generateUUID(),
|
|
groupId,
|
|
x: currentX + c * (genSettings.value.w + genSettings.value.gapX),
|
|
y: currentY + r * (genSettings.value.h + genSettings.value.gapY),
|
|
w: genSettings.value.w,
|
|
h: genSettings.value.h,
|
|
label: `Q${index}`, // 题目编号递增
|
|
selected: false,
|
|
})
|
|
}
|
|
}
|
|
|
|
regions.value.push(...newRegions)
|
|
|
|
if (newRegions.length > 1) {
|
|
const newIds = new Set(newRegions.map(r => r.id))
|
|
selectedRegionIds.value = newIds
|
|
}
|
|
|
|
window?.$message?.success(`已生成 ${newRegions.length} 个区域`)
|
|
}
|
|
|
|
/**
|
|
* 删除区域
|
|
*/
|
|
function deleteRegion(id: string) {
|
|
regions.value = regions.value.filter(r => r.id !== id)
|
|
selectedRegionIds.value.delete(id)
|
|
}
|
|
|
|
/**
|
|
* 清空所有
|
|
*/
|
|
function clearAll() {
|
|
regions.value = []
|
|
selectedRegionIds.value.clear()
|
|
}
|
|
|
|
/**
|
|
* 保存模板
|
|
*/
|
|
async function handleSave() {
|
|
const id = Number(route.query.id) || 0
|
|
|
|
const params: Api.Template.AddTemplateParams = {
|
|
id,
|
|
pageNo: templateInfo.value.pageNo || '1761.172.8.25',
|
|
name: templateInfo.value.name,
|
|
backGroundUrl: templateInfo.value.backGroundUrl || '',
|
|
width: Math.round(templateInfo.value.width),
|
|
height: Math.round(templateInfo.value.height),
|
|
tempContent: JSON.stringify(regions.value),
|
|
}
|
|
console.log(params, 'params')
|
|
|
|
// 校验
|
|
if (!params.name) {
|
|
message.error('请输入模板名称')
|
|
return
|
|
}
|
|
if (!params.pageNo) {
|
|
message.error('请输入页码')
|
|
return
|
|
}
|
|
|
|
const { error } = await fetchAddTemplate(params)
|
|
|
|
if (!error) {
|
|
message.success('保存成功')
|
|
routerBack()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 详情编辑
|
|
* 初始化数据
|
|
*/
|
|
async function initData() {
|
|
const id = Number(route.query.id)
|
|
if (id) {
|
|
try {
|
|
loading.value = true
|
|
const { data: response, error } = await fetchTemplateDetail(id)
|
|
console.log(response, 'response')
|
|
console.log(error, 'error')
|
|
if (!error && response) {
|
|
const { data, success } = response
|
|
if (success) {
|
|
const { Name, Width, Height, BackGroundUrl, TempContent, PageNo } = data || {}
|
|
templateInfo.value = {
|
|
name: Name,
|
|
width: Width,
|
|
height: Height,
|
|
pageNo: PageNo,
|
|
backGroundUrl: BackGroundUrl,
|
|
}
|
|
imgSrc.value = BackGroundUrl || ''
|
|
|
|
if (TempContent) {
|
|
try {
|
|
regions.value = JSON.parse(TempContent || '[]')
|
|
}
|
|
catch (e) {
|
|
console.error('Failed to parse tempContent', e)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (e) {
|
|
console.error('Failed to fetch template detail', e)
|
|
}
|
|
finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
initData()
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="h-full flex flex-col bg-gray-100">
|
|
<!-- Header -->
|
|
<div class="h-14 flex shrink-0 items-center justify-between border-b bg-white px-4">
|
|
<div class="flex items-center gap-2">
|
|
<NButton quaternary circle @click="routerBack()">
|
|
<template #icon>
|
|
<Icon icon="carbon:arrow-left" />
|
|
</template>
|
|
</NButton>
|
|
<span class="text-lg font-medium">答题卡排版编辑器 V2.0</span>
|
|
</div>
|
|
<div class="flex items-center gap-3">
|
|
<NButton
|
|
size="small"
|
|
:type="showGrid ? 'primary' : 'default'"
|
|
secondary
|
|
@click="showGrid = !showGrid"
|
|
>
|
|
<template #icon>
|
|
<Icon icon="carbon:grid" />
|
|
</template>
|
|
{{ showGrid ? '隐藏网格' : '显示网格' }}
|
|
</NButton>
|
|
<NButton
|
|
size="small"
|
|
:type="showRuler ? 'primary' : 'default'"
|
|
secondary
|
|
@click="showRuler = !showRuler"
|
|
>
|
|
<template #icon>
|
|
<Icon icon="carbon:ruler" />
|
|
</template>
|
|
{{ showRuler ? '隐藏标尺' : '显示标尺' }}
|
|
</NButton>
|
|
<NButton
|
|
size="small"
|
|
:type="showCoordinates ? 'primary' : 'default'"
|
|
secondary
|
|
@click="showCoordinates = !showCoordinates"
|
|
>
|
|
<template #icon>
|
|
<Icon icon="carbon:center-to-fit" />
|
|
</template>
|
|
{{ showCoordinates ? '关闭测距' : '开启测距' }}
|
|
</NButton>
|
|
<div class="h-6 w-px bg-gray-200" />
|
|
<NUpload
|
|
ref="uploadRef" :show-file-list="false" :custom-request="handleUpload"
|
|
accept=".pdf,image/png,image/jpeg,image/jpg" style="display: none"
|
|
/>
|
|
<NButton @click="handleUploadTrigger">
|
|
<template #icon>
|
|
<Icon icon="carbon:upload" />
|
|
</template>
|
|
上传底稿
|
|
</NButton>
|
|
<NButton type="primary" :loading="loading" :disabled="!templateInfo.backGroundUrl" @click="handleSave">
|
|
完成设计并保存
|
|
</NButton>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex flex-1 overflow-hidden">
|
|
<!-- Left: Canvas Area -->
|
|
<TemplateCanvas
|
|
v-model:template-info="templateInfo" v-model:regions="regions"
|
|
v-model:selected-region-ids="selectedRegionIds" :img-src="imgSrc" :loading="loading"
|
|
:show-grid="showGrid" :show-ruler="showRuler" :show-coordinates="showCoordinates"
|
|
/>
|
|
|
|
<!-- Right: Settings Panel -->
|
|
<TemplateSettings
|
|
v-model:template-info="templateInfo" v-model:gen-settings="genSettings" v-model:selected-region-ids="selectedRegionIds"
|
|
:regions="regions"
|
|
@generate="handleGenerate" @delete-region="deleteRegion" @clear-all="clearAll"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|