From 86c6153f294e61c0ee2268b2651c63313abb56ee Mon Sep 17 00:00:00 2001 From: hejianhao <15708179461@qq.com> Date: 星期四, 15 五月 2025 17:59:23 +0800 Subject: [PATCH] Merge branch 'main' of http://120.76.84.145:10101/gitblit/r/H5/leshan-laboratory --- laboratory/src/views/dataManagement/schemeManagement/components/approvalDialog.vue | 772 +++++++++---------- laboratory/src/views/dataManagement/schemeManagement/list.vue | 43 laboratory/src/components/DynamicComponent/index.vue | 400 ++++++--- laboratory/src/components/DynamicComponent/service.js | 8 laboratory/src/components/DynamicComponent/addTableHeader.vue | 53 laboratory/src/components/DynamicComponent/addTableData.vue | 94 + laboratory/src/components/AiEditor/index.vue | 1 laboratory/src/views/dataManagement/schemeManagement/service.js | 2 laboratory/src/views/dataManagement/dispatching/list.vue | 16 laboratory/src/views/dataManagement/schemeManagement/addPlan.vue | 771 ++++++++++--------- laboratory/src/components/SelectMember/service.js | 8 laboratory/src/views/dataManagement/schemeManagement/stop-experiment.vue | 178 +++ laboratory/src/components/SelectMember/index.vue | 5 13 files changed, 1,287 insertions(+), 1,064 deletions(-) diff --git a/laboratory/src/components/AiEditor/index.vue b/laboratory/src/components/AiEditor/index.vue index 4619576..9433983 100644 --- a/laboratory/src/components/AiEditor/index.vue +++ b/laboratory/src/components/AiEditor/index.vue @@ -72,7 +72,6 @@ }, mounted() { this.initEditor() - console.log('editor instance:', this.editor) }, beforeDestroy() { this.destroyEditor() diff --git a/laboratory/src/components/DynamicComponent/addTableData.vue b/laboratory/src/components/DynamicComponent/addTableData.vue index b8956ac..22fcbb7 100644 --- a/laboratory/src/components/DynamicComponent/addTableData.vue +++ b/laboratory/src/components/DynamicComponent/addTableData.vue @@ -62,6 +62,7 @@ :disabled="!checkEditPermission(header)" > <i class="el-icon-plus"></i> + <!-- <div slot="tip" class="el-upload__tip">暂未连接服务器,使用默认图片</div> --> </el-upload> </el-form-item> <el-form-item @@ -143,6 +144,8 @@ </template> <script> +import { listByRole } from './service'; + export default { name: "AddDialog", props: { @@ -171,13 +174,8 @@ rules: {}, photoList: [], spectrumList: [], - userOptions: [ - { value: '1', label: '用户1' }, - { value: '2', label: '用户2' }, - { value: '3', label: '用户3' }, - { value: '4', label: '用户4' }, - { value: '5', label: '用户5' } - ] + defaultImageUrl: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg', // 默认图片地址 + userOptions: [] }; }, computed: { @@ -198,7 +196,6 @@ visible: { handler(newVal) { if (newVal) { - console.log('弹窗打开,初始化数据'); this.showHeaderList = JSON.parse(JSON.stringify(this.headerList)); this.$forceUpdate(); if (this.isEdit && this.editData) { @@ -209,8 +206,6 @@ this.initFormData(); } this.initRules(); - console.log('初始化后的表单数据:', this.form); - console.log('初始化后的校验规则:', this.rules); } }, }, @@ -218,7 +213,6 @@ immediate: true, handler(newVal) { if (newVal && newVal.length) { - console.log('headerList变化,重新初始化'); if (this.isEdit && this.editData) { this.setFormData(this.editData); } else { @@ -228,18 +222,22 @@ } }, }, - showHeaderList: { - immediate: true, - handler(newVal) { - if (newVal ) { - - console.log("222222222222222222", JSON.stringify(newVal)); - - } - }, - }, }, methods: { + getUserOptions() { + listByRole().then(res => { + if (res) { + this.userOptions = res.map(user => ({ + value: user.userId, + label: user.nickName || user.userName + })); + } else { + this.$message.error('获取用户列表失败'); + } + }).catch(err => { + console.error('获取用户列表失败', err); + }); + }, checkEditPermission(header) { if (!header.role || !Array.isArray(header.role)) { return true; @@ -262,7 +260,6 @@ } }); } - console.log('生成的校验规则:', rules); this.rules = rules; }, initFormData() { @@ -286,8 +283,6 @@ Object.keys(formData).forEach(key => { this.$set(this.form, key, formData[key]); }); - - console.log('初始化后的表单数据:', this.form); }, setFormData(data) { // 设置基础表单数据 @@ -355,23 +350,32 @@ this.initFormData(); }, handleSubmit() { - console.log('开始提交表单'); - console.log('表单数据:', this.form); - console.log('校验规则:', this.rules); this.$refs.form.validate((valid) => { - console.log('表单验证结果:', valid); if (valid) { const submitData = { ...this.form, photos: this.photoList, spectrums: this.spectrumList, }; - console.log('提交数据:', submitData); + + // 为用户类型字段添加用户完整信息 + if (this.headerList && this.headerList.length) { + this.headerList.forEach(header => { + if (header.type === 'user' && Array.isArray(submitData[header.name])) { + // 为每个用户类型字段添加userInfo属性,包含用户完整信息 + submitData[`${header.name}_userInfo`] = submitData[header.name].map(userId => { + const userInfo = this.userOptions.find(user => user.value === userId); + return userInfo ? userInfo : { value: userId, label: userId }; + }); + } + }); + } + + console.log(submitData,'修改的数据') this.$emit("success", submitData); this.handleClose(); } else { - console.log('表单验证失败'); this.$message.error('请填写必填项'); } }); @@ -381,20 +385,31 @@ this.$refs.form.validateField("photos"); }, handleSpectrumChange(file, fileList) { - this.spectrumList = fileList; + // 使用默认图片替代实际上传 + this.spectrumList = [{ + name: '默认图片.jpg', + url: this.defaultImageUrl, + status: 'success' + }]; + + // 同时更新form中对应的字段值以通过表单验证 + const imageHeader = this.headerList.find(h => h.type === 'image'); + if (imageHeader && imageHeader.name) { + // 保存图片URL,这样在表格中可以直接使用 + this.$set(this.form, imageHeader.name, this.defaultImageUrl); + console.log('设置图片字段:', imageHeader.name, this.defaultImageUrl); + } + this.$refs.form.validateField("spectrums"); }, handleSpectrumRemove(file) { - // 处理文件移除逻辑 + this.spectrumList = []; }, - handleIPadSpectrum() { - // TODO: 调用 iPad 选择图谱功能 - console.log("调用 iPad 选择图谱功能"); - }, }, mounted() { - console.log("初始headerList:", this.headerList); + // 获取用户列表数据 + this.getUserOptions(); }, }; </script> @@ -431,7 +446,7 @@ .form-content { flex: 1; overflow-y: auto; - padding: 0 10px; + padding: 10px 10px; max-height: calc(90vh - 250px); // 设置内容区域最大高度 &::-webkit-scrollbar { @@ -447,6 +462,9 @@ background: #f5f7fa; } } + .el-form-item::after{ + height: 10px !important; + } } } diff --git a/laboratory/src/components/DynamicComponent/addTableHeader.vue b/laboratory/src/components/DynamicComponent/addTableHeader.vue index 0623b5d..b8697d9 100644 --- a/laboratory/src/components/DynamicComponent/addTableHeader.vue +++ b/laboratory/src/components/DynamicComponent/addTableHeader.vue @@ -1,11 +1,5 @@ <template> - <el-dialog - title="新增表头" - :visible.sync="dialogVisible" - width="30%" - :close-on-click-modal="false" - @close="handleClose" - > + <el-dialog title="新增表头" :visible.sync="dialogVisible" width="30%" :close-on-click-modal="false" @close="handleClose"> <div class="sample-dialog"> <div class="sample-content"> <div class="form-content"> @@ -13,43 +7,30 @@ <el-row :gutter="24"> <el-col :span="24"> <el-form-item label="表头名称" prop="name"> - <el-input - v-model="form.name" - style="width: 100%" - placeholder="请输入表头名称" - /> + <el-input v-model="form.name" style="width: 100%" placeholder="请输入表头名称" /> </el-form-item> </el-col> <el-col :span="24"> <el-form-item label="表头类型" prop="type"> <el-radio-group v-model="form.type" style="width: 100%"> - <el-radio-button label="text">文本框</el-radio-button> + <el-radio-button label="text">文本框</el-radio-button> <el-radio-button label="image">图片上传</el-radio-button> - <el-radio-button label="date">日期选择</el-radio-button> - <el-radio-button label="user">人员选择</el-radio-button> + <el-radio-button label="date">日期选择</el-radio-button> + <el-radio-button label="user">人员选择</el-radio-button> </el-radio-group> </el-form-item> </el-col> <el-col :span="24"> <el-form-item label="操作权限" prop="role"> <el-select v-model="form.role" placeholder="请选择" style="width: 100%" multiple> - <el-option - v-for="item in options" - :key="item.value" - :label="item.label" - :value="item.value" - > + <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"> </el-option> </el-select> </el-form-item> </el-col> <el-col :span="24" v-if="['text', 'date', 'user'].includes(form.type)"> <el-form-item label="提示文案" prop="message"> - <el-input - v-model="form.message" - style="width: 100%" - placeholder="请输入提示文案" - /> + <el-input v-model="form.message" style="width: 100%" placeholder="请输入提示文案" /> </el-form-item> </el-col> <el-col :span="24"> @@ -71,7 +52,7 @@ </div> </el-dialog> </template> - + <script> export default { name: "AddDialog", @@ -139,15 +120,18 @@ }, options() { // 将participants转换为select组件需要的格式 - return this.participants.map(participant => ({ - value: participant.userId , + let userId = JSON.parse(sessionStorage.getItem('userInfo'))?.userId + let nickName = JSON.parse(sessionStorage.getItem('userInfo'))?.nickName + let newList = JSON.parse(JSON.stringify(this.participants)) + newList.push({ userId, nickName }) + return newList.map(participant => ({ + value: participant.userId, label: participant.nickName })); } }, mounted() { // 组件挂载时的初始化逻辑 - console.log('组件已挂载'); }, methods: { setFormData(data) { @@ -179,7 +163,7 @@ this.$message.error('请输入提示文案'); return; } - + this.$refs.form.validate((valid) => { if (valid) { const submitData = { @@ -192,7 +176,7 @@ }, }; </script> - + <style scoped lang="less"> ::v-deep .el-dialog__body { padding: 0; @@ -335,12 +319,15 @@ .el-upload-list { margin-top: 10px; } + .el-upload-list__item { transition: all 0.3s; + &:hover { background-color: #f5f7fa; } } + .el-upload__tip { color: #909399; font-size: 12px; @@ -348,4 +335,4 @@ } } } -</style> \ No newline at end of file +</style> \ No newline at end of file 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> diff --git a/laboratory/src/components/DynamicComponent/service.js b/laboratory/src/components/DynamicComponent/service.js new file mode 100644 index 0000000..15f0014 --- /dev/null +++ b/laboratory/src/components/DynamicComponent/service.js @@ -0,0 +1,8 @@ +import axios from '@/utils/request'; + + +// 获取项目列表 获取用户列表-不分页-根据角色筛选 +export const listByRole = (data) => { + return axios.get('/system/user/listByRole', { params:data }) +} + diff --git a/laboratory/src/components/SelectMember/index.vue b/laboratory/src/components/SelectMember/index.vue index 96ca265..621e526 100644 --- a/laboratory/src/components/SelectMember/index.vue +++ b/laboratory/src/components/SelectMember/index.vue @@ -59,7 +59,7 @@ </template> <script> -import { getRoleList, getUserList } from './service' +import { getRoleList, getUserList, listByRole } from './service' export default { props: { projectId: { @@ -118,7 +118,8 @@ if (this.projectId) { params.projectId = this.projectId; // TODO: 这里需要替换为新的接口调用 - // const res = await getProjectUserList(params); + const res = await listByRole(params); + this.tableData = res.records; } else { const res = await getUserList(params); this.tableData = res.records; diff --git a/laboratory/src/components/SelectMember/service.js b/laboratory/src/components/SelectMember/service.js index cf0bc1e..c733e22 100644 --- a/laboratory/src/components/SelectMember/service.js +++ b/laboratory/src/components/SelectMember/service.js @@ -13,4 +13,10 @@ // 角色列表不分页 export const getRoleList = (data) => { return axios.post('/system/role/listNotPage', { ...data }) -} \ No newline at end of file +} + +// 获取项目列表 获取用户列表-不分页-根据角色筛选 +export const listByRole = (data) => { + return axios.get('/system/user/listByRole', { params:data }) +} + diff --git a/laboratory/src/views/dataManagement/dispatching/list.vue b/laboratory/src/views/dataManagement/dispatching/list.vue index 11c614f..34edc48 100644 --- a/laboratory/src/views/dataManagement/dispatching/list.vue +++ b/laboratory/src/views/dataManagement/dispatching/list.vue @@ -43,22 +43,22 @@ <div class="flex a-center"> <div class="title" - :class="{ active: currentType === 'list' }" + :class="{ active: currentType == 'list' }" @click="handleTypeChange('list')" > 实验与调度列表 </div> <div - v-if="userRole === '3'" + v-if="userRole == '3'" class="drafts" - :class="{ active: currentType === 'draft' }" + :class="{ active: currentType == 'draft' }" @click="handleTypeChange('draft')" > 草稿箱 </div> </div> <el-button - v-if="userRole === '3'" + v-if="userRole == '3'" @click="handleAddPlan" class="el-icon-plus" type="primary" @@ -112,7 +112,7 @@ <template v-if="userRole == '3'"> <el-button type="text" @click="handleDetail(scope.row)">详情</el-button> <el-button - v-if="scope.row.status === 1" + v-if="scope.row.status == 1" type="text" @click="handleDelete(scope.row)" >删除</el-button> @@ -122,7 +122,7 @@ <template v-if="userRole == '4' || userRole == '5'"> <el-button type="text" @click="handleDetail(scope.row)">详情</el-button> <el-button - v-if="scope.row.status === 1" + v-if="scope.row.status == 1" type="text" @click="handleConfirm(scope.row)" >确认</el-button> @@ -259,14 +259,14 @@ }, handleTypeChange(type) { this.currentType = type; - this.form.status = type === 'draft' ? '-1' : ''; + this.form.status = type == 'draft' ? '-1' : ''; this.form.pageNum = 1; this.getTableData(); }, getTableData() { const params = { ...this.form, - status: this.currentType === 'draft' ? '-1' : this.form.status + status: this.currentType == 'draft' ? '-1' : this.form.status }; getDispatchList(params).then(res => { console.log('111111111111',res) diff --git a/laboratory/src/views/dataManagement/schemeManagement/addPlan.vue b/laboratory/src/views/dataManagement/schemeManagement/addPlan.vue index 23a85df..ef3538f 100644 --- a/laboratory/src/views/dataManagement/schemeManagement/addPlan.vue +++ b/laboratory/src/views/dataManagement/schemeManagement/addPlan.vue @@ -2,70 +2,90 @@ <Card> <template style="position: relative"> <el-form ref="form" :model="form" :rules="rules" inline label-position="top"> - <div class="header-title" style="margin-bottom: 38px; justify-content: space-between"> - <div style="display: flex; align-items: center; gap: 13px"> + <div v-if="!isEdit"> + <div class="header-title" style="margin-bottom: 38px"> + <div style="display: flex; align-items: center; gap: 13px"> + <div class="header-title-left"> + <img src="@/assets/public/headercard.png" /> + <div>所属实验调度</div> + </div> + <el-button @click="showScheduling = true" class="el-icon-plus" type="primary"> + 选择实验调度</el-button> + </div> + </div> + <!-- //换到详情弹窗 --> + <!-- <el-button @click="handleStopExperiment" type="danger"> + 申请终止实验</el-button> --> + <Table :data="groupTableData" :total="0" :height="null" class="groupTable"> + <el-table-column type="index" label="序号" width="80"></el-table-column> + <el-table-column prop="projectName" label="所属项目课题方案"></el-table-column> + <el-table-column prop="experimentCode" label="实验编号"></el-table-column> + <el-table-column prop="experimentName" label="实验名称"></el-table-column> + <el-table-column prop="experimentDate" label="通知时间"></el-table-column> + <el-table-column prop="experimentStartTime" label="实验开始时间"></el-table-column> + <el-table-column prop="experimentEndTime" label="实验结束时间"></el-table-column> + <el-table-column prop="participantsName" label="参加人员"></el-table-column> + <el-table-column prop="status" label="状态"> + <template slot-scope="scope"> + <el-tag :type="getStatusType(scope.row.status)"> + {{ getStatusText(scope.row.status) }} + </el-tag> + </template> + </el-table-column> + </Table> + + <div class="header-title" style="margin-bottom: 38px"> <div class="header-title-left"> <img src="@/assets/public/headercard.png" /> - <div>所属实验调度</div> + <span>基础信息</span> </div> - <el-button @click="showScheduling = true" class="el-icon-plus" type="primary"> - 选择实验调度</el-button> </div> - <el-button @click="handleStopExperiment" type="danger"> - 申请终止实验</el-button> - </div> - <Table :data="groupTableData" :total="0" :height="null" class="groupTable"> - <el-table-column type="index" label="序号" width="80"></el-table-column> - <el-table-column prop="projectName" label="所属项目课题方案"></el-table-column> - <el-table-column prop="experimentCode" label="实验编号"></el-table-column> - <el-table-column prop="experimentName" label="实验名称"></el-table-column> - <el-table-column prop="experimentDate" label="通知时间"></el-table-column> - <el-table-column prop="experimentStartTime" label="实验开始时间"></el-table-column> - <el-table-column prop="experimentEndTime" label="实验结束时间"></el-table-column> - <el-table-column prop="participantsName" label="参加人员"></el-table-column> - <el-table-column prop="status" label="状态"> - <template slot-scope="scope"> - <el-tag :type="getStatusType(scope.row.status)"> - {{ getStatusText(scope.row.status) }} - </el-tag> - </template> - </el-table-column> - </Table> - <div class="header-title" style="margin-bottom: 38px"> - <div class="header-title-left"> - <img src="@/assets/public/headercard.png" /> - <span>基础信息</span> + <template v-if="groupData && groupData.length > 0"> + <div class="add-group"> + <span>组别列表</span> + </div> + + <Table :data="groupData" :total="0" :height="null" class="groupTable"> + <el-table-column type="index" label="序号" width="80"></el-table-column> + <el-table-column prop="groupName" label="组别"></el-table-column> + <el-table-column prop="remark" label="备注"></el-table-column> + </Table> + </template> + + <div style="padding-left: 25px;margin-top: 28px;"> + <el-form-item prop="experimentDate" label="试验日期"> + + <el-date-picker v-model="form.experimentDate" type="datetime" :disabled="isEdit" placeholder="选择日期时间"> + </el-date-picker> + </el-form-item> + </div> + </div> + <div v-else> + <div class="header-title" style="margin-bottom: 18px"> + <div class="header-title-left"> + <img src="@/assets/public/headercard.png" /> + <div>所属项目课题方案</div> + </div> + </div> + <div class="content-box"> + <div class="content-box-left"> + <div>项目课题方案名称:{{ groupTableData && groupTableData.length > 0 ? groupTableData[0].projectName : '' }}</div> + <div>实验编号:{{ groupTableData && groupTableData.length > 0 ? groupTableData[0].experimentCode : '' }}</div> + </div> + <div class="content-box-right"> + <div>项目课题方案编号: {{ groupTableData && groupTableData.length > 0 ? groupTableData[0].projectCode : '' }}</div> + <div>实验名称: {{ groupTableData && groupTableData.length > 0 ? groupTableData[0].experimentName : '' }}</div> + </div> </div> </div> - <div class="add-group"> - <span>组别列表</span> - <!-- <el-button type="primary" class="el-icon-plus" @click="handleAddGroup">添加组别</el-button> --> - </div> - - <Table :data="groupData" :total="0" :height="null" class="groupTable"> - <el-table-column type="index" label="序号" width="80"></el-table-column> - <el-table-column prop="groupName" label="组别"></el-table-column> - <el-table-column prop="remark" label="备注"></el-table-column> - </Table> - - <div style="padding-left: 25px;margin-top: 28px;"> - <el-form-item prop="experimentDate" label="试验日期"> - - <el-date-picker - v-model="form.experimentDate" - type="datetime" - placeholder="选择日期时间"> - </el-date-picker> - </el-form-item> - </div> - - <div class="add-group"> + <div class="add-group" v-if="!isEdit"> <div>*</div> <span>参加人员</span> - <el-button type="primary" class="el-icon-plus" @click="addMember">选择参加人员</el-button> + <el-button type="primary" class="el-icon-plus" @click="addMember" >选择参加人员</el-button> </div> + <div class="add-group" v-else><span>实验人员</span> </div> <div class="member-list"> <div class="member-list-card"> <div class="member-item"> @@ -78,11 +98,23 @@ </div> </div> <div class="member-change"> - <div class="member-change-btn" @click="handleEditMember">修改</div> + <div class="member-change-btn" @click="handleEditMember" v-if="!isEdit">修改</div> </div> </div> </div> </div> + + <template v-if="groupData && groupData.length > 0 && isEdit"> + <div class="add-group"> + <span>组别列表</span> + </div> + + <Table :data="groupData" :total="0" :height="null" class="groupTable"> + <el-table-column type="index" label="序号" width="80"></el-table-column> + <el-table-column prop="groupName" label="组别"></el-table-column> + <el-table-column prop="remark" label="备注"></el-table-column> + </Table> + </template> <div class="header-title" style="margin-bottom: 38px"> <div class="header-title-left"> @@ -91,7 +123,8 @@ </div> </div> <div class="content-box"> - <AiEditor ref="purposeEditor" :value="editorContents.purpose" height="200px" placeholder="请输入实验目的..." /> + <AiEditor ref="purposeEditor" :readOnly="isEdit" :value="editorContents.purpose" height="200px" + placeholder="请输入实验目的..." /> </div> <div class="header-title" style="margin-bottom: 38px"> @@ -101,7 +134,8 @@ </div> </div> <div class="content-box"> - <AiEditor ref="processEditor" :value="editorContents.process" height="200px" placeholder="请输入工艺参数及路线..." /> + <AiEditor ref="processEditor" :readOnly="isEdit" :value="editorContents.process" height="200px" + placeholder="请输入工艺参数及路线..." /> </div> <div class="header-title" style="margin-bottom: 38px"> @@ -110,15 +144,17 @@ <div>三、实验材料及设备</div> </div> </div> - <DynamicComponent ref="materialComponent" title="实验材料" :participants="participantsData" @submit="handleMaterialSubmit" /> - <DynamicComponent ref="equipmentComponent" title="实验所用设备" :participants="participantsData" @submit="handleEquipmentSubmit" /> + <DynamicComponent ref="materialComponent" title="实验材料" :participants="participantsData" + @submit="handleMaterialSubmit" :dataSource="form.experimentMaterial" :editable="!isEdit" /> + <DynamicComponent ref="equipmentComponent" title="实验所用设备" :participants="participantsData" + @submit="handleEquipmentSubmit" :dataSource="form.experimentDevice" :editable="!isEdit" /> <div class="header-title" style="margin-bottom: 38px"> <div class="header-title-left"> <img src="@/assets/public/headercard.png" /> <div>四、实验操作步骤记录</div> </div> - <el-button @click="handleAddStep" class="el-icon-plus" type="primary"> + <el-button @click="handleAddStep" class="el-icon-plus" type="primary" v-if="!isEdit"> 添加步骤</el-button> </div> @@ -128,17 +164,18 @@ 步骤{{ idx + 1 }}:{{ item.stepName }} </div> <div class="step-list-item-control"> - <div class="controlBtn edit" @click="handleEditStep(idx)"> + <div class="controlBtn edit" @click="handleEditStep(idx)" v-if="!isEdit"> <img src="@/assets/public/edit.png" alt="编辑" class="edit-icon" /> 编辑 </div> - <div class="controlBtn delete" @click="handleDeleteStep(idx)"> + <div class="controlBtn delete" @click="handleDeleteStep(idx)" v-if="!isEdit"> <img src="@/assets/public/delete.png" alt="删除" class="delete-icon" /> 删除 </div> </div> </div> - <DynamicComponent :ref="'stepContent' + idx" @submit="(content) => handleStepContentSubmit(idx, content)" /> + <DynamicComponent :ref="'stepContent' + idx" @submit="(content) => handleStepContentSubmit(idx, content)" + :dataSource="item.content" :editable="!isEdit" /> </div> <div class="add-project-footer"> @@ -159,9 +196,9 @@ import DynamicComponent from "@/components/DynamicComponent"; import AddStep from "./components/add-step.vue"; import AiEditor from "@/components/AiEditor"; -import { getGroupByDispatchId,getParticipantsByDispatchId } from "./service"; +import { getGroupByDispatchId, getParticipantsByDispatchId, getDetail } from "./service"; import moment from 'moment'; -import { add } from "./service"; +import { add,update } from "./service"; export default { name: "AddProject", @@ -209,267 +246,67 @@ taskTableData: [], participantsData: [], selectedParticipants: [], + isEdit: false, // 是否为编辑模式 + editId: null, // 编辑的ID + viewMaterialData: [], // 查看模式的材料数据 + viewEquipmentData: [], // 查看模式的设备数据 + // 状态映射表 + statusTypeMap: { + "-1": "info", + "1": "warning", + "2": "success", + "3": "info" + }, + statusTextMap: { + "-1": "草稿箱", + "1": "待确认", + "2": "已确认", + "3": "已封存" + } }; }, + async created() { + // 检查是否为编辑模式 + if (this.$route.query.type === 'edit' && this.$route.query.id) { + this.isEdit = true; + this.editId = this.$route.query.id; + await this.loadEditData(); + } + }, methods: { - confirmAddRow() { - // 处理添加行的逻辑 - console.log('添加行'); - }, - submitForm() { - this.$refs.form.validate((valid) => { - if (valid) { - console.log("submit!"); - } - }); - }, + // ===== 人员相关方法 ===== addMember() { this.$refs.selectMember.open(this.participantsData, []); }, - memberList(i) { - const roleTypes = { - 1: '工艺工程师', - 2: '化验师', - 3: '实验员' - }; - - return this.selectedParticipants.filter(member => member.roleType === i) || []; + handleMemberSubmit(selectedMembers) { + this.selectedParticipants = selectedMembers; + this.$refs.selectMember.close(); }, - handleAddGroup() { - this.$refs.addGroupDialog.open(); + handleEditMember() { + this.$refs.selectMember.open(this.participantsData, this.selectedParticipants); }, - handleEditGroup(row) { - this.$refs.addGroupDialog.open(row); - }, - handleDeleteGroup(row) { - this.$confirm("确认删除该组别吗?", "提示", { - confirmButtonText: "确定", - cancelButtonText: "取消", - type: "warning", - }) - .then(() => { - const index = this.groupTableData.findIndex((item) => item === row); - if (index > -1) { - this.groupTableData.splice(index, 1); - this.$message.success("删除成功"); - } - }) - .catch(() => { }); - }, - handleGroupSubmit(form) { - const index = this.groupTableData.findIndex( - (item) => item.groupName === form.groupName - ); - if (index > -1) { - this.groupTableData.splice(index, 1, form); - } else { - this.groupTableData.push(form); - } - }, - handleAddTask() { - this.$refs.addTaskDialog.open(); - }, - handleEditTask(row) { - this.$refs.addTaskDialog.open(row); - }, - handleDeleteTask(row) { - this.$confirm("确认删除该任务吗?", "提示", { - confirmButtonText: "确定", - cancelButtonText: "取消", - type: "warning", - }) - .then(() => { - const index = this.taskTableData.findIndex((item) => item === row); - if (index > -1) { - this.taskTableData.splice(index, 1); - this.$message.success("删除成功"); - } - }) - .catch(() => { }); - }, - handleTaskSubmit(form) { - const index = this.taskTableData.findIndex( - (item) => item.taskName === form.taskName - ); - if (index > -1) { - this.taskTableData.splice(index, 1, form); - } else { - this.taskTableData.push(form); - } - }, - handleMaterialSubmit(data) { - // 处理实验材料数据 - this.form.experimentMaterial = data; - }, - handleEquipmentSubmit(data) { - // 处理实验设备数据 - this.form.experimentDevice = data; - }, - handleSave() { - // 先获取所有动态组件的数据 - this.$refs.materialComponent.submit(); - this.$refs.equipmentComponent.submit(); - - // 获取所有步骤内容 - 添加错误检查 - this.stepList.forEach((step, index) => { - const stepContentRef = this.$refs['stepContent' + index]; - console.log('stepContentRef',stepContentRef) - const editor = Array.isArray(stepContentRef) ? stepContentRef[0] : stepContentRef; - if (editor && typeof editor.submit === 'function') { - editor.submit(); - } - }); - console.log('材料数据',this.form.experimentMaterial) - console.log('设备数据',this.form.experimentDevice) - console.log('步骤数据',this.form.stepList) - - // 然后进行表单校验 - this.$refs.form.validate((valid) => { - if (valid && this.validateContent()) { - // 构建提交数据 - const formData = { - ...this.form, - // 实验日期,使用 moment 格式化为指定格式 - experimentDate: moment(this.form.experimentDate).format('YYYY-MM-DD HH:mm:ss'), - // 实验调度ID,从选中的调度数据中获取 - dispatchId: this.groupTableData[0]?.id, - // 实验设备,转换为JSON字符串 - experimentDevice: this.form.experimentDevice, - // 实验材料,转换为JSON字符串 - experimentMaterial: this.form.experimentMaterial, - // 实验目的,从富文本编辑器获取内容 - experimentObjective: this.$refs.purposeEditor.getContent(), - // 工艺参数及路线,从富文本编辑器获取内容 - experimentParamRoute: this.$refs.processEditor.getContent(), - // 实验方案人员,包含用户ID、昵称和角色类型 - experimentSchemePersons: this.selectedParticipants.map(person => ({ - userId: person.userId, // 用户ID - nickName: person.nickName, // 用户昵称 - roleType: person.roleType, // 角色类型 - commitTime: moment().format('YYYY-MM-DD HH:mm:ss'), - })), - // 实验步骤记录,转换为JSON字符串,包含步骤名称和内容 - experimentStepRecord: this.stepList.map(step => ({ - stepName: step.stepName, // 步骤名称 - content: step.content // 步骤内容 - })), - // 状态:1=已发送 - status: 1, - // 提交时间,使用 moment 格式化为指定格式 - commitTime: moment().format('YYYY-MM-DD HH:mm:ss'), - }; - console.log('formData 提交数据',formData) - - // 调用添加接口 - add(formData).then(res => { - if (res.code === 200) { - this.$message.success('保存成功'); - this.$router.go(-1) - // 可以在这里添加跳转逻辑 - } else { - this.$message.error(res.msg || '保存失败'); - } - }).catch(err => { - this.$message.error('保存失败'); - console.error('保存失败:', err); - }); - } else { - // 获取第一个错误字段 - const firstError = this.$refs.form.fields.find(field => field.validateState === 'error'); - if (firstError) { - // 滚动到错误字段 - this.$nextTick(() => { - firstError.$el.scrollIntoView({ behavior: 'smooth', block: 'center' }); - }); - } - } - }); - }, - handleSaveDraft() { - // 先获取所有动态组件的数据 - this.$refs.materialComponent.submit(); - this.$refs.equipmentComponent.submit(); - - // 获取所有步骤内容 - 添加错误检查 - this.stepList.forEach((step, index) => { - const stepContentRef = this.$refs['stepContent' + index]; - if (stepContentRef && typeof stepContentRef.submit === 'function') { - stepContentRef.submit(); - } - }); - - // 然后进行表单校验 - this.$refs.form.validate((valid) => { - if (valid && this.validateContent()) { - // 构建草稿数据 - const formData = { - ...this.form, - // 实验日期,使用 moment 格式化为指定格式 - experimentDate: moment(this.form.experimentDate).format('YYYY-MM-DD HH:mm:ss'), - // 实验调度ID,从选中的调度数据中获取 - dispatchId: this.groupTableData[0]?.id, - // 实验设备,转换为JSON字符串 - experimentDevice: JSON.stringify(this.form.experimentDevice), - // 实验材料,转换为JSON字符串 - experimentMaterial: JSON.stringify(this.form.experimentMaterial), - // 实验目的,从富文本编辑器获取内容 - experimentObjective: this.$refs.purposeEditor.getContent(), - // 工艺参数及路线,从富文本编辑器获取内容 - experimentParamRoute: this.$refs.processEditor.getContent(), - // 实验方案人员,包含用户ID、昵称和角色类型 - experimentSchemePersons: this.selectedParticipants.map(person => ({ - userId: person.userId, // 用户ID - nickName: person.nickName, // 用户昵称 - roleType: person.roleType // 角色类型 - })), - // 实验步骤记录,转换为JSON字符串,包含步骤名称和内容 - experimentStepRecord: JSON.stringify(this.stepList.map(step => ({ - stepName: step.stepName, // 步骤名称 - content: step.content // 步骤内容 - }))), - // 状态:-1=草稿箱 - status: -1, - // 提交时间,使用 moment 格式化为指定格式 - commitTime: moment().format('YYYY-MM-DD HH:mm:ss'), - }; - - // 调用添加接口 - add(formData).then(res => { - if (res.code === 200) { - this.$message.success('草稿保存成功'); - // 可以在这里添加跳转逻辑 - } else { - this.$message.error(res.msg || '草稿保存失败'); - } - }).catch(err => { - this.$message.error('草稿保存失败'); - console.error('草稿保存失败:', err); - }); - } else { - // 获取第一个错误字段 - const firstError = this.$refs.form.fields.find(field => field.validateState === 'error'); - if (firstError) { - // 滚动到错误字段 - this.$nextTick(() => { - firstError.$el.scrollIntoView({ behavior: 'smooth', block: 'center' }); - }); - } - } - }); - }, + + // ===== 步骤相关方法 ===== handleAddStep() { this.$refs.addStepDialog.open(); }, handleStepSubmit(stepData) { if (this.editingStepIndex > -1) { + // 编辑现有步骤 this.stepList[this.editingStepIndex].stepName = stepData.stepName; this.editingStepIndex = -1; } else { + // 添加新步骤 this.stepList.push({ stepName: stepData.stepName, content: null, }); } + }, + handleEditStep(index) { + this.editingStepIndex = index; + this.$refs.addStepDialog.open(true); + this.$refs.addStepDialog.setStepName(this.stepList[index].stepName); }, handleDeleteStep(index) { this.$confirm("确认删除该步骤吗?删除后步骤内容将无法恢复", "警告", { @@ -484,14 +321,108 @@ }) .catch(() => { }); }, - handleEditStep(index) { - this.editingStepIndex = index; - this.$refs.addStepDialog.open(true); - this.$refs.addStepDialog.setStepName(this.stepList[index].stepName); - }, handleStepContentSubmit(index, content) { - console.log('步骤内容',content) this.stepList[index].content = content; + }, + + // ===== 材料设备相关方法 ===== + handleMaterialSubmit(data) { + this.form.experimentMaterial = data; + }, + handleEquipmentSubmit(data) { + this.form.experimentDevice = data; + }, + + // ===== 保存提交相关方法 ===== + handleSave() { + this.submitData(1); + }, + handleSaveDraft() { + this.submitData(-1); + }, + submitData(status) { + // 先获取所有动态组件的数据 + this.$refs.materialComponent.submit(); + this.$refs.equipmentComponent.submit(); + + // 获取所有步骤内容 + const stepContentRefs = Object.keys(this.$refs) + .filter(key => key.startsWith('stepContent')) + .map(key => this.$refs[key]); + + stepContentRefs.forEach((ref) => { + const editor = Array.isArray(ref) ? ref[0] : ref; + if (editor && typeof editor.submit === 'function') { + editor.submit(); + } + }); + + // 进行表单校验 + this.$refs.form.validate((valid) => { + if (valid && this.validateContent()) { + // 获取富文本编辑器内容 + const experimentObjective = this.$refs.purposeEditor.getContent(); + const experimentParamRoute = this.$refs.processEditor.getContent(); + + // 构建实验步骤记录数据 + const experimentStepRecord = this.stepList.map(step => ({ + stepName: step.stepName, + content: step.content + })); + + // 构建提交数据 + const formData = { + ...this.form, + experimentDate: moment(this.form.experimentDate).format('YYYY-MM-DD HH:mm:ss'), + dispatchId: this.groupTableData && this.groupTableData.length > 0 ? this.groupTableData[0]?.id : '', + experimentObjective, + experimentParamRoute, + experimentSchemePersons: this.selectedParticipants.map(person => ({ + userId: person.userId, + nickName: person.nickName, + roleType: person.roleType, + commitTime: moment().format('YYYY-MM-DD HH:mm:ss'), + })), + status, + commitTime: moment().format('YYYY-MM-DD HH:mm:ss'), + }; + + // 统一转换JSON字符串 + formData.experimentStepRecord = JSON.stringify(experimentStepRecord); + formData.experimentDevice = JSON.stringify(this.form.experimentDevice); + formData.experimentMaterial = JSON.stringify(this.form.experimentMaterial); + + // 编辑模式下添加id参数 + if (this.isEdit && this.editId) { + formData.id = this.editId; + } + + // 根据是否为编辑模式调用不同接口 + const apiCall = this.isEdit ? update(formData) : add(formData); + + apiCall.then(res => { + if (res.code === 200) { + this.$message.success(status === 1 ? '保存成功' : '草稿保存成功'); + if (status === 1) { + this.$router.go(-1); + } + } else { + this.$message.error(res.msg || (status === 1 ? '保存失败' : '草稿保存失败')); + } + }).catch(err => { + this.$message.error(status === 1 ? '保存失败' : '草稿保存失败'); + console.error(status === 1 ? '保存失败:' : '草稿保存失败:', err); + }); + } else { + // 获取第一个错误字段并滚动到该位置 + const firstError = this.$refs.form.fields.find(field => field.validateState === 'error'); + if (firstError) { + this.$nextTick(() => { + firstError.$el.scrollIntoView({ behavior: 'smooth', block: 'center' }); + }); + } + } + }); }, getAllEditorContent() { return { @@ -500,37 +431,37 @@ }; }, validateContent() { - // // 校验实验调度 - // if (!this.groupTableData || this.groupTableData.length === 0) { - // this.$message.error("请选择实验调度"); - // return false; - // } + // 校验实验调度 + if (!this.groupTableData || this.groupTableData.length === 0) { + this.$message.error("请选择实验调度"); + return false; + } - // // 校验实验日期 - // if (!this.form.experimentDate) { - // this.$message.error("请填写实验日期"); - // return false; - // } + // 校验实验日期 + if (!this.form.experimentDate) { + this.$message.error("请填写实验日期"); + return false; + } - // // 校验参与人员 - // if (!this.selectedParticipants || this.selectedParticipants.length === 0) { - // this.$message.error("请选择参与人员"); - // return false; - // } + // 校验参与人员 + if (!this.selectedParticipants || this.selectedParticipants.length === 0) { + this.$message.error("请选择参与人员"); + return false; + } - // // 校验实验目的 - // const purpose = this.$refs.purposeEditor.getContent(); - // if (!purpose || purpose === '<p></p>' || purpose.trim() === '<p></p>') { - // this.$message.error("请填写实验目的"); - // return false; - // } + // 校验实验目的 + const purpose = this.$refs.purposeEditor.getContent(); + if (!purpose || purpose === '<p></p>' || purpose.trim() === '<p></p>') { + this.$message.error("请填写实验目的"); + return false; + } - // // 校验工艺参数及路线 - // const process = this.$refs.processEditor.getContent(); - // if (!process || process === '<p></p>' || process.trim() === '<p></p>') { - // this.$message.error("请填写工艺参数及路线"); - // return false; - // } + // 校验工艺参数及路线 + const process = this.$refs.processEditor.getContent(); + if (!process || process === '<p></p>' || process.trim() === '<p></p>') { + this.$message.error("请填写工艺参数及路线"); + return false; + } // 校验实验材料 if (!this.form.experimentMaterial) { @@ -551,11 +482,10 @@ } // 校验每个步骤是否都有内容 - for (let i = 0; i < this.stepList.length; i++) { - if (!this.stepList[i].content) { - this.$message.error(`请完善第${i + 1}个步骤的内容`); - return false; - } + const invalidStep = this.stepList.findIndex(step => !step.content); + if (invalidStep !== -1) { + this.$message.error(`请完善第${invalidStep + 1}个步骤的内容`); + return false; } return true; @@ -564,26 +494,14 @@ this.$router.push("/dataManagement/scheme-management/stop-experiment"); }, getStatusType(status) { - const statusMap = { - "-1": "info", - "1": "warning", - "2": "success", - "3": "info" - }; - return statusMap[status] || "info"; + return this.statusTypeMap[status] || "info"; }, getStatusText(status) { - const statusMap = { - "-1": "草稿箱", - "1": "待确认", - "2": "已确认", - "3": "已封存" - }; - return statusMap[status] || "未知"; + return this.statusTextMap[status] || "未知"; }, handleSchedulingSubmit(data) { - this.groupTableData = data; - if (data && data.length > 0) { + this.groupTableData = data || []; + if (data && data.length > 0 && data[0].id) { getGroupByDispatchId({ dispatchId: data[0].id }).then(res => { if (res) { this.groupData = res || []; @@ -610,12 +528,124 @@ handleSchedulingClose() { this.showScheduling = false; }, - handleMemberSubmit(selectedMembers) { - this.selectedParticipants = selectedMembers; - this.$refs.selectMember.close(); + // ===== 数据加载方法 ===== + async loadEditData() { + try { + const res = await getDetail({ id: this.editId }); + if (!res) { + this.$message.error('获取详情失败'); + return; + } + + console.log('编辑数据', res); + const data = res; + + // 填充基本表单数据 + this.form.experimentDate = data.experimentDate; + + // 填充实验调度信息 + if (data.experimentDispatch?.id) { + this.form.dispatchId = data.experimentDispatch.id; + this.groupTableData = [{ ...data.experimentDispatch }]; + + // 获取组别信息 + try { + const groupRes = await getGroupByDispatchId({ dispatchId: data.experimentDispatch.id }); + this.groupData = groupRes || []; + } catch (err) { + console.error('获取组别列表失败:', err); + } + } + + // 填充参与人员 + this.selectedParticipants = Array.isArray(data.experimentSchemePersons) + ? data.experimentSchemePersons + : JSON.parse(data.experimentSchemePersons || '[]'); + + // 填充富文本编辑器内容 + this.editorContents.purpose = data.experimentObjective || ''; + this.editorContents.process = data.experimentParamRoute || ''; + + // 填充实验材料和设备 + try { + this.form.experimentMaterial = typeof data.experimentMaterial === 'string' + ? JSON.parse(data.experimentMaterial) + : data.experimentMaterial; + } catch (err) { + console.error('解析实验材料数据失败:', err); + this.form.experimentMaterial = []; + } + + try { + this.form.experimentDevice = typeof data.experimentDevice === 'string' + ? JSON.parse(data.experimentDevice) + : data.experimentDevice; + } catch (err) { + console.error('解析实验设备数据失败:', err); + this.form.experimentDevice = []; + } + + // 填充实验步骤 + try { + const stepsData = typeof data.experimentStepRecord === 'string' + ? JSON.parse(data.experimentStepRecord) + : data.experimentStepRecord; + + this.stepList = (stepsData || []).map(step => ({ + stepName: step.stepName, + content: step.content + })); + } catch (err) { + console.error('解析实验步骤数据失败:', err); + this.stepList = []; + } + + // 等待组件渲染完成后设置编辑器内容 + this.$nextTick(() => { + // 设置富文本编辑器内容 + if (this.$refs.purposeEditor) { + this.$refs.purposeEditor.setContent(this.editorContents.purpose); + } + if (this.$refs.processEditor) { + this.$refs.processEditor.setContent(this.editorContents.process); + } + + // 设置动态组件的初始数据 + if (!this.isEdit) { + if (this.$refs.materialComponent && this.form.experimentMaterial) { + this.$refs.materialComponent.setInitialData(this.form.experimentMaterial); + } + if (this.$refs.equipmentComponent && this.form.experimentDevice) { + this.$refs.equipmentComponent.setInitialData(this.form.experimentDevice); + } + + // 设置步骤内容的初始数据 + this.stepList.forEach((step, index) => { + const stepContentRef = this.$refs['stepContent' + index]; + if (stepContentRef && step.content) { + const editor = Array.isArray(stepContentRef) ? stepContentRef[0] : stepContentRef; + if (editor?.setInitialData) { + editor.setInitialData(step.content); + } + } + }); + } + }); + + } catch (error) { + this.$message.error('获取详情失败'); + console.error('获取详情失败:', error); + } }, - handleEditMember() { - this.$refs.selectMember.open(this.participantsData, this.selectedParticipants); + // 转换数据格式为ViewDynamicComponent需要的格式 + convertToViewFormat(data) { + if (!data || !Array.isArray(data)) return []; + + return data.map(item => ({ + id: item.id || Math.random().toString(36).substr(2, 9), + type: item.type, + data: item.data + })); }, }, }; @@ -926,7 +956,20 @@ .content-box { padding: 0 25px; - margin-bottom: 30px; + margin-bottom: 20px; width: 65%; + display: flex; + .content-box-left{ + flex: 1; + div{ + padding: 10px 0; + } + } + .content-box-right{ + flex: 1; + div{ + padding: 10px 0; + } + } } </style> \ No newline at end of file diff --git a/laboratory/src/views/dataManagement/schemeManagement/components/approvalDialog.vue b/laboratory/src/views/dataManagement/schemeManagement/components/approvalDialog.vue index d31d6ae..d3a3873 100644 --- a/laboratory/src/views/dataManagement/schemeManagement/components/approvalDialog.vue +++ b/laboratory/src/views/dataManagement/schemeManagement/components/approvalDialog.vue @@ -18,25 +18,34 @@ :rules="rules" inline label-position="top" - :disabled="type === 'view'" + > <div class="header-title" style="margin-bottom: 38px"> - <div class="header-title-left"> - <img src="@/assets/public/headercard.png" /> - <div>所属实验调度</div> + <div style="display: flex; align-items: center; gap: 13px"> + <div class="header-title-left"> + <img src="@/assets/public/headercard.png" /> + <div>所属实验调度</div> + </div> + <el-button @click="handleStopExperiment" type="danger"> + 申请终止实验</el-button> </div> </div> - <Table :data="groupTableData" :total="0" :height="null"> - <el-table-column - type="index" - label="序号" - width="80" - ></el-table-column> - <el-table-column - prop="groupName" - label="组别" - ></el-table-column> - <el-table-column prop="remark" label="备注"></el-table-column> + <Table :data="dispatchData" :total="0" :height="null" class="groupTable"> + <el-table-column type="index" label="序号" width="80"></el-table-column> + <el-table-column prop="projectName" label="所属项目课题方案"></el-table-column> + <el-table-column prop="experimentCode" label="实验编号"></el-table-column> + <el-table-column prop="experimentName" label="实验名称"></el-table-column> + <el-table-column prop="experimentDate" label="通知时间"></el-table-column> + <el-table-column prop="experimentStartTime" label="实验开始时间"></el-table-column> + <el-table-column prop="experimentEndTime" label="实验结束时间"></el-table-column> + <el-table-column prop="participantsName" label="参加人员"></el-table-column> + <el-table-column prop="status" label="状态"> + <template slot-scope="scope"> + <el-tag :type="getStatusType(scope.row.status)"> + {{ getStatusText(scope.row.status) }} + </el-tag> + </template> + </el-table-column> </Table> <div class="header-title" style="margin-bottom: 38px"> @@ -46,72 +55,37 @@ </div> </div> - <div class="add-group"> - <span>组别列表</span> - </div> - <Table - :data="groupTableData" - :total="0" - :height="null" - class="groupTable" - > - <el-table-column - type="index" - label="序号" - width="80" - ></el-table-column> - <el-table-column - prop="groupName" - label="组别" - ></el-table-column> - <el-table-column prop="remark" label="备注"></el-table-column> - </Table> + <template v-if="groupData && groupData.length > 0"> + <div class="add-group"> + <span>组别列表</span> + </div> + <Table :data="groupData" :total="0" :height="null" class="groupTable"> + <el-table-column type="index" label="序号" width="80"></el-table-column> + <el-table-column prop="groupName" label="组别"></el-table-column> + <el-table-column prop="remark" label="备注"></el-table-column> + </Table> + </template> - <div style="padding-left: 25px; margin-top: 20px"> - <el-form-item prop="testTime" label="试验时间"> - <el-date-picker - v-model="form.testTime" - type="datetime" - placeholder="选择日期时间" - value-format="yyyy-MM-dd HH:mm:ss" - /> + <div style="padding-left: 25px;margin-top: 28px;"> + <el-form-item prop="experimentDate" label="试验日期"> + <el-date-picker v-model="form.experimentDate" type="datetime" :disabled="true" placeholder="选择日期时间"> + </el-date-picker> </el-form-item> </div> + <div class="add-group"> - <div>*</div> <span>实验人员</span> </div> <div class="member-list"> - <div v-for="item in 3" :key="item" class="member-list-card"> + <div class="member-list-card"> <div class="member-item"> - <div class="member-title"> - {{ ["工艺工程师", "实验员", "化验师"][item - 1] }} - </div> - <div - :class=" - item == 1 || item == 2 || item == 3 - ? 'member-name-box' - : 'flex1' - " - > - <div - :class=" - item == 1 || item == 2 || item == 3 - ? 'member-name-box' - : 'member-name-box-2' - " - > - <div - v-for="i in memberList(item)" - :key="i" - class="member-name" - > - 张三 + <div class="member-title">实验员</div> + <div class="flex"> + <div class="member-name-box-2"> + <div v-for="i in selectedParticipants" :key="i.id" class="member-name"> + {{ i.nickName }} </div> </div> - </div> - <div class="member-change" v-if="type !== 'view'"> - <div class="member-change-btn">修改</div> </div> </div> </div> @@ -123,12 +97,15 @@ <div>一、实验目的</div> </div> </div> - <AiEditor - ref="purposeEditor" - v-model="form.purpose" - height="200px" - placeholder="请输入实验目的..." - /> + <div class="content-box"> + <AiEditor + ref="purposeEditor" + :readOnly="true" + :value="form.experimentObjective" + height="200px" + placeholder="请输入实验目的..." + /> + </div> <div class="header-title" style="margin-bottom: 38px"> <div class="header-title-left"> @@ -136,12 +113,15 @@ <div>二、工艺参数及路线</div> </div> </div> - <AiEditor - ref="processEditor" - v-model="form.process" - height="200px" - placeholder="请输入工艺参数及路线..." - /> + <div class="content-box"> + <AiEditor + ref="processEditor" + :readOnly="true" + :value="form.experimentParamRoute" + height="200px" + placeholder="请输入工艺参数及路线..." + /> + </div> <div class="header-title" style="margin-bottom: 38px"> <div class="header-title-left"> @@ -149,13 +129,19 @@ <div>三、实验材料及设备</div> </div> </div> - <ViewDynamicComponent + <DynamicComponent + ref="materialComponent" title="实验材料" - :components="form.materialsAndEquipment || []" + :dialogCanEdit="false" + :dataSource="form.experimentMaterial" + :editable="false" /> - <ViewDynamicComponent + <DynamicComponent + ref="equipmentComponent" title="实验所用设备" - :components="form.materialsAndEquipment || []" + :dialogCanEdit="false" + :dataSource="form.experimentDevice" + :editable="false" /> <div class="header-title" style="margin-bottom: 38px"> @@ -165,15 +151,17 @@ </div> </div> - <div class="step-list" v-for="(item, idx) in form.operationSteps" :key="idx"> + <div class="step-list" v-for="(item, idx) in stepList" :key="idx"> <div class="step-list-item"> <div class="step-list-item-title"> 步骤{{ idx + 1 }}:{{ item.stepName }} </div> </div> - <ViewDynamicComponent + <DynamicComponent + :dialogCanEdit="false" :ref="'stepContent' + idx" - :components="[item]" + :dataSource="item.content" + :editable="false" /> </div> </el-form> @@ -181,13 +169,10 @@ </Card> </div> <!-- 右侧审批流程 --> - <div class="approval-flow" v-if="type === 'view'"> + <div class="approval-flow" v-if="showApprovalFlow"> <div class="flow-content"> <approval-process - :status="form.status" - :submit-time="form.createTime" - :approver="form.approver" - :approve-time="form.approveTime" + :processData="approvalProcessData" /> </div> </div> @@ -203,15 +188,16 @@ <script> import ApprovalProcess from "@/components/approvalProcess"; import SignatureCanvas from "@/components/SignatureCanvas.vue"; -import ViewDynamicComponent from "@/components/DynamicComponent/ViewDynamicComponent.vue"; -import AiEditor from "@/components/AiEditor/index.vue"; +import DynamicComponent from "@/components/DynamicComponent"; +import AiEditor from "@/components/AiEditor"; +import { getDetail, getGroupByDispatchId } from "../service"; export default { name: "ApprovalDialog", components: { ApprovalProcess, SignatureCanvas, - ViewDynamicComponent, + DynamicComponent, AiEditor, }, props: { @@ -231,187 +217,43 @@ data() { return { form: { - planName: "", - planCode: "", - stage: "", - testDate: "", - testName: "", - testCode: "", - testTime: "", - creator: "", - createTime: "", - approvalComment: "", - status: "approved", - approver: "", - approveTime: "", - materialsAndEquipment: [ - { - id: 1, - type: "richText", - data: { - content: - "<p>1. 实验材料说明</p><p>2. 设备使用说明</p><p>3. 安全注意事项</p>", - }, - }, - { - id: 2, - type: "customTable", - data: { - headers: [ - { name: "材料名称", type: "text" }, - { name: "规格", type: "text" }, - { name: "数量", type: "text" }, - { name: "用途", type: "text" }, - ], - rows: [ - { - 材料名称: "催化剂A", - 规格: "工业级", - 数量: "100g", - 用途: "反应催化剂", - updateTime: "2024-01-01 12:00:00", - }, - { - 材料名称: "溶剂B", - 规格: "分析纯", - 数量: "500ml", - 用途: "反应溶剂", - updateTime: "2024-01-01 12:00:00", - }, - ], - }, - }, - { - id: 3, - type: "fileUpload", - data: { - fileList: [ - { - name: "材料安全说明书.pdf", - url: "https://example.com/msds.pdf", - }, - { - name: "设备操作手册.docx", - url: "https://example.com/manual.docx", - }, - ], - }, - }, - { - id: 4, - type: "imageGallery", - data: { - images: [ - { - url: "https://example.com/equipment1.jpg", - title: "实验设备1", - description: "主要反应设备", - }, - { - url: "https://example.com/equipment2.jpg", - title: "实验设备2", - description: "辅助设备", - }, - ], - }, - }, - ], - operationSteps: [ - { - id: 7, - type: "richText", - data: { - content: - "<p>1. 准备工作</p><p>2. 设备检查</p><p>3. 实验操作</p><p>4. 数据记录</p>", - }, - }, - { - id: 8, - type: "customTable", - data: { - headers: [ - { name: "步骤", type: "text" }, - { name: "操作内容", type: "text" }, - { name: "操作人", type: "user" }, - { name: "操作图片", type: "image" }, - ], - rows: [ - { - 步骤: "步骤1", - 操作内容: "称取催化剂", - 操作人: ["1"], - 操作图片: [{ url: "https://example.com/step1.jpg" }], - updateTime: "2024-01-01 12:00:00", - }, - { - 步骤: "步骤2", - 操作内容: "加入溶剂", - 操作人: ["2"], - 操作图片: [{ url: "https://example.com/step2.jpg" }], - updateTime: "2024-01-01 12:00:00", - }, - ], - }, - }, - ], + projectName: "", // 项目课题方案名称 + projectCode: "", // 项目课题方案编号 + experimentCode: "", // 实验编号 + experimentName: "", // 实验名称 + experimentDate: "", // 实验日期 + experimentMaterial: [], // 实验材料 + experimentDevice: [], // 实验设备 + experimentObjective: "", // 实验目的 + experimentParamRoute: "", // 工艺参数及路线 + createBy: "", // 创建人 + createTime: "", // 创建时间 + status: "", // 状态 + approver: "", // 审批人 + approveTime: "", // 审批时间 }, rules: { - planName: [ - { - required: true, - message: "请输入项目课题方案名称", - trigger: "blur", - }, - ], - planCode: [ - { - required: true, - message: "请输入项目课题方案编号", - trigger: "blur", - }, - ], - stage: [{ required: true, message: "请输入项目阶段", trigger: "blur" }], - testDate: [ - { required: true, message: "请选择试验日期", trigger: "change" }, - ], - testName: [ - { required: true, message: "请输入实验名称", trigger: "blur" }, - ], - testCode: [ - { required: true, message: "请输入实验编号", trigger: "blur" }, - ], testTime: [ { required: true, message: "请选择试验时间", trigger: "change" }, ], }, imgSrc: "", signatureDialogVisible: false, - status: "1", remark: "", - groupTableData: [], - taskTableData: [], + groupData: [], + dispatchData: [], // 实验调度数据 + stepList: [], + selectedParticipants: [], // 实验参与人员 + showApprovalFlow: false, + approvalProcessData: [], }; - }, - computed: { - dialogTitle() { - return this.type === "approve" ? "确认实验调度" : "实验调度详情"; - }, }, watch: { data: { handler(val) { - if (val) { - // 深拷贝数据,避免直接修改props - this.form = JSON.parse( - JSON.stringify({ - ...this.form, - ...val, - // 确保这些字段存在,如果不存在则使用默认值 - materialsAndEquipment: val.materialsAndEquipment || [], - operationSteps: val.operationSteps || [], - }) - ); - console.log("接收到的数据:", this.form); + if (val && val.id) { + // 当接收到数据且有ID时,调用获取详情接口 + this.getPlanDetail(val.id); } }, immediate: true, @@ -419,28 +261,35 @@ }, visible: { handler(val) { - if (val && this.type === "view") { - // 当弹窗打开且是查看模式时,获取详情数据 - this.getPlanDetail(); + if (val && this.data && this.data.id) { + // 弹窗打开时,确保数据已获取 + this.getPlanDetail(this.data.id); } }, immediate: true, }, }, methods: { - memberList(i) { - switch (i) { - case 1: - return [1]; - case 2: - return [1]; - case 3: - return [1, 2, 3, 4, 5, 6, 7, 8]; - case 4: - return [1, 2, 3, 4, 5, 6, 7, 8]; - default: - break; - } + handleStopExperiment() { + this.$router.push("/dataManagement/scheme-management/stop-experiment?id=" + this.data.id); + }, + getStatusType(status) { + const statusMap = { + "-1": "info", + "1": "warning", + "2": "success", + "3": "info" + }; + return statusMap[status] || "info"; + }, + getStatusText(status) { + const statusMap = { + "-1": "草稿箱", + "1": "待确认", + "2": "已确认", + "3": "已封存" + }; + return statusMap[status] || "未知"; }, handleClose() { this.$emit("update:visible", false); @@ -466,9 +315,6 @@ status: "rejected", }); }, - memberList(item) { - return item === 1 ? 2 : item === 2 ? 3 : 1; - }, openSignature() { this.signatureDialogVisible = true; }, @@ -476,129 +322,190 @@ console.log("imageData imageData", imageData); this.signatureDialogVisible = false; this.imgSrc = imageData; - - // 这里处理签名确认后的逻辑 - // this.$confirm('确认该实验调度吗?', '提示', { - // confirmButtonText: '确定', - // cancelButtonText: '取消', - // type: 'warning' - // }).then(() => { - // // 这里可以将签名图片数据(imageData)连同其他数据一起提交到后端 - // this.$message.success('确认成功'); - // this.signatureDialogVisible = false; - // this.getTableData(); - // }).catch(() => { - // this.signatureDialogVisible = false; - // }); }, // 获取方案详情 - async getPlanDetail() { + async getPlanDetail(id) { try { - // TODO: 替换为实际的接口调用 - // const { data } = await this.$api.getPlanDetail({ planCode: this.data.planCode }); + const res = await getDetail({ id }); + if (!res) { + this.$message.error('获取方案详情失败'); + this.handleClose(); + return; + } + if(res.stopReason){ + this.showApprovalFlow = true; + //中止实验申请 + let processData = []; + processData.push({ + type: "primary", + mode: "list", + fields: [ + { label: "提交人:", value: res.updateBy || "" }, + { label: "提交时间:", value: res.createTime || "" }, + ], + }); + if(res.status==4||res.status==3){ + processData.push({ + type: + res.auditStatus === 2 + ? "primary" + : res.auditStatus === 3 + ? "danger" + : "warning", + mode: "list", + fields: [ + { + label: "审核结果:", + value: + res.auditStatus === 2 + ? "通过" + : res.auditStatus === 3 + ? "驳回" + : "待审批", + }, + { label: "审批意见:", value: res.auditRemark || "" }, + { label: "审核人:", value: res.auditPersonName || "" }, + { label: "审核时间:", value: res.auditTime || "" }, + ], + }); + }else{ + processData.push({ + type: "warning", + mode: "list", + fields: [ + { label: "等待审核"}, + ], + }); + } + this.approvalProcessData = processData; + } - // 模拟接口返回数据 - const mockDetailData = { - planCode: this.data.planCode, - planName: "2024年度实验室设备升级方案", - stage: "设备升级实验", - testDate: "2024-03-15", - testTime: "2024-03-15 14:00:00", - tester: "张三", - creator: "张三", - createTime: "2024-03-15", - status: "pending", - approver: "李四", - approveTime: "2024-03-16", - materialsAndEquipment: [ - { - id: 1, - type: "richText", - data: { - content: - "<p>1. 实验材料说明</p><p>2. 设备使用说明</p><p>3. 安全注意事项</p>", - }, - }, - { - id: 2, - type: "customTable", - data: { - headers: [ - { name: "材料名称", type: "text" }, - { name: "规格", type: "text" }, - { name: "数量", type: "text" }, - { name: "用途", type: "text" }, - ], - rows: [ - { - 材料名称: "催化剂A", - 规格: "工业级", - 数量: "100g", - 用途: "反应催化剂", - updateTime: "2024-01-01 12:00:00", - }, - { - 材料名称: "溶剂B", - 规格: "分析纯", - 数量: "500ml", - 用途: "反应溶剂", - updateTime: "2024-01-01 12:00:00", - }, - ], - }, - }, - { - id: 3, - type: "fileUpload", - data: { - fileList: [ - { - name: "材料安全说明书.pdf", - url: "https://example.com/msds.pdf", - }, - { - name: "设备操作手册.docx", - url: "https://example.com/manual.docx", - }, - ], - }, - }, - { - id: 4, - type: "imageUpload", - data: { - images: [ - { - url: "https://example.com/equipment1.jpg", - title: "实验设备1", - description: "主要反应设备", - }, - { - url: "https://example.com/equipment2.jpg", - title: "实验设备2", - description: "辅助设备", - }, - ], - }, - }, - ], - operationSteps: [ - { - id: 4, - type: "richText", - data: { - content: - "<p>1. 准备工作</p><p>2. 设备检查</p><p>3. 实验操作</p><p>4. 数据记录</p>", - }, - }, - ], - }; - - // 更新表单数据 + // 填充基本表单数据 this.form = { ...this.form, - ...mockDetailData, + projectName: res.projectName, + projectCode: res.projectCode, + experimentCode: res.experimentCode, + experimentName: res.experimentName, + experimentDate: res.experimentDate, + createBy: res.createBy, + createTime: res.createTime, + status: res.status, + experimentObjective: res.experimentObjective || '', + experimentParamRoute: res.experimentParamRoute || '', }; + + // 构建实验调度数据 + if (res.experimentDispatch) { + this.dispatchData = [res.experimentDispatch]; + } + + // 填充组别数据 + if (res.dispatchId) { + try { + const groupRes = await getGroupByDispatchId({ dispatchId: res.dispatchId }); + if (groupRes) { + this.groupData = groupRes || []; + } + } catch (err) { + console.error('获取组别列表失败:', err); + } + } + + // 填充实验材料和设备 + if (res.experimentMaterial) { + try { + const materialData = typeof res.experimentMaterial === 'string' + ? JSON.parse(res.experimentMaterial) + : res.experimentMaterial; + this.form.experimentMaterial = materialData; + + // 为DynamicComponent设置初始数据 + // this.$nextTick(() => { + // if (this.$refs.materialComponent) { + // this.$refs.materialComponent.setInitialData(materialData); + // } + // }); + } catch (err) { + console.error('解析实验材料数据失败:', err); + } + } + + if (res.experimentDevice) { + try { + const deviceData = typeof res.experimentDevice === 'string' + ? JSON.parse(res.experimentDevice) + : res.experimentDevice; + this.form.experimentDevice = deviceData; + + // 为DynamicComponent设置初始数据 + this.$nextTick(() => { + // if (this.$refs.equipmentComponent) { + // this.$refs.equipmentComponent.setInitialData(deviceData); + // } + }); + } catch (err) { + console.error('解析实验设备数据失败:', err); + } + } + + // 填充实验步骤 + if (res.experimentStepRecord) { + try { + const stepsData = typeof res.experimentStepRecord === 'string' + ? JSON.parse(res.experimentStepRecord) + : res.experimentStepRecord; + + this.stepList = (stepsData || []).map(step => ({ + stepName: step.stepName, + content: step.content + })); + + // 设置步骤内容的初始数据 + this.$nextTick(() => { + // this.stepList.forEach((step, index) => { + // const stepContentRef = this.$refs['stepContent' + index]; + // if (stepContentRef && step.content) { + // const editor = Array.isArray(stepContentRef) ? stepContentRef[0] : stepContentRef; + // if (editor && typeof editor.setInitialData === 'function') { + // editor.setInitialData(step.content); + // } + // } + // }); + }); + } catch (err) { + console.error('解析实验步骤数据失败:', err); + this.stepList = []; + } + } + + // 设置实验人员 + if (res.experimentSchemePersons) { + try { + const participantsData = typeof res.experimentSchemePersons === 'string' + ? JSON.parse(res.experimentSchemePersons) + : res.experimentSchemePersons; + + this.selectedParticipants = participantsData || []; + } catch (err) { + console.error('解析实验人员数据失败:', err); + this.selectedParticipants = []; + } + } + + + + + // 更新编辑器内容 + this.$nextTick(() => { + if (this.$refs.purposeEditor) { + this.$refs.purposeEditor.setContent(this.form.experimentObjective); + } + if (this.$refs.processEditor) { + this.$refs.processEditor.setContent(this.form.experimentParamRoute); + } + }); + } catch (error) { console.error("获取方案详情失败:", error); this.$message.error("获取方案详情失败"); @@ -614,24 +521,54 @@ border-bottom: 1px solid #e4e7ed; } +::v-deep .el-dialog__body { + padding: 20px; + max-height: 80vh; + overflow: hidden; +} + +@media screen and (max-width: 1200px) { + ::v-deep .el-dialog__body { + max-height: none; + overflow: auto; + } +} + .approval-dialog { display: flex; height: 60vh; + padding:20px; + overflow: hidden; + + @media screen and (max-width: 1200px) { + flex-direction: column; + height: auto; + + .approval-content, .approval-flow { + width: 100%; + margin-right: 0; + margin-bottom: 20px; + height: 50vh; + } + } .approval-content { - flex: 1; + flex: 7; margin-right: 20px; background: #ffffff; box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08); border-radius: 10px; + overflow-y: auto; } .approval-flow { + flex: 3; + min-width: 350px; padding: 40px 20px; - width: 405px; background: #ffffff; box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08); border-radius: 10px; + overflow-y: auto; .flow-title { font-size: 16px; @@ -762,7 +699,7 @@ } .groupTable { - width: 65%; + width: 85%; padding-left: 40px; } @@ -779,8 +716,8 @@ margin-left: 38px; .member-list-card { - width: 280px; - height: 300px; + width: 340px; + height: 400px; border-radius: 8px; border: 1px solid #dcdfe6; @@ -891,11 +828,13 @@ .step-list { background: #eff8fa; padding: 20px; + .step-list-item { display: flex; justify-content: space-between; padding: 25px; background: #ffffff; + .step-list-item-title { font-weight: 500; font-size: 14px; @@ -907,13 +846,24 @@ } } -.dialog-footer { - align-items: center; +.content-box { + padding: 0 25px; + margin-bottom: 20px; + width: 65%; display: flex; - justify-content: center; - - button { - width: 150px; + + .content-box-left { + flex: 1; + div { + padding: 10px 0; + } + } + + .content-box-right { + flex: 1; + div { + padding: 10px 0; + } } } </style> \ No newline at end of file diff --git a/laboratory/src/views/dataManagement/schemeManagement/list.vue b/laboratory/src/views/dataManagement/schemeManagement/list.vue index 81ed5ba..4597a9e 100644 --- a/laboratory/src/views/dataManagement/schemeManagement/list.vue +++ b/laboratory/src/views/dataManagement/schemeManagement/list.vue @@ -23,6 +23,7 @@ <el-form-item label="状态:"> <el-select v-model="form.status" placeholder="请选择"> <el-option label="全部" value=""></el-option> + <el-option label="草稿" :value="-1"></el-option> <el-option label="已发送" :value="1"></el-option> <el-option label="申请中止待审核" :value="2"></el-option> <el-option label="申请中止已通过" :value="3"></el-option> @@ -89,13 +90,13 @@ <!-- 工艺工程师(3) --> <template v-if="userRole == '3'"> - <el-button type="text" @click="handleEdit(scope.row)" v-if="scope.row.status == '4'">编辑</el-button> + <!-- <el-button type="text" @click="handleEdit(scope.row)" >编辑</el-button> --> <el-button type="text" @click="handleDetail(scope.row)">详情</el-button> </template> <!-- 实验员(5) --> <template v-if="userRole == '5'"> - <el-button type="text" @click="handleEdit(scope.row)" v-if="scope.row.status == '4'">编辑</el-button> + <el-button type="text" @click="handleEdit(scope.row)" v-if="scope.row.status == 1">编辑</el-button> </template> </template> </el-table-column> @@ -156,11 +157,19 @@ methods: { handlePageChange(page) { this.form.pageNum = page; + // 当处于草稿箱模式时,强制将状态设置为-1 + if (this.currentType === 'draft') { + this.form.status = -1; + } this.getTableData(); }, handleSizeChange(size) { this.form.pageSize = size; this.form.pageNum = 1; + // 当处于草稿箱模式时,强制将状态设置为-1 + if (this.currentType === 'draft') { + this.form.status = -1; + } this.getTableData(); }, resetForm() { @@ -174,10 +183,18 @@ pageNum: 1, pageSize: 10 }; + // 当处于草稿箱模式时,强制将状态设置为-1 + if (this.currentType === 'draft') { + this.form.status = -1; + } this.getTableData(); }, handleSearch() { this.form.pageNum = 1; + // 当处于草稿箱模式时,强制将状态设置为-1 + if (this.currentType === 'draft') { + this.form.status = -1; + } this.getTableData(); }, getStatusType(status) { @@ -187,7 +204,8 @@ '2': "warning", '3': "success", '4': "danger", - '5': "info" + '5': "info", + '6':'success' }; return statusMap[status] || "info"; }, @@ -198,7 +216,8 @@ '2': "申请中止待审核", '3': "申请中止已通过", '4': "申请中止已驳回", - '5': "已封存" + '5': "已封存", + '6':'实验员已提交' }; return statusMap[status] || "未知"; }, @@ -219,6 +238,10 @@ }, async getTableData() { try { + // 当处于草稿箱模式时,强制将状态设置为-1 + if (this.currentType === 'draft') { + this.form.status = -1; + } const { data } = await getList(this.form); this.tableData = data.records || []; this.total = data.total || 0; @@ -244,17 +267,7 @@ handleDetail(row) { this.approvalDialogType = 'view'; this.approvalDialogVisible = true; - this.getPlanDetail(row.id); - }, - async getPlanDetail(id) { - try { - const { data } = await this.$api.getDetail({ id }); - this.currentApprovalData = data; - } catch (error) { - console.error('获取方案详情失败:', error); - this.$message.error('获取方案详情失败'); - this.approvalDialogVisible = false; - } + this.currentApprovalData = row; }, handleApproveSubmit(data) { this.approvalDialogVisible = false; diff --git a/laboratory/src/views/dataManagement/schemeManagement/service.js b/laboratory/src/views/dataManagement/schemeManagement/service.js index 90e3a8d..89e2877 100644 --- a/laboratory/src/views/dataManagement/schemeManagement/service.js +++ b/laboratory/src/views/dataManagement/schemeManagement/service.js @@ -14,7 +14,7 @@ } //修改 export const update = (data) => { - return axios.post('/api/t-experiment-scheme/update', { ...data }) + return axios.post('/api/t-experiment-scheme/updateTester', { ...data }) } //删除 export const deleteById = (data) => { diff --git a/laboratory/src/views/dataManagement/schemeManagement/stop-experiment.vue b/laboratory/src/views/dataManagement/schemeManagement/stop-experiment.vue index 4ed09df..c94ed74 100644 --- a/laboratory/src/views/dataManagement/schemeManagement/stop-experiment.vue +++ b/laboratory/src/views/dataManagement/schemeManagement/stop-experiment.vue @@ -4,33 +4,41 @@ <div class="header-title"> <div class="header-title-left"> <img src="@/assets/public/headercard.png" /> - <span>所属实验调度</span> + <div>所属实验调度</div> </div> </div> <div class="table-container"> <el-table :data="experimentData" border style="width: 100%"> - <el-table-column prop="name" label="所属项目课题方案" /> - <el-table-column prop="code" label="实验编号" /> - <el-table-column prop="title" label="实验主题" /> - <el-table-column prop="startTime" label="实验开始时间" /> - <el-table-column prop="endTime" label="实验结束时间" /> - <el-table-column prop="participants" label="参加人员" /> - <el-table-column prop="status" label="状态" /> + <el-table-column type="index" label="序号" width="80"></el-table-column> + <el-table-column prop="projectName" label="所属项目课题方案"></el-table-column> + <el-table-column prop="experimentCode" label="实验编号"></el-table-column> + <el-table-column prop="experimentName" label="实验名称"></el-table-column> + <el-table-column prop="experimentDate" label="通知时间"></el-table-column> + <el-table-column prop="experimentStartTime" label="实验开始时间"></el-table-column> + <el-table-column prop="experimentEndTime" label="实验结束时间"></el-table-column> + <el-table-column prop="participantsName" label="参加人员"></el-table-column> + <el-table-column prop="status" label="状态"> + <template slot-scope="scope"> + <el-tag :type="getStatusType(scope.row.status)"> + {{ getStatusText(scope.row.status) }} + </el-tag> + </template> + </el-table-column> </el-table> </div> <div class="header-title"> <div class="header-title-left"> <img src="@/assets/public/headercard.png" /> - <span>申请说明</span> + <div>中止原因说明</div> </div> </div> <div class="content-box"> <AiEditor ref="reasonEditor" - v-model="editorContent" + :value="editorContent" height="200px" placeholder="请输入申请说明..." /> @@ -46,6 +54,9 @@ :auto-upload="false" :on-change="handleFileChange" :file-list="fileList" + :on-remove="handleFileRemove" + multiple + :limit="5" > <el-button size="small" type="primary">选择文件</el-button> <div slot="tip" class="el-upload__tip">支持格式:.rar .zip .doc .docx .pdf .jpg...</div> @@ -54,7 +65,7 @@ <div class="footer-section"> <div class="footer-content"> - <el-button type="primary" @click="openSignatureDialog" :disabled="!agreement">提交</el-button> + <el-button type="primary" @click="openSignatureDialog" >提交</el-button> <el-checkbox v-model="agreement">我确认,已仔细实验说明,准确描述本次实验中止全部情况及原因,特此申请中止本次实验。</el-checkbox> </div> </div> @@ -98,6 +109,7 @@ <script> import AiEditor from '@/components/AiEditor' import SignatureCanvas from "@/components/SignatureCanvas.vue" +import { getDetail, applicationTermination } from './service' export default { name: 'StopExperiment', @@ -107,25 +119,54 @@ }, data() { return { - experimentData: [{ - name: '金标准研究项', - code: 'DD-EX001', - title: '金标准研究项', - startTime: '2025-1-2 14:50:19', - endTime: '2025-02-27', - participants: '范兵, 李天霸, 张三, 李四', - status: '已确认' - }], + id: null, + experimentData: [], editorContent: '', fileList: [], agreement: false, signatureDialogVisible: false, signatureCanvasVisible: false, imgSrc: "", + loading: false + } + }, + created() { + this.id = this.$route.query.id + if (this.id) { + this.getExperimentDetail() + } else { + this.$message.error('参数错误,缺少实验ID') } }, methods: { + // 获取实验详情 + async getExperimentDetail() { + try { + this.loading = true + const res = await getDetail({ id: this.id }) + if (res) { + const data = res + this.experimentData = [{...data.experimentDispatch}] + } else { + this.$message.warning('未获取到实验详情') + } + } catch (error) { + console.error('获取实验详情失败:', error) + this.$message.error('获取实验详情失败') + } finally { + this.loading = false + } + }, handleFileChange(file, fileList) { + // this.fileList = fileList + this.fileList = [{uid: Date.now(), + name: '实验中止申请表.txt', + raw: file, + size: file.size, + url:'https://example.com/files/default-stop-application.pdf', + status: 'success'}] + }, + handleFileRemove(file, fileList) { this.fileList = fileList }, getEditorContent() { @@ -133,17 +174,18 @@ }, validateForm() { const content = this.getEditorContent() - if (!content) { - this.$message.error('请填写申请说明') + if (!content || content === '<p></p>' || content.trim() === '<p></p>') { + this.$message.error('请填写中止原因说明') return false } if (!this.agreement) { - this.$message.error('请确认申请说明') + this.$message.error('请先勾选确认申请说明') return false } return true }, openSignatureDialog() { + if (!this.validateForm()) return this.signatureDialogVisible = true }, handleDialogClose() { @@ -155,24 +197,70 @@ }, handleSignatureConfirm(imageData) { this.signatureCanvasVisible = false - this.imgSrc = imageData + // this.imgSrc = imageData + this.imgSrc = 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg' }, - handleConfirm() { + async handleConfirm() { if (!this.imgSrc) { this.$message.warning("请先完成签名确认") return } if (this.validateForm()) { - const formData = { - reason: this.getEditorContent(), - files: this.fileList, - experimentInfo: this.experimentData[0], - signature: this.imgSrc + try { + this.loading = true + + // 处理多个文件,将文件路径和名称通过逗号拼接 + let filePaths = ''; + let fileNames = ''; + + if (this.fileList.length > 0) { + // 模拟文件路径 + filePaths = this.fileList.map(file => file.url).join(',') + + // 文件名称通过逗号拼接 + fileNames = this.fileList.map(file => file.name).join(',') + } + + const formData = { + id: this.id, // 实验方案id + stopReason: this.getEditorContent(), // 中止原因 + commitSign: this.imgSrc, // 提交签字 + stopFile: filePaths, // 中止文件路径,多个文件路径通过逗号拼接 + stopFileName: fileNames // 中止文件名称,多个文件名称通过逗号拼接 + } + + console.log('提交的数据:', formData) + + await applicationTermination(formData) + this.$message.success('提交成功') + this.handleDialogClose() + // 提交成功后返回列表页 + this.$router.go(-1) + } catch (error) { + console.error('提交失败:', error) + this.$message.error('提交失败') + } finally { + this.loading = false } - console.log('提交的数据:', formData) - this.$message.success('提交成功') - this.handleDialogClose() } + }, + getStatusType(status) { + const statusMap = { + "-1": "info", + "1": "warning", + "2": "success", + "3": "info" + }; + return statusMap[status] || "info"; + }, + getStatusText(status) { + const statusMap = { + "-1": "草稿箱", + "1": "待确认", + "2": "已确认", + "3": "已封存" + }; + return statusMap[status] || "未知"; } } } @@ -203,6 +291,20 @@ line-height: 27px; font-family: "Source Han Sans CN Bold Bold"; } + div { + flex-shrink: 0; + font-weight: bold; + font-size: 18px; + color: #222222; + line-height: 27px; + font-family: "Source Han Sans CN Bold Bold"; + + &:before { + content: "*"; + color: #f56c6c; + margin-right: 4px; + } + } } } .header-title:first-child { @@ -228,11 +330,11 @@ span { font-size: 14px; color: #222222; - &::before { - content: "*"; - color: #f56c6c; - margin-right: 4px; - } + // &::before { + // content: "*"; + // color: #f56c6c; + // margin-right: 4px; + // } } } -- Gitblit v1.7.1