|
@@ -331,7 +331,7 @@
|
|
@click="handleExport"
|
|
@click="handleExport"
|
|
:loading="exportLoading"
|
|
:loading="exportLoading"
|
|
>
|
|
>
|
|
- <Icon icon="ep:upload" class="mr-5px" /> 导出
|
|
+ <Icon icon="ep:upload" class="mr-5px" /> 导出excel
|
|
</el-button>
|
|
</el-button>
|
|
<el-button
|
|
<el-button
|
|
type="success"
|
|
type="success"
|
|
@@ -468,17 +468,48 @@
|
|
<!-- 制作标签弹窗-->
|
|
<!-- 制作标签弹窗-->
|
|
<el-dialog v-model="dialogTableVisible" title="标签制作" width="800">
|
|
<el-dialog v-model="dialogTableVisible" title="标签制作" width="800">
|
|
<div>
|
|
<div>
|
|
- <el-form :model="form" label-width="auto" style="max-width: 600px">
|
|
+ <el-form-item label="尺寸">
|
|
- <el-form-item label="Activity zone">
|
|
+ <el-radio-group v-model="formData.size">
|
|
- <el-select v-model="form.region" placeholder="please select your zone">
|
|
+ <el-radio :value="1">90:54</el-radio>
|
|
- <el-option
|
|
+ <el-radio :value="2">90:50</el-radio>
|
|
- v-for="item in optionsLabel"
|
|
+ <el-radio :value="3">5:5</el-radio>
|
|
- :key="item.value"
|
|
+ </el-radio-group>
|
|
- :label="item.label"
|
|
|
|
- :value="item.value"
|
|
|
|
- />
|
|
|
|
- </el-select>
|
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
|
+ <el-form :model="formData" label-width="auto" style="max-width: 750px">
|
|
|
|
+ <el-form-item label="标题">
|
|
|
|
+ <el-select v-model="formData.content" placeholder="请选择字段">
|
|
|
|
+ <el-option
|
|
|
|
+ v-for="item in optionsLabel"
|
|
|
|
+ :key="item.value"
|
|
|
|
+ :label="item.label"
|
|
|
|
+ :value="item.value"
|
|
|
|
+ />
|
|
|
|
+ </el-select>
|
|
|
|
+ </el-form-item>
|
|
|
|
+
|
|
|
|
+ <div v-for="(item, index) in formData.contentItems" :key="index" style="margin-top: 10px">
|
|
|
|
+ <el-form-item>
|
|
|
|
+ <div style="float: left; margin-right: 5px">
|
|
|
|
+ <el-button type="primary" :icon="Delete" size="small" @click="removeContentItem(index)" />
|
|
|
|
+ </div>
|
|
|
|
+ <div style="float: left">内容{{ index + 1 }}:</div>
|
|
|
|
+ <el-select v-model="item.value" placeholder="请选择字段" style="float: left; width: 40%">
|
|
|
|
+ <el-option
|
|
|
|
+ v-for="opt in optionsLabel"
|
|
|
|
+ :key="opt.value"
|
|
|
|
+ :label="opt.label"
|
|
|
|
+ :value="opt.value"
|
|
|
|
+ />
|
|
|
|
+ </el-select>
|
|
|
|
+ </el-form-item>
|
|
|
|
+
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <el-form-item>
|
|
|
|
+ <el-button type="success" @click="addContentItem">增加内容</el-button>
|
|
|
|
+ <el-button type="primary" @click="onSubmit">导出图片</el-button>
|
|
|
|
+ <el-button @click="dialogTableVisible = false">取消</el-button>
|
|
|
|
+ </el-form-item>
|
|
</el-form>
|
|
</el-form>
|
|
</div>
|
|
</div>
|
|
</el-dialog>
|
|
</el-dialog>
|
|
@@ -490,12 +521,15 @@
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
|
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
|
|
|
+import { Plus,Delete } from '@element-plus/icons-vue'
|
|
import {dateFormatter, formatDate} from '@/utils/formatTime'
|
|
import {dateFormatter, formatDate} from '@/utils/formatTime'
|
|
import download from '@/utils/download'
|
|
import download from '@/utils/download'
|
|
import { SpecimenInfoApi, SpecimenInfoVO } from '@/api/museums/specimeninfo'
|
|
import { SpecimenInfoApi, SpecimenInfoVO } from '@/api/museums/specimeninfo'
|
|
import SpecimenInfoForm from './SpecimenInfoForm.vue'
|
|
import SpecimenInfoForm from './SpecimenInfoForm.vue'
|
|
import SpecimenImportForm from './SpecimenImportForm.vue'
|
|
import SpecimenImportForm from './SpecimenImportForm.vue'
|
|
import ImageImportForm from './imageImportForm.vue'
|
|
import ImageImportForm from './imageImportForm.vue'
|
|
|
|
+import JSZip from 'jszip';
|
|
|
|
+import { saveAs } from 'file-saver';
|
|
/** 标本管理 列表 */
|
|
/** 标本管理 列表 */
|
|
defineOptions({ name: 'SpecimenInfo' })
|
|
defineOptions({ name: 'SpecimenInfo' })
|
|
|
|
|
|
@@ -546,6 +580,18 @@ const queryParams = reactive({
|
|
operator: undefined,
|
|
operator: undefined,
|
|
entryDate: []
|
|
entryDate: []
|
|
})
|
|
})
|
|
|
|
+//标签打印的表单
|
|
|
|
+const formData = ref({
|
|
|
|
+ content: undefined,
|
|
|
|
+ selectedData: [] as SpecimenInfoVO[],
|
|
|
|
+ contentItems: [] as { label: string; value: string }[],
|
|
|
|
+ size:1,
|
|
|
|
+ sizeMap: {
|
|
|
|
+ 1: { width: 900, height: 540 },
|
|
|
|
+ 2: { width: 900, height: 500 },
|
|
|
|
+ 3: { width: 500, height: 500 }
|
|
|
|
+ }
|
|
|
|
+})
|
|
const queryFormRef = ref() // 搜索的表单
|
|
const queryFormRef = ref() // 搜索的表单
|
|
const exportLoading = ref(false) // 导出的加载中
|
|
const exportLoading = ref(false) // 导出的加载中
|
|
|
|
|
|
@@ -641,14 +687,15 @@ const handleExport = async () => {
|
|
}
|
|
}
|
|
//字段类型
|
|
//字段类型
|
|
const optionsLabel = [
|
|
const optionsLabel = [
|
|
- {
|
|
+
|
|
- value: 'specimenNumber',
|
|
|
|
- label: '标本编号',
|
|
|
|
- },
|
|
|
|
{
|
|
{
|
|
value: 'chineseName',
|
|
value: 'chineseName',
|
|
label: '中文名称',
|
|
label: '中文名称',
|
|
},
|
|
},
|
|
|
|
+ {
|
|
|
|
+ value: 'specimenNumber',
|
|
|
|
+ label: '标本编号',
|
|
|
|
+ },
|
|
{
|
|
{
|
|
value: 'specimenType',
|
|
value: 'specimenType',
|
|
label: '标本类型',
|
|
label: '标本类型',
|
|
@@ -718,12 +765,8 @@ const optionsLabel = [
|
|
},
|
|
},
|
|
|
|
|
|
{
|
|
{
|
|
- value: 'source',
|
|
+ value: 'acquisitionTime',
|
|
- label: '来源',
|
|
+ label: '入藏时间',
|
|
- },
|
|
|
|
- {
|
|
|
|
- value: 'provider',
|
|
|
|
- label: '标本提供者',
|
|
|
|
},
|
|
},
|
|
|
|
|
|
]
|
|
]
|
|
@@ -732,6 +775,18 @@ const optionsLabel = [
|
|
|
|
|
|
const selectedlabel = ref<any[]>([]) // 选中的标本
|
|
const selectedlabel = ref<any[]>([]) // 选中的标本
|
|
const dialogTableVisible = ref(false) //弹窗开关
|
|
const dialogTableVisible = ref(false) //弹窗开关
|
|
|
|
+//标签内容新增
|
|
|
|
+const addContentItem = () => {
|
|
|
|
+ if (formData.value.contentItems.length >= 4) {
|
|
|
|
+ message.warning('最多添加4个标签内容')
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ formData.value.contentItems.push({ label: '', value: '' })
|
|
|
|
+}
|
|
|
|
+//标签内容删除
|
|
|
|
+const removeContentItem = (index: number) => {
|
|
|
|
+ formData.value.contentItems.splice(index, 1)
|
|
|
|
+}
|
|
/** 处理选中变化 */
|
|
/** 处理选中变化 */
|
|
const handleSelectionChange = (selection: any[]) => {
|
|
const handleSelectionChange = (selection: any[]) => {
|
|
selectedlabel.value = selection
|
|
selectedlabel.value = selection
|
|
@@ -748,12 +803,17 @@ const labelExport = async () => {
|
|
return
|
|
return
|
|
}
|
|
}
|
|
dialogTableVisible.value = true
|
|
dialogTableVisible.value = true
|
|
|
|
+ // 将选中的数据传递给对话框
|
|
|
|
+ formData.value.selectedData = selectedlabel.value
|
|
|
|
|
|
} catch {
|
|
} catch {
|
|
} finally {
|
|
} finally {
|
|
exportLoading.value = false
|
|
exportLoading.value = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
/** 初始化 **/
|
|
/** 初始化 **/
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
getList()
|
|
getList()
|
|
@@ -778,5 +838,84 @@ const handleSegmentedChange = (value: string) => {
|
|
queryParams.specimenType = value
|
|
queryParams.specimenType = value
|
|
handleQuery()
|
|
handleQuery()
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+//标签导出
|
|
|
|
+const onSubmit = async () => {
|
|
|
|
+ try {
|
|
|
|
+ const selectedData = formData.value.selectedData
|
|
|
|
+ const labels = selectedData.map(item => {
|
|
|
|
+ const content = formData.value.contentItems
|
|
|
|
+ .map(contentItem => {
|
|
|
|
+ const label = optionsLabel.find(opt => opt.value === contentItem.value)?.label
|
|
|
|
+ const value = item[contentItem.value]
|
|
|
|
+ if (value !== null && value !== undefined && value !== '') {
|
|
|
|
+ return `${label}:${value}`
|
|
|
|
+ }
|
|
|
|
+ return ''
|
|
|
|
+ })
|
|
|
|
+ .filter(Boolean) // 过滤掉空字符串
|
|
|
|
+ .join('\n') // 每个内容项换行
|
|
|
|
+
|
|
|
|
+ return {
|
|
|
|
+ title: item[formData.value.content],
|
|
|
|
+ content: content
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ const zip = new JSZip();
|
|
|
|
+ // 生成标签图片的逻辑
|
|
|
|
+ for (const label of labels) {
|
|
|
|
+ const canvas = document.createElement('canvas')
|
|
|
|
+ const ctx = canvas.getContext('2d')
|
|
|
|
+ if (ctx) {
|
|
|
|
+ const size = formData.value.sizeMap[formData.value.size]
|
|
|
|
+ canvas.width = size.width
|
|
|
|
+ canvas.height = size.height
|
|
|
|
+ ctx.fillStyle = 'white'
|
|
|
|
+ ctx.fillRect(0, 0, canvas.width, canvas.height)
|
|
|
|
+
|
|
|
|
+ // 设置文本对齐方式为居中
|
|
|
|
+ ctx.textAlign = 'center'
|
|
|
|
+ // 设置字体样式
|
|
|
|
+ ctx.font = `${Math.min(size.width, size.height) * 0.2}px Arial`
|
|
|
|
+ // 设置文本颜色
|
|
|
|
+ ctx.fillStyle = 'black'
|
|
|
|
+ // 绘制标题
|
|
|
|
+ ctx.fillText(label.title, canvas.width / 2, size.height * 0.3)
|
|
|
|
+
|
|
|
|
+ // 设置文本对齐方式为左对齐
|
|
|
|
+ ctx.textAlign = 'left'
|
|
|
|
+ // 设置字体样式
|
|
|
|
+ ctx.font = `${Math.min(size.width, size.height) * 0.08}px Arial`
|
|
|
|
+ // 绘制内容
|
|
|
|
+ const lines = label.content.split('\n')
|
|
|
|
+ let y = size.height * 0.45
|
|
|
|
+ for (const line of lines) {
|
|
|
|
+ ctx.fillText(line, size.width * 0.15, y)
|
|
|
|
+ y += Math.min(size.width, size.height) * 0.15 // 每行间隔
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 将画布转换为数据URL
|
|
|
|
+ const dataUrl = canvas.toDataURL('image/png')
|
|
|
|
+ // 将数据URL转换为Blob
|
|
|
|
+ const response = await fetch(dataUrl)
|
|
|
|
+ const blob = await response.blob()
|
|
|
|
+
|
|
|
|
+ // 将Blob添加到压缩文件中
|
|
|
|
+ zip.file(`${label.title}.png`, blob)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ // 生成压缩文件
|
|
|
|
+ const content = await zip.generateAsync({ type: 'blob' });
|
|
|
|
+ // 下载压缩文件
|
|
|
|
+ saveAs(content, 'labels.zip');
|
|
|
|
+ message.success('标签图片已导出')
|
|
|
|
+ } catch (error) {
|
|
|
|
+ message.error('导出失败')
|
|
|
|
+ } finally {
|
|
|
|
+ dialogTableVisible.value = false
|
|
|
|
+ }
|
|
|
|
+}
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
+
|
|
|
|
+
|