董国庆
2025-04-30 4a69fdde1710397eb0afd09087854127e3f842b6
动态添加表格进行数据回显
4个文件已修改
1个文件已添加
1263 ■■■■ 已修改文件
laboratory/src/components/DynamicComponent/ViewDynamicComponent.vue 230 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/components/DynamicComponent/index.vue 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/dataManagement/schemeManagement/components/approvalDialog.vue 789 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/dataManagement/schemeManagement/list.vue 158 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/dataManagement/schemeManagement/stop-experiment.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/components/DynamicComponent/ViewDynamicComponent.vue
New file
@@ -0,0 +1,230 @@
<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>
laboratory/src/components/DynamicComponent/index.vue
@@ -298,26 +298,6 @@
        })
        .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;
    },
@@ -329,70 +309,6 @@
      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>
laboratory/src/views/dataManagement/schemeManagement/components/approvalDialog.vue
@@ -1,6 +1,7 @@
<template>
  <div>
  <el-dialog
    :title="dialogTitle"
      title="实验方案详情"
    :visible.sync="visible"
    width="80%"
    :close-on-click-modal="false"
@@ -11,85 +12,176 @@
      <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"
                :disabled="type === 'view'"
            >
              <el-form-item prop="name" label="项目课题方案名称">
                <el-input v-model="form.name" placeholder="请输入" />
                <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" 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>
              <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>
                <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>
            <div class="header-title">
                <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>
              </div>
            </div>
            <div class="item-title">
              <span>1.实验材料</span>
                <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 class="item-title">
              <span>2.实验设备</span>
                </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="header-title">
              <div class="header-title-left">
                <img src="@/assets/public/headercard.png" />
                <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>
            <div class="header-title">
              <div class="header-title-left">
                <img src="@/assets/public/headercard.png" />
                <div>四 、实验步骤</div>
                  <ViewDynamicComponent
                    :ref="'stepContent' + idx"
                    :components="[item]"
                  />
              </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>
              </el-form>
          </template>
          <SelectMember ref="selectMember" />
        </Card>
      </div>
      <!-- 右侧审批流程 -->
      <div class="approval-flow">
        <div class="approval-flow" v-if="type === 'view'">
        <div class="flow-content">
          <approval-process
            :status="form.status"
@@ -100,47 +192,27 @@
        </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>
        </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>
    <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: {
@@ -162,35 +234,214 @@
        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>",
      },
      radio1: 1,
      rules: {},
          },
          {
            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",
                },
              ],
            },
          },
        ],
      },
      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 = "";
@@ -215,6 +466,145 @@
        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>
@@ -226,7 +616,7 @@
.approval-dialog {
  display: flex;
  height: 300px;
  height: 60vh;
  .approval-content {
    flex: 1;
@@ -261,6 +651,10 @@
  }
}
.approval-dialog-approve {
  margin-top: 26px;
}
.approval-content-card {
  height: calc(100% - 100px) !important;
  box-shadow: none !important;
@@ -271,12 +665,12 @@
  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;
@@ -314,6 +708,8 @@
}
.header-title:first-child {
  margin-top: 0 !important;
  .header-title-left {
    margin-top: 0;
  }
@@ -340,54 +736,174 @@
}
.approval-dialog-approve {
  padding: 38px 20px;
  display: flex;
  align-content: center;
  .status {
    margin-right: 40px;
  img {
    border: 2px dashed #049c9a;
  }
  //   align-items: center;
  .status-title {
    color: #222222;
    font-family: "SourceHanSansCN-Medium";
    line-height: 14px;
    margin-bottom: 16px;
  }
  .status-content {
.add-group {
  padding-left: 25px;
  margin-top: 14px;
    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;
  margin-bottom: 19px;
  div {
    color: #f56c6c;
    }
    .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 {
  span {
    font-weight: 500;
    font-size: 14px;
    color: #222222;
    font-family: "SourceHanSansCN-Medium";
    line-height: 14px;
    margin-bottom: 16px;
    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;
    }
  }
}
@@ -395,6 +911,7 @@
    align-items: center;
    display: flex;
    justify-content: center;
    button{
        width: 150px;
    }
laboratory/src/views/dataManagement/schemeManagement/list.vue
@@ -127,7 +127,37 @@
          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',
@@ -139,7 +169,25 @@
          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',
@@ -261,9 +309,113 @@
      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;
laboratory/src/views/dataManagement/schemeManagement/stop-experiment.vue
@@ -84,7 +84,7 @@
      <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>