From c3ccabfc59431aea657379c72c2e09c8e1b27d18 Mon Sep 17 00:00:00 2001 From: 董国庆 <364620639@qq.com> Date: 星期五, 12 九月 2025 15:24:42 +0800 Subject: [PATCH] 图片回显 --- laboratory/src/components/DynamicComponent/index.vue | 370 ++++++++++++++++++++++++++++++++++------------------ 1 files changed, 242 insertions(+), 128 deletions(-) diff --git a/laboratory/src/components/DynamicComponent/index.vue b/laboratory/src/components/DynamicComponent/index.vue index a093c13..54bfc79 100644 --- a/laboratory/src/components/DynamicComponent/index.vue +++ b/laboratory/src/components/DynamicComponent/index.vue @@ -4,40 +4,91 @@ <div class="add-group"> <div v-if="title">*</div> <span v-if="title">{{ title }}</span> - <el-button v-if="editable" @click="showAddDialog = true" class="el-icon-plus" type="primary"> - 添加组件</el-button> + <el-button + v-if="editable" + @click="showAddDialog = true" + class="el-icon-plus" + type="primary" + > + 添加组件</el-button + > </div> <!-- 选择组件弹窗 --> - <AddComponentDialog v-if="editable" :visible="showAddDialog" @confirm="addComponent" - @close="showAddDialog = false" /> + <AddComponentDialog + v-if="editable" + :visible="showAddDialog" + @confirm="addComponent" + @close="showAddDialog = false" + /> <!-- 动态渲染组件 --> - <div v-for="(item, idx) in components" :key="item.id" class="dynamic-component"> + <div + v-for="(item, idx) in components" + :key="item.id" + class="dynamic-component" + > <!-- 富文本 --> <div v-if="item.type == 'richText'"> - <AiEditor :ref="`editor_${item.id}`" :value="item.data.content" height="200px" :readOnly="!editable" - placeholder="请输入内容..." :disabled="!editable" /> + <AiEditor + :ref="`editor_${item.id}`" + :value="item.data.content" + height="400px" + :readOnly="!editable" + placeholder="请输入内容..." + :disabled="!editable" + /> </div> <!-- 自定义表格 --> <div v-else-if="item.type == 'customTable'" style="flex: 1"> <div v-if="editable" class="table-actions"> - <el-button size="mini" @click="showTableHeaderDialog(idx)">添加表头</el-button> - <el-button size="mini" type="primary" @click="showAddRowDialog(idx, item.data.headers)">添加数据</el-button> + <el-button size="mini" @click="showTableHeaderDialog(idx)" + >添加表头</el-button + > + <el-button + size="mini" + type="primary" + @click="showAddRowDialog(idx, item.data.headers)" + >添加数据</el-button + > </div> - <Table :data="item.data.rows" :total="null" :height="null" class="groupTable" - :key="item.id + '_' + JSON.stringify(item.data.rows).length"> - <el-table-column v-for="(header, hidx) in item.data.headers" :key="hidx" :label="header.name" - :prop="header.name"> + <Table + :data="item.data.rows" + :total="null" + :height="null" + class="groupTable" + :key="item.id + '_' + JSON.stringify(item.data.rows).length" + > + <el-table-column + v-for="(header, hidx) in item.data.headers" + :key="hidx" + :label="header.name" + :prop="header.name" + > <template slot-scope="scope"> <!-- 用户类型显示 --> <template v-if="header.type === 'user'"> {{ getUserDisplayText(header.name, scope.row) }} </template> - <!-- 图片类型显示 --> + <!-- 图片类型显示,兼容数组和字符串 --> <template v-else-if="header.type === 'image'"> - <img v-if="scope.row[header.name]" :src="getFullUrl(scope.row[header.name])" alt="头像" - class="table-image" /> + <template v-if="Array.isArray(scope.row[header.name])"> + <el-image + v-for="(img, i) in scope.row[header.name]" + :key="i" + :src="getFullUrl(img)" + :preview-src-list="scope.row[header.name].map(getFullUrl)" + class="table-image" + /> + </template> + <template v-else> + <el-image + v-if="scope.row[header.name]" + :src="getFullUrl(scope.row[header.name])" + :preview-src-list="[getFullUrl(scope.row[header.name])]" + class="table-image" + /> + </template> </template> <!-- 其他类型 --> <template v-else> @@ -45,65 +96,128 @@ </template> </template> </el-table-column> - <el-table-column label="更新时间" prop="updateTime" min-width="180"></el-table-column> + <el-table-column + label="更新时间" + prop="updateTime" + min-width="180" + ></el-table-column> <el-table-column label="操作" min-width="200" v-if="dialogCanEdit"> <template slot-scope="scope"> - <el-button type="text" @click="handleEditRow(idx, scope.$index)">编辑</el-button> - <el-button type="text" v-if="editable" @click="handleDeleteRow(idx, scope.$index)">删除</el-button> + <el-button type="text" @click="handleEditRow(idx, scope.$index)" + >编辑</el-button + > + <el-button + type="text" + v-if="editable" + @click="handleDeleteRow(idx, scope.$index)" + >删除</el-button + > </template> </el-table-column> </Table> </div> <!-- 文件上传 --> <div v-else-if="item.type == 'fileUpload'"> - <el-upload v-if="editable" :action="uploadUrl" :headers="uploadHeaders" :file-list="item.data.fileList" + <el-upload + v-if="editable" + :action="uploadUrl" + :headers="uploadHeaders" + :file-list="item.data.fileList" :on-change="(file, fileList) => handleFileChange(idx, fileList)" - :on-success="(res, file, fileList) => handleFileSuccess(res, file, fileList, idx)" - list-type="text"> + :on-success=" + (res, file, fileList) => + handleFileSuccess(res, file, fileList, idx) + " + list-type="text" + > <el-button size="small" icon="el-icon-upload2">点击上传</el-button> </el-upload> <div v-else> - <div v-for="file in item.data.fileList" :key="file.uid" class="file-list-item" > - <span style="color: #409EFF; cursor: pointer;" @click="downloadFileByUrl(file.url, file.name)">{{ file.name }}</span> + <div + v-for="file in item.data.fileList" + :key="file.uid" + class="file-list-item" + > + <span + style="color: #409eff; cursor: pointer" + @click="downloadFileByUrl(file.url, file.name)" + >{{ file.name }}</span + > </div> </div> </div> <!-- 图片上传 --> <div v-else-if="item.type == 'imageUpload'"> <div class="image-upload-container"> - <el-upload v-if="editable" - :action="uploadUrl" - :headers="uploadHeaders" - :file-list="item.data.imageList" + <el-upload + v-if="editable" + :action="uploadUrl" + :headers="uploadHeaders" + :file-list="item.data.imageList" :on-change="(file, fileList) => handleImageChange(idx, fileList)" - :on-success="(res, file, fileList) => handleImageSuccess(res, file, fileList, idx)" :auto-upload="true" - :before-upload="beforeImageUpload" + :on-success=" + (res, file, fileList) => + handleImageSuccess(res, file, fileList, idx) + " + :auto-upload="true" + :before-upload="beforeImageUpload" list-type="picture-card" - :on-preview="(file) => handlePreview(file, idx)" class="image-uploader"> + :on-preview="(file) => handlePreview(file, idx)" + class="image-uploader" + > <i class="el-icon-plus"></i> <div class="upload-text">上传图片</div> </el-upload> <div v-else class="image-preview"> - <el-image v-for="image in item.data.imageList" :key="image.uid" :src="getFullUrl(image.url)" - :preview-src-list="item.data.imageList.map(img => getFullUrl(img.url))" class="preview-image" /> + <el-image + v-for="image in item.data.imageList" + :key="image.uid" + :src="getFullUrl(image.url)" + :preview-src-list=" + item.data.imageList.map((img) => getFullUrl(img.url)) + " + class="preview-image" + /> </div> <div class="uploaf-notice">支持.jpg .png格式</div> </div> </div> - <img v-if="editable" src="@/assets/public/delete.png" @click="removeComponent(idx)" alt="删除" - class="delete-icon" /> + <img + v-if="editable" + src="@/assets/public/delete.png" + @click="removeComponent(idx)" + alt="删除" + class="delete-icon" + /> </div> </div> - - <addTableHeader :visible.sync="tableHeaderDialog.visible" :participants="participants" @confirm="confirmAddHeader"> + <addTableHeader + :visible.sync="tableHeaderDialog.visible" + :participants="participants" + @confirm="confirmAddHeader" + > </addTableHeader> - <addTableData :visible.sync="rowDialog.visible" :headerList="rowDialog.headers" :editData="rowDialog.form" - :isEdit="rowDialog.isEdit" @success="confirmAddRow"> + <addTableData + :visible.sync="rowDialog.visible" + :headerList="rowDialog.headers" + :editData="rowDialog.form" + :isEdit="rowDialog.isEdit" + @success="confirmAddRow" + > </addTableData> - <el-dialog :visible.sync="imagePreviewVisible" width="auto" top="10vh" :show-close="true" v-if="imagePreviewUrl"> - <img :src="imagePreviewUrl" style="max-width:80vw;max-height:70vh;display:block;margin:auto;" /> + <el-dialog + :visible.sync="imagePreviewVisible" + width="auto" + top="10vh" + :show-close="true" + v-if="imagePreviewUrl" + > + <img + :src="imagePreviewUrl" + style="max-width: 80vw; max-height: 70vh; display: block; margin: auto" + /> </el-dialog> </div> </template> @@ -114,9 +228,9 @@ import Table from "../Table/index.vue"; import addTableHeader from "./addTableHeader.vue"; import addTableData from "./addTableData.vue"; -import apiConfig from '../../utils/baseurl' -import { getFullUrl } from '@/utils/utils' -import { downloadFileByUrl } from '@/utils/utils' +import apiConfig from "../../utils/baseurl"; +import { getFullUrl } from "@/utils/utils"; +import { downloadFileByUrl,getAllocateIp } from "@/utils/utils"; export default { name: "DynamicComponent", @@ -134,27 +248,27 @@ }, participants: { type: Array, - default: () => [] + default: () => [], }, dataSource: { type: Array, - default: () => [] + default: () => [], }, editable: { type: Boolean, - default: true + default: true, }, dialogCanEdit: { type: Boolean, - default: true - } + default: true, + }, }, data() { return { apiConfig: apiConfig, uploadUrl: apiConfig.imgUrl, uploadHeaders: { - Authorization: sessionStorage.getItem('token') || '' + Authorization: sessionStorage.getItem("token") || "", }, showAddDialog: false, components: [], @@ -172,53 +286,56 @@ form: {}, }, headerList: [], - defaultImageUrl: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg', // 默认图片地址 + defaultImageUrl: + "https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg", // 默认图片地址 imagePreviewVisible: false, - imagePreviewUrl: '', + imagePreviewUrl: "", }; }, watch: { dataSource: { handler(newVal) { if (newVal) { - newVal = newVal.map(component => { + newVal = newVal.map((component) => { let componentData = null; switch (component.type) { - case 'richText': - componentData = { content: component.data } + case "richText": + componentData = { content: component.data }; break; - case 'customTable': + case "customTable": componentData = { headers: component.data.headers, - rows: component.data.rows + rows: component.data.rows, }; break; - case 'fileUpload': + case "fileUpload": componentData = { fileList: component.data }; - console.log('component.data component.data',component.data) + console.log("component.data component.data", component.data); break; - case 'imageUpload': - componentData = { imageList: component.data.map(item=>{ - return { - ...item, - url: getFullUrl(item.url), - } - }) }; + case "imageUpload": + componentData = { + imageList: component.data.map((item) => { + return { + ...item, + url: getFullUrl(item.url), + }; + }), + }; break; } return { type: component.type, id: component.id || Math.random().toString(36).substr(2, 9), - data: componentData - } - }) + data: componentData, + }; + }); } this.components = newVal ? [...newVal] : []; }, immediate: true, - deep: true - } + deep: true, + }, }, methods: { getFullUrl, @@ -229,49 +346,51 @@ // 如果没有rowData或fieldName,直接返回空字符串 if (!rowData || !fieldName) { - return ''; + return ""; } // 情况1: 有_userInfo数据 if (rowData[userInfoKey] && Array.isArray(rowData[userInfoKey])) { - return rowData[userInfoKey].map(user => user.label || '').join(', '); + return rowData[userInfoKey].map((user) => user.label || "").join(", "); } // 情况2: 只有用户ID数组 if (Array.isArray(rowData[fieldName])) { // 使用participants查找用户信息 - return rowData[fieldName].map(userId => { - const user = this.participants.find(p => p.userId === userId); - return user ? (user.nickName || user.userName || userId) : userId; - }).join(', '); + return rowData[fieldName] + .map((userId) => { + const user = this.participants.find((p) => p.userId === userId); + return user ? user.nickName || user.userName || userId : userId; + }) + .join(", "); } // 情况3: 已经是字符串(可能是之前格式化过的) - if (typeof rowData[fieldName] === 'string') { + if (typeof rowData[fieldName] === "string") { return rowData[fieldName]; } // 默认返回空字符串 - return ''; + return ""; }, formatUserNames(userArray) { if (!userArray || !Array.isArray(userArray) || userArray.length === 0) { - return ''; + return ""; } // 查找参与者列表中的用户,获取昵称并用逗号拼接 - const userNames = userArray.map(userId => { - const user = this.participants.find(p => p.userId === userId); + const userNames = userArray.map((userId) => { + const user = this.participants.find((p) => p.userId === userId); return user ? user.nickName : userId; }); - return userNames.join(', '); + return userNames.join(", "); }, addComponent(type) { if (!this.editable) return; if (type === "customTable") { if (!this.participants || this.participants.length === 0) { - this.$message.warning('请先选择实验调度'); + this.$message.warning("请先选择实验调度"); this.showAddDialog = false; return; } @@ -289,34 +408,34 @@ this.emitUpdate(); }, submit() { - const data = this.components.map(component => { + const data = this.components.map((component) => { let componentData = null; - const prefix = apiConfig.showImgUrl; - + // const prefix = apiConfig.showImgUrl; + const prefix = getAllocateIp(); switch (component.type) { - case 'richText': + case "richText": const editorRef = this.$refs[`editor_${component.id}`]; const editor = Array.isArray(editorRef) ? editorRef[0] : editorRef; - const content = editor ? editor.getContent() : ''; - componentData = content && content !== '<p></p>' ? content : ''; + const content = editor ? editor.getContent() : ""; + componentData = content && content !== "<p></p>" ? content : ""; break; - case 'customTable': + case "customTable": componentData = { headers: component.data.headers, - rows: component.data.rows + rows: component.data.rows, }; break; - case 'fileUpload': - componentData = component.data.fileList.map(file => { - console.log('fileUpload fileUpload fileUpload',file) + case "fileUpload": + componentData = component.data.fileList.map((file) => { + console.log("fileUpload fileUpload fileUpload", file); if (file.url && file.url.startsWith(prefix)) { return { ...file, url: file.url.substring(prefix.length) }; } return file; }); break; - case 'imageUpload': - componentData = component.data.imageList.map(image => { + case "imageUpload": + componentData = component.data.imageList.map((image) => { if (image.url && image.url.startsWith(prefix)) { return { ...image, url: image.url.substring(prefix.length) }; } @@ -327,11 +446,11 @@ return { type: component.type, - data: componentData + data: componentData, }; }); - this.$emit('submit', data); + this.$emit("submit", data); }, confirmAddRow(formData) { // if (!this.editable) return; @@ -344,19 +463,15 @@ // 调试输出 if (isEdit) { // Vue无法检测到对象或数组深层属性的变化,使用Vue.set来确保响应式 - this.$set( - this.components[idx].data.rows, - rowIndex, - { - ...processedData, - updateTime: new Date().toLocaleString() - } - ); + this.$set(this.components[idx].data.rows, rowIndex, { + ...processedData, + updateTime: new Date().toLocaleString(), + }); } else { // 使用数组方法push会被Vue检测到 this.components[idx].data.rows.push({ ...processedData, - updateTime: new Date().toLocaleString() + updateTime: new Date().toLocaleString(), }); } @@ -387,24 +502,24 @@ const { idx } = this.tableHeaderDialog; this.components[idx].data.headers.push({ ...data }); - this.components[idx].data.rows.forEach(row => { - if (typeof row === 'object' && row !== null) { - if (data.type === 'user') { + this.components[idx].data.rows.forEach((row) => { + if (typeof row === "object" && row !== null) { + if (data.type === "user") { this.$set(row, data.name, []); } else { - this.$set(row, data.name, ''); + this.$set(row, data.name, ""); } } else { const newRow = {}; - this.components[idx].data.headers.forEach(header => { + this.components[idx].data.headers.forEach((header) => { if (header.name === data.name) { - if (header.type === 'user') { + if (header.type === "user") { newRow[header.name] = []; } else { - newRow[header.name] = ''; + newRow[header.name] = ""; } } else { - newRow[header.name] = row[header.name] || ''; + newRow[header.name] = row[header.name] || ""; } }); const rowIndex = this.components[idx].data.rows.indexOf(row); @@ -458,7 +573,7 @@ this.$message.success("删除成功"); this.emitUpdate(); }) - .catch(() => { }); + .catch(() => {}); }, handleFileChange(idx, fileList) { if (!this.editable) return; @@ -485,18 +600,18 @@ this.emitUpdate(); }, beforeImageUpload(file) { - const isJPG = file.type === 'image/jpeg'; - const isPNG = file.type === 'image/png'; - const isLt2M = file.size / 1024 / 1024 < 2; + const isJPG = file.type === "image/jpeg"; + const isPNG = file.type === "image/png"; + // const isLt2M = file.size / 1024 / 1024 < 2; if (!isJPG && !isPNG) { - this.$message.error('上传图片只能是 JPG 或 PNG 格式!'); + this.$message.error("上传图片只能是 JPG 或 PNG 格式!"); return false; } - if (!isLt2M) { - this.$message.error('上传图片大小不能超过 2MB!'); - return false; - } + // if (!isLt2M) { + // this.$message.error('上传图片大小不能超过 2MB!'); + // return false; + // } this.imagePreviewVisible = true; return true; }, @@ -510,11 +625,10 @@ emitUpdate() { // 先创建新对象,这有助于触发更新 const updatedComponents = JSON.parse(JSON.stringify(this.components)); - this.$emit('update:dataSource', updatedComponents); + this.$emit("update:dataSource", updatedComponents); }, }, - computed: { - }, + computed: {}, }; </script> -- Gitblit v1.7.1