New file |
| | |
| | | <template> |
| | | <div> |
| | | <div class="choose-material" :class="title ? '' : 'has-title'"> |
| | | <div class="add-group" v-if="title"> |
| | | <div>*</div> |
| | | <span>{{ title }}</span> |
| | | </div> |
| | | |
| | | <!-- 动态渲染组件 --> |
| | | <div |
| | | v-for="(item, idx) in components" |
| | | :key="item.id" |
| | | class="dynamic-component" |
| | | > |
| | | <!-- 富文本 --> |
| | | <div v-if="item.type == 'richText'"> |
| | | <AiEditor |
| | | :ref="`editor_${item.id}`" |
| | | v-model="item.data.content" |
| | | height="200px" |
| | | placeholder="请输入内容..." |
| | | /> |
| | | </div> |
| | | <!-- 自定义表格 --> |
| | | <div v-else-if="item.type == 'customTable'" style="flex: 1"> |
| | | <Table |
| | | :data="item.data.rows" |
| | | :total="null" |
| | | :height="null" |
| | | class="groupTable" |
| | | > |
| | | <el-table-column |
| | | v-for="(header, hidx) in item.data.headers" |
| | | :key="hidx" |
| | | :label="header.name" |
| | | :prop="header.name" |
| | | > |
| | | <template slot-scope="scope"> |
| | | <!-- 文本类型 --> |
| | | <span v-if="header.type === 'text'">{{ scope.row[header.name] }}</span> |
| | | <!-- 图片类型 --> |
| | | <div v-else-if="header.type === 'image'" class="image-preview"> |
| | | <el-image |
| | | v-for="(img, imgIndex) in scope.row[header.name]" |
| | | :key="imgIndex" |
| | | :src="img.url" |
| | | :preview-src-list="[img.url]" |
| | | fit="cover" |
| | | class="preview-image" |
| | | /> |
| | | </div> |
| | | <!-- 日期类型 --> |
| | | <span v-else-if="header.type === 'date'">{{ scope.row[header.name] }}</span> |
| | | <!-- 用户类型 --> |
| | | <div v-else-if="header.type === 'user'" class="user-tags"> |
| | | <el-tag |
| | | v-for="user in scope.row[header.name]" |
| | | :key="user" |
| | | class="user-tag" |
| | | > |
| | | {{ getUserName(user) }} |
| | | </el-tag> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | label="更新时间" |
| | | prop="updateTime" |
| | | min-width="180" |
| | | ></el-table-column> |
| | | </Table> |
| | | </div> |
| | | <!-- 文件上传 --> |
| | | <div v-else-if="item.type == 'fileUpload'"> |
| | | <el-upload |
| | | action="#" |
| | | :file-list="item.data.fileList" |
| | | :disabled="true" |
| | | list-type="text" |
| | | > |
| | | <el-button style="display: none">点击上传</el-button> |
| | | </el-upload> |
| | | </div> |
| | | <!-- 图片上传 --> |
| | | <div v-else-if="item.type == 'imageUpload'"> |
| | | <el-image |
| | | v-for="(img, imgIndex) in item.data.images" |
| | | :key="imgIndex" |
| | | :src="img.url" |
| | | :preview-src-list="[img.url]" |
| | | fit="cover" |
| | | class="preview-image" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import Table from "../Table/index.vue"; |
| | | import AiEditor from "../AiEditor/index.vue"; |
| | | |
| | | export default { |
| | | name: "ViewDynamicComponent", |
| | | components: { |
| | | Table, |
| | | AiEditor |
| | | }, |
| | | props: { |
| | | title: { |
| | | type: String, |
| | | default: "", |
| | | }, |
| | | components: { |
| | | type: Array, |
| | | default: () => [], |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | userOptions: [ |
| | | { value: '1', label: '用户1' }, |
| | | { value: '2', label: '用户2' }, |
| | | { value: '3', label: '用户3' }, |
| | | { value: '4', label: '用户4' }, |
| | | { value: '5', label: '用户5' } |
| | | ] |
| | | }; |
| | | }, |
| | | methods: { |
| | | getUserName(userId) { |
| | | const user = this.userOptions.find(u => u.value === userId); |
| | | return user ? user.label : userId; |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="less"> |
| | | .preview-image{ |
| | | width: 120px; |
| | | height: 120px; |
| | | border-radius: 4px; |
| | | object-fit: cover; |
| | | margin-right: 20px; |
| | | } |
| | | .choose-material { |
| | | background: #eff8fa; |
| | | padding: 20px; |
| | | margin-top: 37px; |
| | | } |
| | | .has-title{ |
| | | margin-top: 0px !important; |
| | | } |
| | | .add-group { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 19px; |
| | | |
| | | div { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | span { |
| | | font-weight: 500; |
| | | font-size: 14px; |
| | | color: #222222; |
| | | line-height: 21px; |
| | | margin: 0 32px 0 8px; |
| | | } |
| | | } |
| | | .dynamic-component { |
| | | background: #ffffff; |
| | | padding: 15px 20px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .rich-text-content { |
| | | width: 100%; |
| | | min-height: 200px; |
| | | padding: 10px; |
| | | border: 1px solid #dcdfe6; |
| | | border-radius: 4px; |
| | | background-color: #f5f7fa; |
| | | } |
| | | |
| | | .image-preview { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 8px; |
| | | |
| | | .preview-image { |
| | | width: 120px; |
| | | height: 120px; |
| | | border-radius: 4px; |
| | | object-fit: cover; |
| | | } |
| | | } |
| | | |
| | | .user-tags { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .user-tag { |
| | | margin-right: 4px; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | |
| | | .uploaf-notice { |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: rgba(0, 0, 0, 0.85); |
| | | line-height: 22px; |
| | | margin-top: 8px; |
| | | } |
| | | |
| | | .groupTable { |
| | | width: 100%; |
| | | margin-top: 10px; |
| | | ::v-deep .el-input__inner { |
| | | width: unset !important; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | }) |
| | | .catch(() => {}); |
| | | }, |
| | | confirmAddRow(formData) { |
| | | const { idx, rowIndex, isEdit } = this.rowDialog; |
| | | if (isEdit) { |
| | | // 编辑模式:替换原有行数据 |
| | | this.components[idx].data.rows.splice(rowIndex, 1, formData); |
| | | } else { |
| | | // 新增模式:添加新行数据 |
| | | this.components[idx].data.rows.push(formData); |
| | | } |
| | | this.rowDialog.visible = false; |
| | | // 重置对话框数据 |
| | | this.rowDialog = { |
| | | visible: false, |
| | | idx: null, |
| | | rowIndex: null, |
| | | isEdit: false, |
| | | headers: [], |
| | | form: {}, |
| | | }; |
| | | }, |
| | | handleFileChange(idx, fileList) { |
| | | this.components[idx].data.fileList = fileList; |
| | | }, |
| | |
| | | file.url = res.url; |
| | | this.components[idx].data.imageList = fileList; |
| | | }, |
| | | // 获取所有组件数据 |
| | | getComponentsData() { |
| | | // 整理数据,图片只保留url |
| | | const submitData = this.components.map((item) => { |
| | | if (item.type === "richText") { |
| | | // 获取富文本编辑器的内容 |
| | | const editorRef = this.$refs[`editor_${item.id}`]; |
| | | return { |
| | | ...item, |
| | | data: { |
| | | content: editorRef ? editorRef.getContent() : item.data.content |
| | | } |
| | | }; |
| | | } |
| | | if (item.type === "imageUpload") { |
| | | return { |
| | | ...item, |
| | | data: { |
| | | imageList: item.data.imageList.map((img) => ({ url: img.url })), |
| | | }, |
| | | }; |
| | | } |
| | | return item; |
| | | }); |
| | | return submitData; |
| | | }, |
| | | // 验证所有组件数据 |
| | | validateComponents() { |
| | | // 验证富文本编辑器 |
| | | const richTextValid = this.components.every(item => { |
| | | if (item.type === 'richText') { |
| | | const editorRef = this.$refs[`editor_${item.id}`]; |
| | | return editorRef && editorRef.getContent().trim() !== ''; |
| | | } |
| | | return true; |
| | | }); |
| | | |
| | | if (!richTextValid) { |
| | | this.$message.error('请填写所有富文本内容'); |
| | | return false; |
| | | } |
| | | |
| | | // 验证表格数据 |
| | | const tableValid = this.components.every(item => { |
| | | if (item.type === 'customTable') { |
| | | return item.data.rows.length > 0; |
| | | } |
| | | return true; |
| | | }); |
| | | |
| | | if (!tableValid) { |
| | | this.$message.error('请至少添加一行表格数据'); |
| | | return false; |
| | | } |
| | | |
| | | return true; |
| | | }, |
| | | // 提交数据 |
| | | submit() { |
| | | if (this.validateComponents()) { |
| | | const data = this.getComponentsData(); |
| | | this.$emit('submit', data); |
| | | } |
| | | } |
| | | }, |
| | | }; |
| | | </script> |
| | |
| | | <template> |
| | | <el-dialog |
| | | :title="dialogTitle" |
| | | :visible.sync="visible" |
| | | width="80%" |
| | | :close-on-click-modal="false" |
| | | @close="handleClose" |
| | | > |
| | | <div class="approval-dialog"> |
| | | <!-- 左侧审批内容 --> |
| | | <div class="approval-content"> |
| | | <Card class="approval-content-card"> |
| | | <template style="position: relative"> |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <span>项目课题方案信息</span> |
| | | </div> |
| | | </div> |
| | | <el-form |
| | | ref="form" |
| | | :model="form" |
| | | :rules="rules" |
| | | inline |
| | | label-position="top" |
| | | style="margin-top: 38px" |
| | | > |
| | | <el-form-item prop="name" label="项目课题方案名称"> |
| | | <el-input v-model="form.name" placeholder="请输入" /> |
| | | </el-form-item> |
| | | <el-form-item prop="description" label="项目阶段"> |
| | | <el-input v-model="form.description" placeholder="请输入" /> |
| | | </el-form-item> |
| | | <el-form-item prop="description" label="项目课题方案编号"> |
| | | <el-input v-model="form.description" placeholder="请输入" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>一 、实验目的</div> |
| | | </div> |
| | | </div> |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>二 、实验拆料和设备</div> |
| | | </div> |
| | | </div> |
| | | <div class="item-title"> |
| | | <span>1.实验材料</span> |
| | | </div> |
| | | <div class="item-title"> |
| | | <span>2.实验设备</span> |
| | | </div> |
| | | <div> |
| | | <el-dialog |
| | | title="实验方案详情" |
| | | :visible.sync="visible" |
| | | width="80%" |
| | | :close-on-click-modal="false" |
| | | @close="handleClose" |
| | | > |
| | | <div class="approval-dialog"> |
| | | <!-- 左侧审批内容 --> |
| | | <div class="approval-content"> |
| | | <Card class="approval-content-card"> |
| | | <template style="position: relative"> |
| | | <el-form |
| | | ref="form" |
| | | :model="form" |
| | | :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> |
| | | </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> |
| | | |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>三 、检测方法及开发</div> |
| | | </div> |
| | | </div> |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>四 、实验步骤</div> |
| | | </div> |
| | | </div> |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>五 、数据采集及分析</div> |
| | | </div> |
| | | </div> |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>六 、结果评估</div> |
| | | </div> |
| | | </div> |
| | | <div class="header-title"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <span>注意事项</span> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <SelectMember ref="selectMember" /> |
| | | </Card> |
| | | </div> |
| | | <!-- 右侧审批流程 --> |
| | | <div class="approval-flow"> |
| | | <div class="flow-content"> |
| | | <approval-process |
| | | :status="form.status" |
| | | :submit-time="form.createTime" |
| | | :approver="form.approver" |
| | | :approve-time="form.approveTime" |
| | | /> |
| | | <div class="header-title" style="margin-bottom: 38px"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <span>基础信息</span> |
| | | </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> |
| | | |
| | | <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" |
| | | /> |
| | | </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-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> |
| | | </div> |
| | | </div> |
| | | <div class="member-change" v-if="type !== 'view'"> |
| | | <div class="member-change-btn">修改</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="header-title" style="margin-bottom: 38px"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>一、实验目的</div> |
| | | </div> |
| | | </div> |
| | | <AiEditor |
| | | ref="purposeEditor" |
| | | v-model="form.purpose" |
| | | height="200px" |
| | | placeholder="请输入实验目的..." |
| | | /> |
| | | |
| | | <div class="header-title" style="margin-bottom: 38px"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>二、工艺参数及路线</div> |
| | | </div> |
| | | </div> |
| | | <AiEditor |
| | | ref="processEditor" |
| | | v-model="form.process" |
| | | height="200px" |
| | | placeholder="请输入工艺参数及路线..." |
| | | /> |
| | | |
| | | <div class="header-title" style="margin-bottom: 38px"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>三、实验材料及设备</div> |
| | | </div> |
| | | </div> |
| | | <ViewDynamicComponent |
| | | title="实验材料" |
| | | :components="form.materialsAndEquipment || []" |
| | | /> |
| | | <ViewDynamicComponent |
| | | title="实验所用设备" |
| | | :components="form.materialsAndEquipment || []" |
| | | /> |
| | | |
| | | <div class="header-title" style="margin-bottom: 38px"> |
| | | <div class="header-title-left"> |
| | | <img src="@/assets/public/headercard.png" /> |
| | | <div>四、实验操作步骤记录</div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="step-list" v-for="(item, idx) in form.operationSteps" :key="idx"> |
| | | <div class="step-list-item"> |
| | | <div class="step-list-item-title"> |
| | | 步骤{{ idx + 1 }}:{{ item.stepName }} |
| | | </div> |
| | | </div> |
| | | <ViewDynamicComponent |
| | | :ref="'stepContent' + idx" |
| | | :components="[item]" |
| | | /> |
| | | </div> |
| | | </el-form> |
| | | </template> |
| | | </Card> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="approval-dialog-approve"> |
| | | <div class="status"> |
| | | <div class="status-title">审批结果</div> |
| | | <div class="status-content"> |
| | | <div |
| | | class="resolve" |
| | | :class="status == '1' && 'activeStatus'" |
| | | @click.stop="status = 1" |
| | | > |
| | | 通过 |
| | | </div> |
| | | <div |
| | | class="reject" |
| | | :class="status == '2' && 'activeStatus'" |
| | | @click.stop="status = 2" |
| | | > |
| | | 驳回 |
| | | <!-- 右侧审批流程 --> |
| | | <div class="approval-flow" v-if="type === 'view'"> |
| | | <div class="flow-content"> |
| | | <approval-process |
| | | :status="form.status" |
| | | :submit-time="form.createTime" |
| | | :approver="form.approver" |
| | | :approve-time="form.approveTime" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="remark"> |
| | | <div class="remark-title">审批意见</div> |
| | | <el-input type="textarea" v-model="remark" placeholder="请输入审批意见" /> |
| | | </div> |
| | | </div> |
| | | <div slot="footer" class="dialog-footer"> |
| | | <el-button @click="handleClose">取 消</el-button> |
| | | <el-button type="primary" @click="handleApprove" v-if="type === 'approve'" |
| | | >通过</el-button |
| | | > |
| | | </div> |
| | | </el-dialog> |
| | | </el-dialog> |
| | | <SignatureCanvas |
| | | :visible="signatureDialogVisible" |
| | | @confirm="handleSignatureConfirm" |
| | | /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import ApprovalProcess from '@/components/approvalProcess' |
| | | 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"; |
| | | |
| | | export default { |
| | | name: "ApprovalDialog", |
| | | components: { |
| | | ApprovalProcess |
| | | ApprovalProcess, |
| | | SignatureCanvas, |
| | | ViewDynamicComponent, |
| | | AiEditor, |
| | | }, |
| | | props: { |
| | | visible: { |
| | |
| | | planName: "", |
| | | planCode: "", |
| | | stage: "", |
| | | testDate: "", |
| | | testName: "", |
| | | testCode: "", |
| | | testTime: "", |
| | | creator: "", |
| | | createTime: "", |
| | | approvalComment: "", |
| | | status: "pending", |
| | | status: "approved", |
| | | approver: "", |
| | | approveTime: "" |
| | | 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", |
| | | }, |
| | | ], |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | radio1: 1, |
| | | rules: {}, |
| | | 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: [], |
| | | }; |
| | | }, |
| | | computed: { |
| | | dialogTitle() { |
| | | return this.type === "approve" ? "审批" : "审批详情"; |
| | | return this.type === "approve" ? "确认实验调度" : "实验调度详情"; |
| | | }, |
| | | }, |
| | | watch: { |
| | | data: { |
| | | handler(val) { |
| | | if (val) { |
| | | this.form = { ...val }; |
| | | // 深拷贝数据,避免直接修改props |
| | | this.form = JSON.parse( |
| | | JSON.stringify({ |
| | | ...this.form, |
| | | ...val, |
| | | // 确保这些字段存在,如果不存在则使用默认值 |
| | | materialsAndEquipment: val.materialsAndEquipment || [], |
| | | operationSteps: val.operationSteps || [], |
| | | }) |
| | | ); |
| | | console.log("接收到的数据:", this.form); |
| | | } |
| | | }, |
| | | immediate: true, |
| | | deep: true, |
| | | }, |
| | | visible: { |
| | | handler(val) { |
| | | if (val && this.type === "view") { |
| | | // 当弹窗打开且是查看模式时,获取详情数据 |
| | | this.getPlanDetail(); |
| | | } |
| | | }, |
| | | 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; |
| | | } |
| | | }, |
| | | handleClose() { |
| | | this.$emit("update:visible", false); |
| | | this.form.approvalComment = ""; |
| | |
| | | status: "rejected", |
| | | }); |
| | | }, |
| | | memberList(item) { |
| | | return item === 1 ? 2 : item === 2 ? 3 : 1; |
| | | }, |
| | | openSignature() { |
| | | this.signatureDialogVisible = true; |
| | | }, |
| | | handleSignatureConfirm(imageData) { |
| | | 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() { |
| | | try { |
| | | // TODO: 替换为实际的接口调用 |
| | | // const { data } = await this.$api.getPlanDetail({ planCode: this.data.planCode }); |
| | | |
| | | // 模拟接口返回数据 |
| | | 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, |
| | | }; |
| | | } catch (error) { |
| | | console.error("获取方案详情失败:", error); |
| | | this.$message.error("获取方案详情失败"); |
| | | this.handleClose(); |
| | | } |
| | | }, |
| | | }, |
| | | }; |
| | | </script> |
| | |
| | | |
| | | .approval-dialog { |
| | | display: flex; |
| | | height: 300px; |
| | | height: 60vh; |
| | | |
| | | .approval-content { |
| | | flex: 1; |
| | |
| | | } |
| | | } |
| | | |
| | | .approval-dialog-approve { |
| | | margin-top: 26px; |
| | | } |
| | | |
| | | .approval-content-card { |
| | | height: calc(100% - 100px) !important; |
| | | box-shadow: none !important; |
| | |
| | | align-items: center; |
| | | flex-wrap: wrap; |
| | | gap: 13px; |
| | | margin-top: 38px; |
| | | |
| | | .header-title-left { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 13px; |
| | | margin-top: 38px; |
| | | |
| | | img { |
| | | width: 12px; |
| | |
| | | } |
| | | |
| | | .header-title:first-child { |
| | | margin-top: 0 !important; |
| | | |
| | | .header-title-left { |
| | | margin-top: 0; |
| | | } |
| | |
| | | } |
| | | |
| | | .approval-dialog-approve { |
| | | padding: 38px 20px; |
| | | display: flex; |
| | | align-content: center; |
| | | .status { |
| | | margin-right: 40px; |
| | | } |
| | | // align-items: center; |
| | | .status-title { |
| | | color: #222222; |
| | | font-family: "SourceHanSansCN-Medium"; |
| | | line-height: 14px; |
| | | margin-bottom: 16px; |
| | | } |
| | | .status-content { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 16px; |
| | | background: #ffffff; |
| | | border-radius: 10px; |
| | | border: 1px solid rgba(4, 156, 154, 0.5); |
| | | .resolve { |
| | | border-radius: 10px; |
| | | font-size: 16px; |
| | | padding: 5px 55px; |
| | | font-weight: 400; |
| | | color: #333333; |
| | | cursor: pointer; |
| | | } |
| | | .reject { |
| | | border-radius: 10px; |
| | | font-size: 16px; |
| | | padding: 5px 55px; |
| | | font-weight: 400; |
| | | color: #333333; |
| | | cursor: pointer; |
| | | } |
| | | .activeStatus { |
| | | background: #ebfefd; |
| | | color: #049c9a; |
| | | box-shadow: 0px 0px 6px 0px rgba(10, 109, 108, 0.25); |
| | | border-radius: 10px; |
| | | } |
| | | } |
| | | .remark-title { |
| | | color: #222222; |
| | | font-family: "SourceHanSansCN-Medium"; |
| | | line-height: 14px; |
| | | margin-bottom: 16px; |
| | | img { |
| | | border: 2px dashed #049c9a; |
| | | } |
| | | } |
| | | |
| | | .dialog-footer{ |
| | | align-items: center; |
| | | display: flex; |
| | | justify-content: center; |
| | | button{ |
| | | width: 150px; |
| | | .add-group { |
| | | padding-left: 25px; |
| | | margin-top: 14px; |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 19px; |
| | | |
| | | div { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | span { |
| | | font-weight: 500; |
| | | font-size: 14px; |
| | | color: #222222; |
| | | line-height: 21px; |
| | | margin: 0 32px 0 8px; |
| | | } |
| | | } |
| | | |
| | | .groupTable { |
| | | width: 65%; |
| | | padding-left: 40px; |
| | | } |
| | | |
| | | .rwuTable { |
| | | width: 85%; |
| | | padding-left: 40px; |
| | | } |
| | | |
| | | .member-list { |
| | | margin-top: 18px; |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 28px; |
| | | margin-left: 38px; |
| | | |
| | | .member-list-card { |
| | | width: 280px; |
| | | height: 300px; |
| | | border-radius: 8px; |
| | | border: 1px solid #dcdfe6; |
| | | |
| | | &:nth-child(1) { |
| | | background: linear-gradient( |
| | | to bottom, |
| | | rgba(4, 156, 154, 0.2) 0%, |
| | | rgba(5, 242, 194, 0) 70% |
| | | ); |
| | | } |
| | | |
| | | &:nth-child(2) { |
| | | background: linear-gradient( |
| | | to bottom, |
| | | rgba(5, 160, 193, 0.2) 0%, |
| | | rgba(5, 242, 194, 0) 70% |
| | | ); |
| | | } |
| | | |
| | | &:nth-child(3) { |
| | | background: linear-gradient( |
| | | to bottom, |
| | | rgba(255, 77, 79, 0.2) 0%, |
| | | rgba(255, 242, 194, 0) 70% |
| | | ); |
| | | } |
| | | |
| | | &:nth-child(4) { |
| | | background: linear-gradient( |
| | | to bottom, |
| | | rgba(250, 199, 20, 0.21) 0%, |
| | | rgba(255, 242, 194, 0) 70% |
| | | ); |
| | | } |
| | | |
| | | .member-item { |
| | | height: 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .member-title { |
| | | margin-top: 20px; |
| | | width: 100%; |
| | | font-family: "Source Han Sans CN Bold Bold"; |
| | | font-weight: bold; |
| | | font-size: 16px; |
| | | color: rgba(0, 0, 0, 0.8); |
| | | line-height: 16px; |
| | | text-align: center; |
| | | } |
| | | |
| | | .flex1 { |
| | | flex: 1; |
| | | } |
| | | |
| | | .member-name-box { |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .member-name-box-2 { |
| | | flex: 1; |
| | | padding: 0 20px; |
| | | padding-top: 40px; |
| | | display: grid; |
| | | grid-template-columns: repeat(4, 1fr); |
| | | gap: 20px; |
| | | justify-items: center; |
| | | align-items: start; |
| | | } |
| | | |
| | | .member-name { |
| | | width: 60px; |
| | | height: 60px; |
| | | background: #7d8b79; |
| | | border-radius: 50%; |
| | | text-align: center; |
| | | line-height: 60px; |
| | | font-weight: 500; |
| | | font-size: 16px; |
| | | color: #ffffff; |
| | | margin: 0; |
| | | } |
| | | |
| | | .member-change { |
| | | display: flex; |
| | | justify-content: center; |
| | | padding: 10px 0; |
| | | margin-top: auto; |
| | | cursor: pointer; |
| | | |
| | | .member-change-btn { |
| | | background: #fff1f0; |
| | | border-radius: 4px; |
| | | border: 1px solid #ffccc7; |
| | | padding: 1px 8px; |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: #ff4d4f; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .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; |
| | | color: rgba(0, 0, 0, 0.8); |
| | | line-height: 20px; |
| | | flex-wrap: wrap; |
| | | flex: 1; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .dialog-footer { |
| | | align-items: center; |
| | | display: flex; |
| | | justify-content: center; |
| | | |
| | | button { |
| | | width: 150px; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | createTime: '2024-03-15', |
| | | status: 'pending', |
| | | approver: '李四', |
| | | approveTime: '2024-03-16' |
| | | approveTime: '2024-03-16', |
| | | purpose: '<p>1. 研究新型催化剂的性能</p><p>2. 优化反应条件</p><p>3. 提高产品收率</p>', |
| | | processParameters: [ |
| | | { |
| | | '工艺参数': '反应温度', |
| | | '参数值': '25℃', |
| | | '操作人员': ['1', '2'], |
| | | updateTime: '2024-01-01 12:00:00' |
| | | }, |
| | | { |
| | | '工艺参数': '反应压力', |
| | | '参数值': '1.0MPa', |
| | | '操作人员': ['3'], |
| | | updateTime: '2024-01-01 12:00:00' |
| | | } |
| | | ], |
| | | materials: [ |
| | | { |
| | | '材料名称': '催化剂A', |
| | | '规格': '工业级', |
| | | '数量': '100g', |
| | | updateTime: '2024-01-01 12:00:00' |
| | | }, |
| | | { |
| | | '材料名称': '溶剂B', |
| | | '规格': '分析纯', |
| | | '数量': '500ml', |
| | | updateTime: '2024-01-01 12:00:00' |
| | | } |
| | | ], |
| | | steps: '<p>1. 准备工作</p><p>2. 设备检查</p><p>3. 实验操作</p><p>4. 数据记录</p>' |
| | | }, |
| | | { |
| | | planCode: 'PLAN-2024-002', |
| | |
| | | createTime: '2024-03-14', |
| | | status: 'approved', |
| | | approver: '赵六', |
| | | approveTime: '2024-03-15' |
| | | approveTime: '2024-03-15', |
| | | purpose: '<p>1. 评估现有安全管理制度</p><p>2. 制定新的安全规范</p><p>3. 进行安全培训</p>', |
| | | processParameters: [ |
| | | { |
| | | '工艺参数': '培训时间', |
| | | '参数值': '2小时', |
| | | '操作人员': ['1', '2', '3'], |
| | | updateTime: '2024-01-01 12:00:00' |
| | | } |
| | | ], |
| | | materials: [ |
| | | { |
| | | '材料名称': '培训材料', |
| | | '规格': 'A4', |
| | | '数量': '50份', |
| | | updateTime: '2024-01-01 12:00:00' |
| | | } |
| | | ], |
| | | steps: '<p>1. 安全评估</p><p>2. 制度更新</p><p>3. 人员培训</p><p>4. 效果评估</p>' |
| | | }, |
| | | { |
| | | planCode: 'PLAN-2024-003', |
| | |
| | | console.log("删除数据:", row); |
| | | }, |
| | | handleDetail(row) { |
| | | this.currentApprovalData = row; |
| | | // 打开弹窗 |
| | | this.approvalDialogType = 'view'; |
| | | this.approvalDialogVisible = true; |
| | | |
| | | // 调用获取详情接口 |
| | | this.getPlanDetail(row.planCode); |
| | | }, |
| | | // 获取方案详情 |
| | | async getPlanDetail(planCode) { |
| | | try { |
| | | // TODO: 替换为实际的接口调用 |
| | | // const { data } = await this.$api.getPlanDetail({ planCode }); |
| | | |
| | | // 模拟接口返回数据 |
| | | const mockDetailData = { |
| | | planCode: 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', |
| | | experimentPurpose: [ |
| | | { |
| | | id: 1, |
| | | type: 'richText', |
| | | data: { |
| | | content: '<p>1. 研究新型催化剂的性能</p><p>2. 优化反应条件</p><p>3. 提高产品收率</p>' |
| | | } |
| | | } |
| | | ], |
| | | processParameters: [ |
| | | { |
| | | id: 2, |
| | | type: 'customTable', |
| | | data: { |
| | | headers: [ |
| | | { name: '工艺参数', type: 'text' }, |
| | | { name: '参数值', type: 'text' }, |
| | | { name: '操作人员', type: 'user' } |
| | | ], |
| | | rows: [ |
| | | { |
| | | '工艺参数': '反应温度', |
| | | '参数值': '25℃', |
| | | '操作人员': ['1', '2'], |
| | | updateTime: '2024-01-01 12:00:00' |
| | | }, |
| | | { |
| | | '工艺参数': '反应压力', |
| | | '参数值': '1.0MPa', |
| | | '操作人员': ['3'], |
| | | updateTime: '2024-01-01 12:00:00' |
| | | } |
| | | ] |
| | | } |
| | | } |
| | | ], |
| | | materialsAndEquipment: [ |
| | | { |
| | | id: 3, |
| | | type: 'customTable', |
| | | data: { |
| | | headers: [ |
| | | { 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' |
| | | } |
| | | ] |
| | | } |
| | | } |
| | | ], |
| | | operationSteps: [ |
| | | { |
| | | id: 4, |
| | | type: 'richText', |
| | | data: { |
| | | content: '<p>1. 准备工作</p><p>2. 设备检查</p><p>3. 实验操作</p><p>4. 数据记录</p>' |
| | | } |
| | | } |
| | | ] |
| | | }; |
| | | |
| | | // 更新弹窗数据 |
| | | this.currentApprovalData = mockDetailData; |
| | | } catch (error) { |
| | | console.error('获取方案详情失败:', error); |
| | | this.$message.error('获取方案详情失败'); |
| | | this.approvalDialogVisible = false; |
| | | } |
| | | }, |
| | | handleTypeChange(type) { |
| | | this.currentType = type; |
| | |
| | | |
| | | <div slot="footer" class="dialog-footer"> |
| | | <el-button @click="handleDialogClose">取 消</el-button> |
| | | <el-button type="primary" @click="handleConfirm" :disabled="!imgSrc">确 认</el-button> |
| | | <el-button type="primary" @click="handleConfirm">确 认</el-button> |
| | | </div> |
| | | </el-dialog> |
| | | |