From 653a7a72f7d6a816b4b5e24a07b7d2f450cfb287 Mon Sep 17 00:00:00 2001
From: 董国庆 <364620639@qq.com>
Date: 星期五, 16 五月 2025 14:41:04 +0800
Subject: [PATCH] 实验中止审批
---
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