|
@@ -0,0 +1,558 @@
|
|
|
+<template>
|
|
|
+ <div class="table-box">
|
|
|
+ <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listDataAugmentationApi3">
|
|
|
+ <!-- 表格 header 按钮 -->
|
|
|
+ <template #tableHeader="scope">
|
|
|
+ <el-button type="primary" v-auth="['demo:DataAugmentation:add']" icon="CirclePlus" @click="openDialog(1, '任务新增')"> 新增 </el-button>
|
|
|
+ <!-- <el-button type="primary" v-auth="['demo:DataAugmentation:import']" icon="Upload" plain @click="batchAdd"> 导入</el-button> -->
|
|
|
+ <!-- <el-button type="primary" v-auth="['demo:DataAugmentation:export']" icon="Download" plain @click="downloadFile"> 导出 </el-button> -->
|
|
|
+ <el-button
|
|
|
+ type="danger"
|
|
|
+ v-auth="['demo:DataAugmentation:remove']"
|
|
|
+ icon="Delete"
|
|
|
+ plain
|
|
|
+ :disabled="!scope.isSelected"
|
|
|
+ @click="batchDelete(scope.selectedListIds)"
|
|
|
+ >
|
|
|
+ 批量删除
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ <!-- 表格操作 -->
|
|
|
+ <template #operation="scope">
|
|
|
+ <el-button type="primary" link icon="View" @click="startDataAugmentation(scope.row)" v-if="scope.row.status == '0'"> 开始 </el-button>
|
|
|
+ <el-popconfirm title="确定终止此任务吗?" @confirm="stopDataAugmentation(scope.row)" v-if="scope.row.status == '1'">
|
|
|
+ <template #reference>
|
|
|
+ <el-button type="primary" link icon="View"> 终止 </el-button>
|
|
|
+ </template>
|
|
|
+ </el-popconfirm>
|
|
|
+ <el-button type="primary" link icon="View" @click="openResultDialog(scope.row.id)" v-if="scope.row.status == '2'"> 预览 </el-button>
|
|
|
+ <el-button type="primary" link icon="View" @click="downloadFile(scope.row)" v-if="scope.row.status == '2'"> 导出 </el-button>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ link
|
|
|
+ icon="View"
|
|
|
+ @click="openMetricDialog(scope.row.id)"
|
|
|
+ v-if="(scope.row.taskType == '图像增强' || scope.row.taskType == '图像逆光') && scope.row.status == '2'"
|
|
|
+ >
|
|
|
+ 指标
|
|
|
+ </el-button>
|
|
|
+ <el-button type="primary" link icon="View" v-auth="['demo:DataAugmentation:query']" @click="openDialog(3, '任务查看', scope.row)">
|
|
|
+ 查看
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ link
|
|
|
+ icon="View"
|
|
|
+ v-auth="['demo:DataAugmentation:query']"
|
|
|
+ @click="viewLogRef.handleOpen(scope.row.id)"
|
|
|
+ v-if="scope.row.status != '0' && scope.row.status != '1'"
|
|
|
+ >
|
|
|
+ 日志
|
|
|
+ </el-button>
|
|
|
+ <el-button type="primary" link icon="Delete" v-auth="['demo:DataAugmentation:remove']" @click="deleteDataAugmentation(scope.row)">
|
|
|
+ 删除
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </ProTable>
|
|
|
+ <DataAugmentationFormDialog ref="formDialogRef" />
|
|
|
+ <ImportExcel ref="dialogRef" />
|
|
|
+ <el-dialog v-model="dialogVisible" title="结果预览" width="80%">
|
|
|
+ <div style="display: flex; text-align: center">
|
|
|
+ <div style="width: 50%" v-if="inputVideoUrl">
|
|
|
+ <video controls width="90%">
|
|
|
+ <source :src="inputVideoUrl" />
|
|
|
+ Your browser does not support the video tag.
|
|
|
+ </video>
|
|
|
+ </div>
|
|
|
+ <div style="width: 50%" v-if="outputVideoUrl">
|
|
|
+ <video controls width="90%">
|
|
|
+ <source :src="outputVideoUrl" />
|
|
|
+ Your browser does not support the video tag.
|
|
|
+ </video>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- <VideoWindow ref="videoWindowRef" :video-url="url"></VideoWindow> -->
|
|
|
+ <!-- <el-dialog v-model="logDialogVisible" title="日志" width="80%">
|
|
|
+ <el-text class="mx-1">{{ logDialog }}</el-text>
|
|
|
+ </el-dialog> -->
|
|
|
+ <ViewLog ref="viewLogRef" :get-log-api="getDialogApi" />
|
|
|
+ <el-dialog v-model="metricDialogVisible" title="算法结果指标" width="500px" style="text-align: center">
|
|
|
+ <el-table :data="metricList" style="width: 100%; margin-left: 10%; text-align: left">
|
|
|
+ <el-table-column prop="name" label="name" width="200px" />
|
|
|
+ <el-table-column prop="value" label="value" width="200px" />
|
|
|
+ </el-table>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="tsx" name="DataAugmentation">
|
|
|
+import { ref, reactive, onMounted, computed, onUnmounted, onBeforeMount } from 'vue'
|
|
|
+import { useHandleData } from '@/hooks/useHandleData'
|
|
|
+import { useDownload } from '@/hooks/useDownload'
|
|
|
+import { ElMessageBox, ElMessage } from 'element-plus'
|
|
|
+import ProTable from '@/components/ProTable/index.vue'
|
|
|
+import ImportExcel from '@/components/ImportExcel/index.vue'
|
|
|
+import ViewLog from '@/views/demo/components/ViewLog.vue'
|
|
|
+
|
|
|
+import VideoWindow from '@/views/demo/components/VideoWindow.vue'
|
|
|
+import DataAugmentationFormDialog from '@/components/DataAugmentationFormDialog/index.vue'
|
|
|
+import { ProTableInstance, ColumnProps, EnumProps } from '@/components/ProTable/interface'
|
|
|
+import {
|
|
|
+ listDataAugmentationApi3,
|
|
|
+ delDataAugmentationApi,
|
|
|
+ addDataAugmentationApi,
|
|
|
+ updateDataAugmentationApi,
|
|
|
+ importTemplateApi,
|
|
|
+ importDataAugmentationDataApi,
|
|
|
+ exportDataAugmentationApi,
|
|
|
+ getDataAugmentationApi,
|
|
|
+ startDataAugmentationApi,
|
|
|
+ stopDataAugmentationApi,
|
|
|
+ getCompareImageApi,
|
|
|
+ getCompareImageCountApi,
|
|
|
+ getDialogApi,
|
|
|
+ getTaskDictData,
|
|
|
+ getMetricApi
|
|
|
+} from '@/api/modules/demo/dataAugmentation'
|
|
|
+// import { } from '@/api/modules/system/dictData'
|
|
|
+
|
|
|
+const videoWindowRef = ref()
|
|
|
+const playerOptions = ref({})
|
|
|
+
|
|
|
+const url = 'https://cdn.theguardian.tv/webM/2015/07/20/150716YesMen_synd_768k_vp8.webm'
|
|
|
+//const url = "https://v17.dogevideo.com/vcloud/17/v/20190424/1556036075_818c4125ec9c8cbc7a7a8a7cc1601512/1037/7d515b22c4958598c0fbd1e6290a5ca5.mp4?vkey=B561D3&tkey=17316085819fec852b94&auth_key=1731622981-omcPUa7d7Zy7svg9-1035588803-64c7aed2dc55c2766fcbb126aec1452a"
|
|
|
+//打开指标窗口查看算法指标信息
|
|
|
+const metricDialogVisible = ref(false)
|
|
|
+const metricList = reactive([])
|
|
|
+const openMetricDialog = async (id: string | number) => {
|
|
|
+ const res: any = await getMetricApi(id)
|
|
|
+ console.log(res.data)
|
|
|
+ metricList.splice(0, metricList.length, ...res.data)
|
|
|
+ metricDialogVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+const dialogVisible = ref(false)
|
|
|
+const inputVideoUrl = ref()
|
|
|
+const outputVideoUrl = ref()
|
|
|
+const openResultDialog = (id: string | number) => {
|
|
|
+ inputVideoUrl.value = '/api/profile/upload/2024/11/17/schoolgirls_20241117233329A001_input/schoolgirls.mp4'
|
|
|
+ outputVideoUrl.value = '/api/profile/upload/2024/11/17/schoolgirls_20241117233329A001_input/schoolgirls.mp4'
|
|
|
+ //console.log(inputVideoUrl)
|
|
|
+ dialogVisible.value = true
|
|
|
+}
|
|
|
+const imageIdx = ref(0)
|
|
|
+
|
|
|
+const taskType = ref([])
|
|
|
+const modelType = ref([
|
|
|
+ {
|
|
|
+ label: 'yolov8_best',
|
|
|
+ value: 'yolov8_best'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'yolov8_best_new',
|
|
|
+ value: 'yolov8_best_new'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'yolov8__best',
|
|
|
+ value: 'yolov8__best'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'yolov8__best_new',
|
|
|
+ value: 'yolov8__best_new'
|
|
|
+ }
|
|
|
+])
|
|
|
+const trackingMethod = ref([
|
|
|
+ {
|
|
|
+ label: 'botsort',
|
|
|
+ value: 'botsort'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'strongsort',
|
|
|
+ value: 'strongsort'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'bytetrack',
|
|
|
+ value: 'bytetrack'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'ocsort',
|
|
|
+ value: 'ocsort'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'imprassoc',
|
|
|
+ value: 'imprassoc'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'deepocsort',
|
|
|
+ value: 'deepocsort'
|
|
|
+ }
|
|
|
+])
|
|
|
+const taskTypeEnums: EnumProps[] = []
|
|
|
+
|
|
|
+const hyperparameterConfiguration = []
|
|
|
+
|
|
|
+const viewLogRef = ref()
|
|
|
+
|
|
|
+const getTaskType = async () => {
|
|
|
+ const res: any = await getTaskDictData()
|
|
|
+ if (res.data.length != 0) {
|
|
|
+ taskType.value = res.data
|
|
|
+ .map(item => {
|
|
|
+ if (item.taskType === '多目标跟踪') {
|
|
|
+ return {
|
|
|
+ label: item.taskType,
|
|
|
+ value: item.taskType
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .filter(item => item !== null)
|
|
|
+ res.data.forEach(item => {
|
|
|
+ if (item.taskType === '多目标跟踪') {
|
|
|
+ taskTypeEnums.push({
|
|
|
+ label: item.taskType,
|
|
|
+ value: item.taskType,
|
|
|
+ disabled: false,
|
|
|
+ tagType: 'default'
|
|
|
+ })
|
|
|
+ let obj = {}
|
|
|
+ obj[item.taskType] = item.hyperparameterConfiguration
|
|
|
+ hyperparameterConfiguration.push(obj)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+let intervalId: NodeJS.Timeout | null = null
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ // 组件卸载时清除定时器
|
|
|
+ if (intervalId !== null) {
|
|
|
+ clearInterval(intervalId)
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ intervalId = setInterval(async () => {
|
|
|
+ proTable.value?.getTableList()
|
|
|
+ }, 5000)
|
|
|
+ getTaskType()
|
|
|
+})
|
|
|
+
|
|
|
+const startDataAugmentation = async (params: any) => {
|
|
|
+ const res = await startDataAugmentationApi(params.id)
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('任务已经开始,请等待')
|
|
|
+ } else {
|
|
|
+ ElMessage.error('任务开始失败,请检查!')
|
|
|
+ }
|
|
|
+ proTable.value?.getTableList()
|
|
|
+}
|
|
|
+
|
|
|
+const stopDataAugmentation = async (params: any) => {
|
|
|
+ const res = await stopDataAugmentationApi(params.id)
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('任务终止成功')
|
|
|
+ } else {
|
|
|
+ ElMessage.error('任务终止失败!')
|
|
|
+ }
|
|
|
+ proTable.value?.getTableList()
|
|
|
+}
|
|
|
+
|
|
|
+// ProTable 实例
|
|
|
+const proTable = ref<ProTableInstance>()
|
|
|
+
|
|
|
+// 删除视频去抖动信息
|
|
|
+const deleteDataAugmentation = async (params: any) => {
|
|
|
+ await useHandleData(delDataAugmentationApi, params.id, '删除任务【' + params.name + '】?')
|
|
|
+ proTable.value?.getTableList()
|
|
|
+}
|
|
|
+
|
|
|
+// 批量删除视频去抖动信息
|
|
|
+const batchDelete = async (ids: string[]) => {
|
|
|
+ await useHandleData(delDataAugmentationApi, ids, '删除所选任务?')
|
|
|
+ proTable.value?.clearSelection()
|
|
|
+ proTable.value?.getTableList()
|
|
|
+}
|
|
|
+
|
|
|
+// 导出视频去抖动列表
|
|
|
+const downloadFile = async task => {
|
|
|
+ ElMessageBox.confirm('确认导出任务数据?', '温馨提示', { type: 'warning' }).then(() =>
|
|
|
+ useDownload(exportDataAugmentationApi, 'export', task.outputPath)
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+// 批量添加视频去抖动
|
|
|
+const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
|
|
|
+
|
|
|
+const formDialogRef = ref<InstanceType<typeof DataAugmentationFormDialog> | null>(null)
|
|
|
+// 打开弹框的功能
|
|
|
+const openDialog = async (type: number, title: string, row?: any) => {
|
|
|
+ // hyperparameter.value = ''
|
|
|
+ let res = { data: {} }
|
|
|
+ if (row?.id) {
|
|
|
+ res = await getDataAugmentationApi(row?.id || null)
|
|
|
+ // hyperparameter.value = res.data?.hyperparameterConfiguration
|
|
|
+ }
|
|
|
+ // console.log(itemsOptions[1].compOptions?.value)
|
|
|
+ // 重置表单
|
|
|
+ // setItemsOptions()
|
|
|
+ const params = {
|
|
|
+ title,
|
|
|
+ width: 580,
|
|
|
+ isEdit: type !== 3,
|
|
|
+ itemsOptions: itemsOptions,
|
|
|
+ model: type == 1 ? model : res.data,
|
|
|
+ api: type == 1 ? addDataAugmentationApi : updateDataAugmentationApi,
|
|
|
+ getTableList: proTable.value?.getTableList
|
|
|
+ }
|
|
|
+ formDialogRef.value?.openDialog(params)
|
|
|
+}
|
|
|
+
|
|
|
+const statusEnums: EnumProps[] = [
|
|
|
+ {
|
|
|
+ label: '未开始',
|
|
|
+ value: '0',
|
|
|
+ disabled: false,
|
|
|
+ tagType: 'default'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '进行中',
|
|
|
+ value: '1',
|
|
|
+ disabled: false,
|
|
|
+ tagType: 'primary'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '完成',
|
|
|
+ value: '2',
|
|
|
+ disabled: false,
|
|
|
+ tagType: 'success'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '失败',
|
|
|
+ value: '3',
|
|
|
+ disabled: false,
|
|
|
+ tagType: 'danger'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '已中断',
|
|
|
+ value: '4',
|
|
|
+ disabled: false,
|
|
|
+ tagType: 'default'
|
|
|
+ }
|
|
|
+]
|
|
|
+
|
|
|
+// 表格配置项
|
|
|
+const columns = reactive<ColumnProps<any>[]>([
|
|
|
+ { type: 'selection', fixed: 'left', width: 70 },
|
|
|
+ { prop: 'id', label: '主键ID', width: 180 },
|
|
|
+ {
|
|
|
+ prop: 'name',
|
|
|
+ label: '任务名称',
|
|
|
+ search: {
|
|
|
+ el: 'input'
|
|
|
+ },
|
|
|
+ width: 150
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'taskType',
|
|
|
+ label: '任务类型',
|
|
|
+ search: {
|
|
|
+ el: 'select'
|
|
|
+ },
|
|
|
+ width: 100,
|
|
|
+ tag: true,
|
|
|
+ enum: taskTypeEnums
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'status',
|
|
|
+ label: '任务状态',
|
|
|
+ search: {
|
|
|
+ el: 'select'
|
|
|
+ },
|
|
|
+ width: 100,
|
|
|
+ tag: true,
|
|
|
+ enum: statusEnums
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'startTime',
|
|
|
+ label: '开始时间',
|
|
|
+ width: 180
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'endTime',
|
|
|
+ label: '结束时间',
|
|
|
+ width: 180
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'costSecond',
|
|
|
+ label: '耗时',
|
|
|
+ search: {
|
|
|
+ el: 'input'
|
|
|
+ },
|
|
|
+ width: 80
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'log',
|
|
|
+ label: '日志',
|
|
|
+ width: 120
|
|
|
+ },
|
|
|
+
|
|
|
+ {
|
|
|
+ prop: 'algorithmPath',
|
|
|
+ label: '日志路径',
|
|
|
+ width: 120
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'hyperparameterConfiguration',
|
|
|
+ label: '超参配置',
|
|
|
+ width: 120
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'remarks',
|
|
|
+ label: '备注',
|
|
|
+ search: {
|
|
|
+ el: 'input'
|
|
|
+ },
|
|
|
+ width: 120
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'operation',
|
|
|
+ label: '操作',
|
|
|
+ width: 230,
|
|
|
+ fixed: 'right'
|
|
|
+ }
|
|
|
+])
|
|
|
+// 表单配置项
|
|
|
+let itemsOptions: ProForm.ItemsOptions[] = [
|
|
|
+ {
|
|
|
+ label: '任务名称',
|
|
|
+ prop: 'name',
|
|
|
+ rules: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }],
|
|
|
+ compOptions: {
|
|
|
+ placeholder: '请输入任务名称'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '任务类型',
|
|
|
+ prop: 'taskType',
|
|
|
+ rules: [{ required: true, message: '任务类型不能为空', trigger: 'change' }],
|
|
|
+ compOptions: {
|
|
|
+ elTagName: 'select', // 指定使用 el-select 组件
|
|
|
+ placeholder: '请选择任务类型',
|
|
|
+ enum: taskType,
|
|
|
+ onChange: (value: string) => {
|
|
|
+ model.value['taskType'] = value
|
|
|
+ hyperparameterConfiguration.forEach(obj => {
|
|
|
+ if (value in obj) {
|
|
|
+ // console.log(obj[value])
|
|
|
+ addParams(obj[value], value)
|
|
|
+ openDialog(1, '任务新增')
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '图片集压缩包',
|
|
|
+ prop: 'inputOssId',
|
|
|
+ rules: [{ required: true, message: '数据压缩包不能为空', trigger: 'change' }],
|
|
|
+ compOptions: {
|
|
|
+ elTagName: 'file-upload',
|
|
|
+ fileSize: 4096,
|
|
|
+ fileType: ['zip'],
|
|
|
+ placeholder: '请上传图片集压缩包'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '备注',
|
|
|
+ prop: 'remarks',
|
|
|
+ rules: [
|
|
|
+ {
|
|
|
+ required: false,
|
|
|
+ trigger: 'blur'
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ compOptions: {
|
|
|
+ placeholder: '请输入备注'
|
|
|
+ }
|
|
|
+ }
|
|
|
+]
|
|
|
+const model = ref({})
|
|
|
+const setItemsOptions = () => {
|
|
|
+ if (itemsOptions.length > 4) {
|
|
|
+ itemsOptions.splice(4) // 如果里面有新增参数,删除,重新添加
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const addParams = (params, task_Type) => {
|
|
|
+ setItemsOptions()
|
|
|
+ if (params == 'null') {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ let validJsonString = params.replace(/'/g, '"')
|
|
|
+ if (task_Type === '多目标跟踪') {
|
|
|
+ itemsOptions.push({
|
|
|
+ label: '模型',
|
|
|
+ prop: 'yolo_model',
|
|
|
+ rules: [{ trigger: 'blur' }],
|
|
|
+ compOptions: {
|
|
|
+ elTagName: 'select',
|
|
|
+ enum: modelType,
|
|
|
+ placeholder: '默认值为' + 'yolov8__best.pt'
|
|
|
+ }
|
|
|
+ })
|
|
|
+ itemsOptions.push({
|
|
|
+ label: '跟踪方法',
|
|
|
+ prop: 'tracking_method',
|
|
|
+ rules: [{ trigger: 'blur' }],
|
|
|
+
|
|
|
+ compOptions: {
|
|
|
+ elTagName: 'select',
|
|
|
+ enum: trackingMethod,
|
|
|
+ placeholder: '默认值为' + 'bytetrack'
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ model.value['tracking_method'] = 'bytetrack'
|
|
|
+ model.value['yolo_model'] = 'yolov8__best.pt'
|
|
|
+ try {
|
|
|
+ const obj: { [key: string]: number } = JSON.parse(validJsonString)
|
|
|
+ Object.keys(obj).forEach(key => {
|
|
|
+ model.value[key] = obj[key]
|
|
|
+
|
|
|
+ itemsOptions.push({
|
|
|
+ label: key,
|
|
|
+ prop: key,
|
|
|
+ rules: [{ trigger: 'blur' }],
|
|
|
+ compOptions: {
|
|
|
+ type: 'input',
|
|
|
+ clearable: true,
|
|
|
+ placeholder: '默认值为' + obj[key]
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+ } catch (error) {
|
|
|
+ console.error('解析 JSON 字符串时出错:', error)
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.image-dialog {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ .el-image {
|
|
|
+ margin-right: 20px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ }
|
|
|
+}
|
|
|
+.image-dialog-btn {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ margin-top: 20px;
|
|
|
+}
|
|
|
+</style>
|