From 7615cd178bddab96443504029285d65ea1e7d447 Mon Sep 17 00:00:00 2001 From: pyt <626651354@qq.com> Date: 星期五, 16 五月 2025 09:22:21 +0800 Subject: [PATCH] Merge branch 'main' of http://120.76.84.145:10101/gitblit/r/H5/leshan-laboratory --- laboratory/src/components/DynamicComponent/index.vue | 400 +++++++++++++++++++++++++++++++++++--------------------- 1 files changed, 248 insertions(+), 152 deletions(-) diff --git a/laboratory/src/components/DynamicComponent/index.vue b/laboratory/src/components/DynamicComponent/index.vue index ac328f7..8faa19a 100644 --- a/laboratory/src/components/DynamicComponent/index.vue +++ b/laboratory/src/components/DynamicComponent/index.vue @@ -4,155 +4,96 @@ <div class="add-group"> <div v-if="title">*</div> <span v-if="title">{{ title }}</span> - <el-button - @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 - :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" - placeholder="请输入内容..." - /> + <AiEditor :ref="`editor_${item.id}`" :value="item.data.content" height="200px" :readOnly="!editable" placeholder="请输入内容..." + :disabled="!editable" /> </div> <!-- 自定义表格 --> <div v-else-if="item.type == 'customTable'" style="flex: 1"> - <div 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 - > + <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> </div> - <Table - :data="item.data.rows" - :total="null" - :height="null" - class="groupTable" - > - <!-- <el-table-column - type="index" - label="序号" - width="80" - ></el-table-column> --> - - <el-table-column - v-for="(header, hidx) in item.data.headers" - :key="hidx" - :label="header.name" - :prop="header.name" - /> - <el-table-column - label="更新时间" - prop="updateTime" - min-width="180" - ></el-table-column> - <el-table-column label="操作" min-width="200"> + <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"> - <el-button type="text" @click="handleEditRow(idx, scope.$index)" - >编辑</el-button - > - <el-button - type="text" - @click="handleDeleteRow(idx, scope.$index)" - >删除</el-button - > + <!-- 用户类型显示 --> + <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="scope.row[header.name]" + alt="头像" + class="table-image" /> + </template> + <!-- 其他类型 --> + <template v-else> + {{ scope.row[header.name] }} + </template> + </template> + </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> </template> </el-table-column> </Table> </div> <!-- 文件上传 --> <div v-else-if="item.type == 'fileUpload'"> - <el-upload - action="#" - :file-list="item.data.fileList" - :on-change="(file, fileList) => handleFileChange(idx, fileList)" - list-type="text" - > + <el-upload v-if="editable" action="#" :file-list="item.data.fileList" + :on-change="(file, fileList) => handleFileChange(idx, fileList)" 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"> + {{ file.name }} + </div> + </div> </div> <!-- 图片上传 --> <div v-else-if="item.type == 'imageUpload'"> <div class="image-upload-container"> - <el-upload - action="#" - :file-list="item.data.imageList" + <el-upload v-if="editable" action="#" :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" - :http-request="() => {}" - :before-upload="beforeImageUpload" - list-type="picture-card" - class="image-uploader" - > + :on-success="(res, file, fileList) => handleImageSuccess(res, file, fileList, idx)" :auto-upload="true" + :http-request="() => { }" :before-upload="beforeImageUpload" list-type="picture-card" + class="image-uploader"> <i class="el-icon-plus"></i> <div class="upload-text">上传图片</div> </el-upload> + <div v-else class="image-preview"> + <img v-for="image in item.data.imageList" :key="image.uid" :src="image.url" class="preview-image" /> + </div> <div class="uploaf-notice">支持.jpg .png格式</div> </div> </div> - <img - 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> - <!-- 添加表头弹窗 --> - <el-dialog - :visible.sync="tableHeaderDialog.visible" - title="添加表头" - width="300px" - > - <el-input - v-model="tableHeaderDialog.header" - placeholder="请输入表头" - ></el-input> - <span slot="footer" class="dialog-footer"> - <el-button @click="tableHeaderDialog.visible = false">取消</el-button> - <el-button type="primary" @click="confirmAddHeader">确定</el-button> - </span> - </el-dialog> - <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" - > + <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> </div> </template> @@ -181,6 +122,18 @@ participants: { type: Array, default: () => [] + }, + dataSource: { + type: Array, + default: () => [] + }, + editable: { + type: Boolean, + default: true + }, + dialogCanEdit: { + type: Boolean, + default: true } }, data() { @@ -200,12 +153,94 @@ headers: [], form: {}, }, - headerList: [], //编辑的表头列表 + headerList: [], + defaultImageUrl: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg', // 默认图片地址 }; }, + watch: { + dataSource: { + handler(newVal) { + if (newVal) { + newVal = newVal.map(component => { + let componentData = null; + + switch (component.type) { + case 'richText': + componentData = { content: component.data } + break; + case 'customTable': + componentData = { + headers: component.data.headers, + rows: component.data.rows + }; + break; + case 'fileUpload': + componentData = { fileList: component.data }; + break; + case 'imageUpload': + componentData = { imageList: component.data }; + break; + } + return { + type: component.type, + id: component.id || Math.random().toString(36).substr(2, 9), + data: componentData + } + }) + } + this.components = newVal ? [...newVal] : []; + }, + immediate: true, + deep: true + } + }, methods: { + getUserDisplayText(fieldName, rowData) { + // 检查是否有对应的userInfo数据 + const userInfoKey = `${fieldName}_userInfo`; + + // 如果没有rowData或fieldName,直接返回空字符串 + if (!rowData || !fieldName) { + return ''; + } + + // 情况1: 有_userInfo数据 + if (rowData[userInfoKey] && Array.isArray(rowData[userInfoKey])) { + 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(', '); + } + + // 情况3: 已经是字符串(可能是之前格式化过的) + if (typeof rowData[fieldName] === 'string') { + return rowData[fieldName]; + } + + // 默认返回空字符串 + return ''; + }, + formatUserNames(userArray) { + if (!userArray || !Array.isArray(userArray) || userArray.length === 0) { + return ''; + } + + // 查找参与者列表中的用户,获取昵称并用逗号拼接 + const userNames = userArray.map(userId => { + const user = this.participants.find(p => p.userId === userId); + return user ? user.nickName : userId; + }); + return userNames.join(', '); + }, addComponent(type) { - // 如果是添加自定义表格,需要检查是否已选择实验调度 + if (!this.editable) return; + if (type === "customTable") { if (!this.participants || this.participants.length === 0) { this.$message.warning('请先选择实验调度'); @@ -213,7 +248,7 @@ return; } } - + this.showAddDialog = false; const id = Date.now() + Math.random(); let data = {}; @@ -221,69 +256,88 @@ if (type === "customTable") data = { headers: [], rows: [] }; if (type === "fileUpload") data = { fileList: [] }; if (type === "imageUpload") data = { imageList: [] }; - console.log(type, "111111111111111", this.components); this.components.push({ id, type, data }); + this.emitUpdate(); }, submit() { - // 获取所有组件的数据 const data = this.components.map(component => { let componentData = null; - + switch (component.type) { case 'richText': const editorRef = this.$refs[`editor_${component.id}`]; const editor = Array.isArray(editorRef) ? editorRef[0] : editorRef; - console.log('editor ref:', editor); const content = editor ? editor.getContent() : ''; componentData = content && content !== '<p></p>' ? content : ''; break; case 'customTable': - // 获取表格数据 componentData = { headers: component.data.headers, rows: component.data.rows }; break; case 'fileUpload': - // 获取文件列表 componentData = component.data.fileList; break; case 'imageUpload': - // 获取图片列表 componentData = component.data.imageList; break; } - + return { type: component.type, data: componentData }; }); - // 触发 submit 事件,将数据传递给父组件 this.$emit('submit', data); }, confirmAddRow(formData) { + // if (!this.editable) return; + const { idx, rowIndex, isEdit } = this.rowDialog; + + // 处理formData中的数据,保证用户信息的完整性 + const processedData = { ...formData }; + + // 调试输出 + console.log('添加/编辑行数据:', processedData,'isEdit',isEdit); + if (isEdit) { - // 编辑模式 - this.components[idx].data.rows[rowIndex] = { - ...formData, - updateTime: new Date().toLocaleString() - }; + // Vue无法检测到对象或数组深层属性的变化,使用Vue.set来确保响应式 + this.$set( + this.components[idx].data.rows, + rowIndex, + { + ...processedData, + updateTime: new Date().toLocaleString() + } + ); } else { - // 新增模式 + // 使用数组方法push会被Vue检测到 this.components[idx].data.rows.push({ - ...formData, + ...processedData, updateTime: new Date().toLocaleString() }); } + console.log('this.components',this.components); + + // 手动触发组件更新 + this.$forceUpdate(); + // 延迟发送事件,确保数据已更新 + this.$nextTick(() => { + this.emitUpdate(); + }); + this.rowDialog.visible = false; this.rowDialog.form = {}; }, removeComponent(idx) { + if (!this.editable) return; + this.components.splice(idx, 1); + this.emitUpdate(); }, showTableHeaderDialog(idx) { this.tableHeaderDialog.visible = true; @@ -291,14 +345,12 @@ this.tableHeaderDialog.header = ""; }, confirmAddHeader(data) { - console.log("data", data); + if (!this.editable) return; + 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.$set(row, data.name, []); @@ -306,7 +358,6 @@ this.$set(row, data.name, ''); } } else { - // 如果行数据不是对象,转换为对象 const newRow = {}; this.components[idx].data.headers.forEach(header => { if (header.name === data.name) { @@ -319,19 +370,18 @@ newRow[header.name] = row[header.name] || ''; } }); - // 替换原行数据 const rowIndex = this.components[idx].data.rows.indexOf(row); this.components[idx].data.rows.splice(rowIndex, 1, newRow); } }); - // 关闭弹窗并重置数据 this.tableHeaderDialog.visible = false; this.tableHeaderDialog = { visible: false, idx: null, header: "", }; + this.emitUpdate(); }, showAddRowDialog(idx, headerList) { this.headerList = headerList; @@ -340,7 +390,6 @@ this.rowDialog.isEdit = false; this.rowDialog.headers = this.components[idx].data.headers; this.rowDialog.form = {}; - // 初始化表单数据 this.rowDialog.headers.forEach((header) => { if (header.type === "user") { this.rowDialog.form[header.name] = []; @@ -355,12 +404,13 @@ this.rowDialog.rowIndex = rowIndex; this.rowDialog.isEdit = true; this.rowDialog.headers = this.components[idx].data.headers; - // 深拷贝行数据,避免直接修改原数据 this.rowDialog.form = JSON.parse( JSON.stringify(this.components[idx].data.rows[rowIndex]) ); }, handleDeleteRow(idx, rowIndex) { + if (!this.editable) return; + this.$confirm("确认删除该行数据吗?", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", @@ -369,11 +419,13 @@ .then(() => { this.components[idx].data.rows.splice(rowIndex, 1); this.$message.success("删除成功"); + this.emitUpdate(); }) - .catch(() => {}); + .catch(() => { }); }, handleFileChange(idx, fileList) { - // 为每个文件添加默认的URL和名称 + if (!this.editable) return; + fileList = fileList.map(file => { if (!file.url) { file.url = 'https://picsum.photos/200/200'; @@ -384,9 +436,11 @@ return file; }); this.components[idx].data.fileList = fileList; + this.emitUpdate(); }, handleImageChange(idx, fileList) { - // 为每个文件添加默认的URL + if (!this.editable) return; + fileList = fileList.map(file => { if (!file.url) { file.url = 'https://picsum.photos/200/200'; @@ -394,9 +448,9 @@ return file; }); this.components[idx].data.imageList = fileList; + this.emitUpdate(); }, handleImageSuccess(res, file, fileList, idx) { - // 使用默认图片URL file.url = 'https://picsum.photos/200/200'; this.components[idx].data.imageList = fileList; }, @@ -415,6 +469,11 @@ } return true; }, + emitUpdate() { + // 先创建新对象,这有助于触发更新 + const updatedComponents = JSON.parse(JSON.stringify(this.components)); + this.$emit('update:dataSource', updatedComponents); + }, }, }; </script> @@ -425,9 +484,11 @@ padding: 20px; margin-top: 37px; } -.has-title{ + +.has-title { margin-top: 0px !important; } + .add-group { display: flex; align-items: center; @@ -445,25 +506,30 @@ margin: 0 32px 0 8px; } } + .dynamic-component { background: #ffffff; padding: 15px 20px; display: flex; justify-content: space-between; margin-bottom: 20px; + .delete-icon { width: 16px; height: 16px; cursor: pointer; } } -.image-uploader{ + +.image-uploader { display: flex; - ::v-deep .el-upload-list__item{ -width: 104px !important; -height: 104px !important; + + ::v-deep .el-upload-list__item { + width: 104px !important; + height: 104px !important; } } + .image-upload-container { display: flex; flex-wrap: wrap; @@ -475,6 +541,7 @@ .el-upload--picture-card { margin: 0; } + .el-upload-list--picture-card .el-upload-list__item { margin: 0 10px 10px 0; } @@ -487,6 +554,7 @@ align-items: center; justify-content: center; flex-direction: column; + .upload-text { font-weight: 400; font-size: 14px; @@ -495,6 +563,7 @@ margin-top: 13px; } } + .uploaf-notice { font-weight: 400; font-size: 14px; @@ -502,17 +571,44 @@ line-height: 22px; margin-top: 8px; } + .table-actions { margin-bottom: 10px; display: flex; gap: 10px; } + .groupTable { width: 100%; margin-top: 10px; + ::v-deep .el-input__inner { width: unset !important; } } +.file-list-item { + padding: 8px 0; + border-bottom: 1px solid #eee; +} + +.image-preview { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.preview-image { + width: 104px; + height: 104px; + object-fit: cover; + border-radius: 8px; +} + +.table-image { + width: 50px; + height: 50px; + object-fit: cover; + border-radius: 4px; +} </style> -- Gitblit v1.7.1