pyt
2025-05-08 cd47054e513f2e632ad67d3495eb8d9e35f99d82
Merge branch 'main' of http://120.76.84.145:10101/gitblit/r/H5/leshan-laboratory
2 文件已复制
4 文件已重命名
10个文件已修改
14个文件已删除
39个文件已添加
14726 ■■■■ 已修改文件
culture/package.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/router/index.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/deliveryAssessment/chemistEvaluate/add.vue 200 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/deliveryAssessment/chemistEvaluate/index.vue 217 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/deliveryAssessment/clinicalTrial/components/detail.vue 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/deliveryAssessment/clinicalTrial/index.vue 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/deliveryAssessment/experimentResults/components/detail.vue 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/deliveryAssessment/processEngineerEvaluate/index.vue 157 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/deliveryAssessment/projectTeamIntegral/detail.vue 116 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/deliveryAssessment/restsTask/components/detail.vue 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/deliveryAssessment/restsTask/index.vue 128 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/deliveryAssessment/taskList/components/AssessmentDialog.vue 244 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/deliveryAssessment/taskList/index.vue 171 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/deliveryAssessment/testerWorkerEvaluate/add.vue 200 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/deliveryAssessment/testerWorkerEvaluate/index.vue 217 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/deliveryAssessment/testingAndEvaluation/components/AssessmentDialog.vue 208 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/deliveryAssessment/testingAndEvaluation/index.vue 212 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strain-library/strain-flow-chart/index.vue 694 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strainReportLibrary/reportLibraryOne/add.vue 194 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strainReportLibrary/reportLibraryOne/components/approval/index.vue 393 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strainReportLibrary/reportLibraryOne/index.vue 230 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strainReportLibrary/reportLibraryOneFour/add.vue 173 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strainReportLibrary/reportLibraryOneFour/components/approval/index.vue 393 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strainReportLibrary/reportLibraryOneFour/index.vue 232 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strainReportLibrary/reportLibraryOneTWO/add.vue 173 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strainReportLibrary/reportLibraryOneTWO/components/approval/index.vue 393 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strainReportLibrary/reportLibraryOneTWO/index.vue 232 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strainReportLibrary/reportLibraryOneThree/add.vue 173 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strainReportLibrary/reportLibraryOneThree/components/approval/index.vue 393 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/src/views/strainReportLibrary/reportLibraryOneThree/index.vue 232 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/App.vue 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/assets/public/delete.png 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/assets/public/edit.png 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/components/AddComponentDialog/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/components/DynamicComponent/ViewDynamicComponent.vue 230 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/components/DynamicComponent/addTableData.vue 537 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/components/DynamicComponent/addTableHeader.vue 350 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/components/DynamicComponent/index.vue 389 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/layouts/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/router/index.js 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/chemistQa/pilotAndProduction/add.vue 172 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/chemistQa/pilotAndProduction/components/approval/index.vue 397 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/chemistQa/pilotAndProduction/index.vue 211 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/chemistQa/productApproval/add.vue 172 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/chemistQa/productApproval/components/approval/index.vue 397 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/chemistQa/productApproval/index.vue 211 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/chemistQa/projectTesting/add.vue 173 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/chemistQa/projectTesting/addDetectionReport.vue 172 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/chemistQa/projectTesting/components/approval/index.vue 391 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/chemistQa/projectTesting/index.vue 308 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/chemistQa/rawMaterials/add.vue 172 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/chemistQa/rawMaterials/components/approval/index.vue 397 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/chemistQa/rawMaterials/index.vue 211 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/dataManagement/schemeManagement/addPlan.vue 684 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/dataManagement/schemeManagement/components/add-step.vue 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/dataManagement/schemeManagement/components/approvalDialog.vue 919 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/dataManagement/schemeManagement/list.vue 480 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/dataManagement/schemeManagement/stop-experiment.vue 302 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/dataManagement/suspendExperiment/components/approvalDialog.vue 345 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/dataManagement/suspendExperiment/list.vue 290 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/deliveryAssessment/QA/components/AssessmentDialog.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/deliveryAssessment/QA/index.vue 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/deliveryAssessment/chemistEvaluate/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/deliveryAssessment/experimenterJobEvaluation/index.vue 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/deliveryAssessment/reportEvaluation/components/CraftDialog.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/deliveryAssessment/reportEvaluation/index.vue 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/deliveryAssessment/technicianJobEvaluation/index.vue 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/deliveryAssessment/testingAndEvaluation/components/AssessmentDialog.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/deliveryAssessment/testingAndEvaluation/index.vue 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
culture/package.json
@@ -10,6 +10,7 @@
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "@antv/g6": "^4.8.24",
    "@tinymce/tinymce-vue": "^3.2.8",
    "aieditor": "^1.3.6",
    "axios": "^0.24.0",
culture/src/router/index.js
@@ -221,6 +221,14 @@
                title: "新增菌种传代生产谱系图",
            },
            component: () => import("../views/pedigree-chart/add"),
           },
           {
            path: 'add-pedigree',
            name: 'AddPedigree',
            meta: {
                title: "新增菌种传代生产谱系图",
            },
            component: () => import("../views/pedigree-chart/add"),
           }
        ]
    },
culture/src/views/deliveryAssessment/chemistEvaluate/add.vue
File was deleted
culture/src/views/deliveryAssessment/chemistEvaluate/index.vue
File was deleted
culture/src/views/deliveryAssessment/clinicalTrial/components/detail.vue
File was deleted
culture/src/views/deliveryAssessment/clinicalTrial/index.vue
File was deleted
culture/src/views/deliveryAssessment/experimentResults/components/detail.vue
File was deleted
culture/src/views/deliveryAssessment/processEngineerEvaluate/index.vue
File was deleted
culture/src/views/deliveryAssessment/projectTeamIntegral/detail.vue
@@ -9,11 +9,11 @@
                </div>
            </div>
            <div class="top-box-integral">
                <div :style="{ backgroundColor: ['rgba(232, 250, 246, 1)', 'rgba(255, 243, 213, 1)', 'rgba(254, 237, 220, 1)', 'rgba(239, 248, 255, 1)', 'rgba(255, 237, 238, 1)'][item - 1] }"
                    v-for="item in 5" :key="item" class="top-box-integral-card">
                    <div class="top-box-integral-card-title">{{ ['项目组总积分', '工艺工程师积分', '化验师积分', '实验员积分', '实验终止次数'][item -
                <div :style="{ backgroundColor: ['rgba(232, 250, 246, 1)', 'rgba(254, 237, 220, 1)', 'rgba(239, 248, 255, 1)', 'rgba(255, 237, 238, 1)'][item - 1] }"
                    v-for="item in 4" :key="item" class="top-box-integral-card">
                    <div class="top-box-integral-card-title">{{ ['项目组总积分', '化验师积分', '实验员积分', '实验终止次数'][item -
                        1] }}</div>
                    <div :style="{ color: ['rgba(4, 156, 154, 1)', 'rgba(255, 197, 61, 1)', 'rgba(255, 147, 0, 1)', 'rgba(23, 119, 213, 1)', 'rgba(255, 73, 85, 1)'][item - 1] }"
                    <div :style="{ color: ['rgba(4, 156, 154, 1)', 'rgba(255, 147, 0, 1)', 'rgba(23, 119, 213, 1)', 'rgba(255, 73, 85, 1)'][item - 1] }"
                        class="top-box-integral-card-num">99.9</div>
                </div>
            </div>
@@ -22,9 +22,8 @@
            <div class="integral-content-box">
                <div class="integral-content-box-left">
                    <div @click="changeActiveItem(item)" class="integral-content-box-left-item"
                        :class="actionsLeftTab == item && 'activeItem'" v-for="item in 3" :key="item">
                        <div>{{ ['工艺工程师', '化验师', '实验员'][item - 1] }}</div>
                        <div>工作内容评定</div>
                        :class="actionsLeftTab == item && 'activeItem'" v-for="item in 2" :key="item">
                        <div>{{ ['菌种工程师', '菌种实验员'][item - 1] }}</div>
                    </div>
                </div>
                <div class="integral-content-box-right">
@@ -36,7 +35,6 @@
                        <div>评定情况</div>
                        <div>开始时间</div>
                        <div>结束时间</div>
                        <div>操作</div>
                    </div>
                    <div class="integral-content-box-right-body">
                        <div v-for="item in itemList" :key="item" class="integral-content-box-right-body-item">
@@ -47,9 +45,6 @@
                            </div>
                            <div></div>
                            <div></div>
                            <div>
                                <el-button type="text">修改</el-button>
                            </div>
                        </div>
                    </div>
                </div>
@@ -67,109 +62,28 @@
            actionspPersonnel: null,
            craftList: [
                {
                    gainer: '1、项目可研报告',
                    gainer: '1、创新型课题',
                    situationOne: '课题数:',
                    situationTwo: '积分数:',
                },
                {
                    gainer: '2、项目可行报告',
                    gainer: '2、规程型课题',
                    situationOne: '课题数:',
                    situationTwo: '积分数:',
                },
                {
                    gainer: '3、项目实验室开发阶段',
                    situationOne: '实验数:',
                    gainer: '3、实验操作评定',
                    situationOne: '考核数:',
                    situationTwo: '积分数:',
                },
                {
                    gainer: '4、项目中试试验阶段',
                    situationOne: '实验数:',
                    situationTwo: '积分数:',
                },
                {
                    gainer: '5、项目生产验证试验阶段',
                    situationOne: '实验数:',
                    situationTwo: '积分数:',
                },
                {
                    gainer: '6、工艺开发工具',
                    situationOne: '课题数:',
                    situationTwo: '积分数:',
                },
                {
                    gainer: '7、验证与开发',
                    situationOne: '课题数:',
                    situationTwo: '积分数:',
                },
                {
                    gainer: '8、立项报告',
                    situationOne: '课题数:',
                    situationTwo: '积分数:',
                },
                {
                    gainer: '9、临床实验积分',
                    situationTwo: '积分数:',
                },
            ],//工艺工程师-工作内容评定
            ],//菌种工程师
            assayList: [
                {
                    gainer: '1、项目开发阶段',
                    situationOne: '实验数:',
                    gainer: '1、实验操作评定',
                    situationOne: '考核数:',
                    situationTwo: '积分数:',
                },
                {
                    gainer: '2、项目中试试验阶段',
                    situationOne: '实验数:',
                    situationTwo: '积分数:',
                },
                {
                    gainer: '3、项目生产验证试验阶段',
                    situationOne: '实验数:',
                    situationTwo: '积分数:',
                },
                {
                    gainer: '4、项目检测项、检验包评定',
                    situationOne: '课题数:',
                    situationTwo: '积分数:',
                },
                {
                    gainer: '5、中试、生产验证试验检验分析报告评定',
                    situationOne: '课题数:',
                    situationTwo: '积分数:',
                },
                {
                    gainer: '6、原辅料、包材、竞品检验分析报告评定',
                    situationOne: '课题数:',
                    situationTwo: '积分数:',
                },
                {
                    gainer: '7、产品报批及项目工作总结报告评定',
                    situationOne: '课题数:',
                    situationTwo: '积分数:',
                },
            ],//化验师-工作内容评定
            experimentList: [
                {
                    gainer: '1、项目实验室开发阶段',
                    situationOne: '实验数:',
                    situationTwo: '积分数:',
                },
                {
                    gainer: '2、项目中试试验阶段',
                    situationOne: '实验数:',
                    situationTwo: '积分数:',
                },
                {
                    gainer: '3、项目生产验证试验阶段',
                    situationOne: '实验数:',
                    situationTwo: '积分数:',
                },
                {
                    gainer: '4、其他任务',
                    situationOne: '实验数:',
                    situationTwo: '积分数:',
                },
            ],//实验员-工作内容评定
            ],//菌种实验员
        }
    },
    computed: {
@@ -179,8 +93,6 @@
                    return this.craftList
                case 2:
                    return this.assayList
                case 3:
                    return this.experimentList
                default:
                    return this.craftList
            }
culture/src/views/deliveryAssessment/restsTask/components/detail.vue
File was deleted
culture/src/views/deliveryAssessment/restsTask/index.vue
File was deleted
culture/src/views/deliveryAssessment/taskList/components/AssessmentDialog.vue
File was deleted
culture/src/views/deliveryAssessment/taskList/index.vue
File was deleted
culture/src/views/deliveryAssessment/testerWorkerEvaluate/add.vue
File was deleted
culture/src/views/deliveryAssessment/testerWorkerEvaluate/index.vue
File was deleted
culture/src/views/deliveryAssessment/testingAndEvaluation/components/AssessmentDialog.vue
File was deleted
culture/src/views/deliveryAssessment/testingAndEvaluation/index.vue
File was deleted
culture/src/views/strain-library/strain-flow-chart/index.vue
New file
@@ -0,0 +1,694 @@
<template>
  <div class="strain-flow-chart">
    <div class="toolbar">
      <el-button type="primary" size="small" @click="addNode" :disabled="!canAddNode">新增</el-button>
      <el-button size="small" @click="setGenerationPlan" :disabled="!selectedNode">设置传代计划数</el-button>
      <el-button size="small" @click="showDetail" :disabled="!selectedNode">详情</el-button>
    </div>
    <div id="mountNode"></div>
    <!-- 节点详情弹窗 -->
    <el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="500px" @close="handleDialogClose"
      :close-on-click-modal="false">
      <el-form :model="form" :rules="rules" ref="form" label-width="120px">
        <el-form-item :label="formLabel" prop="value">
          <el-input v-model="form.value" :type="inputType"></el-input>
        </el-form-item>
        <el-form-item label="废弃状态" prop="isDiscarded" v-if="showDiscarded">
          <el-switch v-model="form.isDiscarded"></el-switch>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="handleSubmit">确 定</el-button>
      </div>
    </el-dialog>
    <!-- 新增母代弹窗 -->
    <el-dialog title="新增母代" :visible.sync="addParentDialogVisible" width="500px" :close-on-click-modal="false">
      <el-form :model="parentForm" :rules="parentRules" ref="parentForm" label-width="120px">
        <el-form-item label="代传菌种编号" prop="number">
          <el-input v-model="parentForm.number"></el-input>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="addParentDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="handleAddParent">确 定</el-button>
      </div>
    </el-dialog>
    <!-- 设置传代计划数弹窗 -->
    <el-dialog title="设置传代计划数" :visible.sync="planDialogVisible" width="500px" :close-on-click-modal="false">
      <el-form :model="planForm" :rules="planRules" ref="planForm" label-width="120px">
        <el-form-item label="计划数" prop="count">
          <el-input v-model="planForm.count" type="number" :min="1"></el-input>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="planDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="handleSetPlan">确 定</el-button>
      </div>
    </el-dialog>
  </div>
</template>
<script>
import G6 from '@antv/g6';
import dagre from 'dagre';
export default {
  name: 'StrainFlowChart',
  data() {
    return {
      graph: null,
      nodeCount: 0,
      selectedNode: null,
      graphData: {
        nodes: [],
        edges: []
      },
      // 弹窗相关数据
      dialogVisible: false,
      dialogTitle: '',
      formLabel: '',
      inputType: 'text',
      showDiscarded: false,
      isAddingNode: false,
      form: {
        value: '',
        isDiscarded: false
      },
      rules: {
        value: [
          { required: true, message: '不能为空', trigger: 'blur' }
        ]
      },
      // 新增母代弹窗数据
      addParentDialogVisible: false,
      parentForm: {
        number: ''
      },
      parentRules: {
        number: [
          { required: true, message: '菌种编号不能为空', trigger: 'blur' }
        ]
      },
      // 设置传代计划数弹窗数据
      planDialogVisible: false,
      planForm: {
        count: 1
      },
      planRules: {
        count: [
          { required: true, message: '计划数不能为空', trigger: 'blur' }
        ]
      }
    };
  },
  computed: {
    canAddNode() {
      // 如果没有节点,可以新增母代
      if (this.graphData.nodes.length === 0) {
        return true;
      }
      // 如果选中了传代计划数节点,可以新增下一代
      if (this.selectedNode && this.selectedNode.label === '传代计划数') {
        return true;
      }
      return false;
    }
  },
  mounted() {
    this.initGraph();
    this.initEvents();
  },
  beforeDestroy() {
    this.graph?.destroy();
    window.removeEventListener('resize', this.handleResize);
  },
  methods: {
    initGraph() {
      const container = document.getElementById('mountNode');
      const width = container.scrollWidth;
      const height = container.scrollHeight || 600;
      // 自定义节点
      G6.registerNode('custom-node', {
        draw(cfg, group) {
          const width = 120;
          const titleHeight = 30;
          const contentHeight = 40;
          const gap = 4;
          const totalHeight = titleHeight + gap + contentHeight;
          // 根据节点状态设置颜色
          const isDiscarded = cfg.isDiscarded;
          const titleFill = isDiscarded ? 'rgba(245, 248, 250, 1)' : (cfg.selected ? 'l(0) 0:#0ACBCA 1:#049C9A' : 'l(0) 0:#0ACBCA 1:#049C9A');
          const contentFill = isDiscarded ? 'rgba(245, 248, 250, 1)' : (cfg.selected ? 'rgba(4,156,154,0.2)' : 'rgba(4,156,154,0.1)');
          const textFill = isDiscarded ? 'rgba(144, 147, 153, 1)' : '#049C9A';
          const stroke = isDiscarded ? '#DCDFE6' : (cfg.selected ? '#049C9A' : 'transparent');
          // 创建渐变
          const gradient = group.addShape('rect', {
            attrs: {
              x: -width / 2,
              y: -totalHeight / 2,
              width: width,
              height: titleHeight,
              radius: 20,
              fill: titleFill,
              cursor: 'move',
              stroke: stroke,
              lineWidth: isDiscarded ? 1 : (cfg.selected ? 2 : 0),
            },
            name: 'title-box',
          });
          // 下部分 - 内容背景
          const contentBox = group.addShape('rect', {
            attrs: {
              x: -width / 2,
              y: -totalHeight / 2 + titleHeight + gap,
              width: width,
              height: contentHeight,
              fill: contentFill,
              radius: 15,
              cursor: 'move',
              stroke: stroke,
              lineWidth: isDiscarded ? 1 : (cfg.selected ? 2 : 0),
            },
            name: 'content-box',
          });
          // 标题文本
          if (cfg.label) {
            group.addShape('text', {
              attrs: {
                text: cfg.label,
                x: 0,
                y: -totalHeight / 2 + titleHeight / 2,
                fill: isDiscarded ? 'rgba(144, 147, 153, 1)' : '#fff',
                fontSize: 12,
                textAlign: 'center',
                textBaseline: 'middle',
                fontWeight: 'bold',
                cursor: 'move',
              },
              name: 'title-text',
            });
          }
          // 内容文本
          let content = '';
          if (cfg.label === '传代计划数') {
            content = `${cfg.planCount || 0}`;
          } else if (cfg.number) {
            content = cfg.label === '母代' ? `代传菌种编号:${cfg.number}` : `接种菌种编号:${cfg.number}`;
          }
          if (content) {
            group.addShape('text', {
              attrs: {
                text: content,
                x: 0,
                y: -totalHeight / 2 + titleHeight + gap + contentHeight / 2,
                fill: textFill,
                fontSize: 10,
                textAlign: 'center',
                textBaseline: 'middle',
                cursor: 'move',
              },
              name: 'content-text',
            });
          }
          return gradient;
        },
        getAnchorPoints() {
          return [
            [0.5, 0], // 上
            [1, 0.5], // 右
            [0.5, 1], // 下
            [0, 0.5], // 左
          ];
        },
        setState(name, value, item) {
          // 移除悬浮效果,保持节点样式始终一致
        },
      });
      this.graph = new G6.Graph({
        container: 'mountNode',
        width,
        height,
        fitView: true,
        fitViewPadding: 30,
        animate: false,
        enabledStack: false,
        renderer: 'canvas',
        minZoom: 0.3,
        maxZoom: 2,
        defaultZoom: 1,
        layout: {
          type: 'dagre',
          rankdir: 'LR',
          align: 'UL',
          nodesep: 30,  // 减小节点间距
          ranksep: 50,  // 减小层级间距
          controlPoints: true,
        },
        modes: {
          default: [
            {
              type: 'drag-canvas',
              enableOptimize: true,
              direction: 'both',
              scalableRange: 0.1,
              dragTimesOfScale: 0.1,
              onlyChangeComputeZoom: true,
            },
            {
              type: 'zoom-canvas',
              sensitivity: 1.5,
              enableOptimize: true,
            },
            {
              type: 'drag-node',
              enableDelegate: true,
              delegateStyle: {
                fill: '#f3f3f3',
                stroke: '#ccc',
                opacity: 0.5,
              },
              updateEdge: false,
              enableOptimize: true,
              optimizeZoom: 0.7,
              damping: 0.1,
            }
          ]
        },
        defaultNode: {
          type: 'custom-node',
          style: {
            fill: 'l(0) 0:#0ACBCA 1:#049C9A',
          },
        },
        defaultEdge: {
          type: 'cubic-horizontal',
          style: {
            stroke: 'rgba(4, 156, 154, 1)',
            lineWidth: 1,
            opacity: 0.5,
            endArrow: {
              path: G6.Arrow.triangle(6, 6),
              fill: 'rgba(4, 156, 154, 1)',
              stroke: 'rgba(4, 156, 154, 1)',
            },
          },
        },
        optimizeEdge: true,
        optimizeLayoutAnimation: true,
      });
      const canvas = this.graph.get('canvas');
      canvas.set('localRefresh', false);
      canvas.set('autoDraw', true);
      canvas.set('animating', false);
      let throttleTimer = null;
      const throttleInterval = 16;
      this.graph.on('node:dragstart', () => {
        canvas.set('localRefresh', false);
        this.graph.get('canvas').draw();
      });
      this.graph.on('node:drag', (e) => {
        if (throttleTimer) return;
        throttleTimer = setTimeout(() => {
          const model = e.item.get('model');
          const edges = this.graph.getEdges().filter(edge => {
            const source = edge.getSource();
            const target = edge.getTarget();
            return source.get('id') === model.id || target.get('id') === model.id;
          });
          edges.forEach(edge => {
            this.graph.refreshItem(edge);
          });
          throttleTimer = null;
        }, throttleInterval);
      });
      this.graph.on('node:dragend', (e) => {
        if (throttleTimer) {
          clearTimeout(throttleTimer);
          throttleTimer = null;
        }
        const model = e.item.get('model');
        const edges = this.graph.getEdges().filter(edge => {
          const source = edge.getSource();
          const target = edge.getTarget();
          return source.get('id') === model.id || target.get('id') === model.id;
        });
        edges.forEach(edge => {
          this.graph.refreshItem(edge);
        });
        canvas.set('localRefresh', true);
        this.graph.get('canvas').draw();
      });
      this.graph.data(this.graphData);
      this.graph.render();
      let debounceTimer = null;
      this.graph.on('afterchange', () => {
        if (debounceTimer) clearTimeout(debounceTimer);
        debounceTimer = setTimeout(() => {
          if (!canvas.get('destroyed')) {
            canvas.draw();
          }
        }, 16);
      });
    },
    initEvents() {
      // 监听窗口大小变化
      window.addEventListener('resize', this.handleResize);
      // 节点点击事件
      this.graph.on('node:click', (evt) => {
        const node = evt.item;
        const nodeModel = node.getModel();
        // 如果节点已废弃,不允许任何操作
        if (nodeModel.isDiscarded) {
          this.$message.warning('该节点已废弃,不能进行操作');
          return;
        }
        // 更新选中节点
        this.selectedNode = nodeModel;
        // 更新节点选中状态
        this.graphData.nodes.forEach(n => {
          n.selected = n.id === nodeModel.id;
        });
        this.graph.changeData(this.graphData);
      });
      // 画布点击事件,取消选中节点
      this.graph.on('canvas:click', () => {
        this.selectedNode = null;
        this.graphData.nodes.forEach(n => {
          n.selected = false;
        });
        this.graph.changeData(this.graphData);
      });
    },
    handleResize() {
      if (this.graph) {
        const container = document.getElementById('mountNode');
        const width = container.scrollWidth;
        const height = container.scrollHeight || 600;
        this.graph.changeSize(width, height);
      }
    },
    addNode() {
      // 如果没有节点,新增母代
      if (this.graphData.nodes.length === 0) {
        this.addParentDialogVisible = true;
        this.parentForm.number = '';
        return;
      }
      // 如果选中了传代计划数节点,新增下一代
      if (this.selectedNode && this.selectedNode.label === '传代计划数') {
        const nodeModel = this.selectedNode;
        // 检查是否已达到计划数
        if (nodeModel.currentCount >= nodeModel.planCount) {
          this.$message.warning('已达到计划数,不能再添加');
          return;
        }
        // 获取父节点
        const parentEdge = this.graphData.edges.find(e => e.target === nodeModel.id);
        const parentNode = this.graphData.nodes.find(n => n.id === parentEdge.source);
        // 如果父节点是孙代,不允许添加
        if (parentNode.label === '孙代') {
          this.$message.warning('孙代节点不能再生成下一代');
          return;
        }
        const isParent = parentNode.label === '母代';
        const nextLevel = isParent ? '子代' : '孙代';
        // 使用对话框输入节点信息
        this.dialogVisible = true;
        this.dialogTitle = `新增${nextLevel}`;
        this.formLabel = '接种菌种编号';
        this.form.value = '';
        this.form.isDiscarded = false;
        this.showDiscarded = true;
        this.inputType = 'text';
        this.isAddingNode = true;
      }
    },
    handleAddParent() {
      this.$refs.parentForm.validate((valid) => {
        if (valid) {
          const parentId = `parent-${++this.nodeCount}`;
          const container = document.getElementById('mountNode');
          const height = container.scrollHeight || 600;
          this.graphData.nodes.push({
            id: parentId,
            label: '母代',
            number: this.parentForm.number.trim(),
            x: 200,
            y: 200,
            style: {
              fill: '#00B5AA',
            },
          });
          this.graph.changeData(this.graphData);
          this.$message.success('母代节点添加成功');
          this.addParentDialogVisible = false;
        }
      });
    },
    handleDialogClose() {
      this.$refs.form.resetFields();
    },
    handleSubmit() {
      this.$refs.form.validate((valid) => {
        if (valid) {
          if (this.isAddingNode) {
            // 新增节点的处理逻辑
            const nodeModel = this.selectedNode;
            const parentEdge = this.graphData.edges.find(e => e.target === nodeModel.id);
            const parentNode = this.graphData.nodes.find(n => n.id === parentEdge.source);
            const isParent = parentNode.label === '母代';
            const nextLevel = isParent ? '子代' : '孙代';
            const childId = `child-${++this.nodeCount}`;
            this.graphData.nodes.push({
              id: childId,
              label: nextLevel,
              number: this.form.value.trim(),
              isDiscarded: this.form.isDiscarded,
              style: {
                fill: this.form.isDiscarded ? '#999' : '#00B5AA',
                opacity: this.form.isDiscarded ? 0.3 : (isParent ? 0.6 : 0.4),
              },
            });
            this.graphData.edges.push({
              source: nodeModel.id,
              target: childId,
              style: {
                stroke: 'rgba(4, 156, 154, 1)',
                lineWidth: 1,
              },
            });
            const nodeIndex = this.graphData.nodes.findIndex(n => n.id === nodeModel.id);
            this.graphData.nodes[nodeIndex].currentCount++;
            this.graph.changeData(this.graphData);
            this.$message.success(`${nextLevel}添加成功`);
            this.isAddingNode = false;
            this.dialogVisible = false;
          } else {
            // 编辑节点的处理逻辑
            const nodeModel = this.selectedNode;
            const nodeIndex = this.graphData.nodes.findIndex(n => n.id === nodeModel.id);
            if (nodeIndex > -1) {
              if (nodeModel.label === '传代计划数') {
                this.graphData.nodes[nodeIndex].planCount = parseInt(this.form.value);
              } else {
                this.graphData.nodes[nodeIndex].number = this.form.value.trim();
                if (this.showDiscarded) {
                  this.graphData.nodes[nodeIndex].isDiscarded = this.form.isDiscarded;
                  // 如果设置为废弃状态,同时废弃所有子节点
                  if (this.form.isDiscarded) {
                    const discardChildren = (parentId) => {
                      const childEdges = this.graphData.edges.filter(e => e.source === parentId);
                      childEdges.forEach(edge => {
                        const childNode = this.graphData.nodes.find(n => n.id === edge.target);
                        if (childNode) {
                          const childIndex = this.graphData.nodes.findIndex(n => n.id === childNode.id);
                          if (childIndex > -1) {
                            this.graphData.nodes[childIndex].isDiscarded = true;
                            this.graphData.nodes[childIndex].style.fill = '#999';
                            this.graphData.nodes[childIndex].style.opacity = 0.3;
                            // 递归处理子节点的子节点
                            discardChildren(childNode.id);
                          }
                        }
                      });
                    };
                    discardChildren(nodeModel.id);
                  }
                }
              }
              this.graph.changeData(this.graphData);
              this.$message.success('修改成功');
              this.dialogVisible = false;
            }
          }
        }
      });
    },
    setGenerationPlan() {
      if (!this.selectedNode) {
        this.$message.warning('请先选择节点');
        return;
      }
      const nodeModel = this.selectedNode;
      if (nodeModel.label === '孙代') {
        this.$message.warning('孙代节点不能再生成传代计划数');
        return;
      }
      if (nodeModel.label === '传代计划数') {
        this.$message.warning('传代计划数节点不能再设置计划数');
        return;
      }
      const hasGenerationNode = this.graphData.edges.some(e =>
        e.source === nodeModel.id &&
        this.graphData.nodes.some(n => n.id === e.target && n.label === '传代计划数')
      );
      if (hasGenerationNode) {
        this.$message.warning('该节点已经存在传代计划数节点');
        return;
      }
      this.planDialogVisible = true;
      this.planForm.count = 1;
    },
    handleSetPlan() {
      this.$refs.planForm.validate((valid) => {
        if (valid) {
          const nodeModel = this.selectedNode;
          const planCount = this.planForm.count;
          const generationId = `generation-${++this.nodeCount}`;
          this.graphData.nodes.push({
            id: generationId,
            label: '传代计划数',
            planCount: planCount,
            currentCount: 0,
            style: {
              fill: '#00B5AA',
            },
          });
          this.graphData.edges.push({
            source: nodeModel.id,
            target: generationId,
            style: {
              stroke: 'rgba(4, 156, 154, 1)',
              lineWidth: 1,
            },
          });
          this.graph.changeData(this.graphData);
          this.$message.success('传代计划数设置成功');
          this.planDialogVisible = false;
        }
      });
    },
    showDetail() {
      if (!this.selectedNode) {
        this.$message.warning('请先选择节点');
        return;
      }
      const nodeModel = this.selectedNode;
      if (nodeModel.label === '母代') {
        this.dialogTitle = '母代详情';
        this.formLabel = '代传菌种编号';
        this.form.value = nodeModel.number || '';
        this.showDiscarded = false;
        this.inputType = 'text';
      } else if (nodeModel.label === '子代' || nodeModel.label === '孙代') {
        this.dialogTitle = `${nodeModel.label}详情`;
        this.formLabel = '接种菌种编号';
        this.form.value = nodeModel.number || '';
        this.form.isDiscarded = nodeModel.isDiscarded || false;
        this.showDiscarded = true;
        this.inputType = 'text';
      } else if (nodeModel.label === '传代计划数') {
        this.dialogTitle = '传代计划数详情';
        this.formLabel = '计划数';
        this.form.value = nodeModel.planCount || '';
        this.showDiscarded = false;
        this.inputType = 'number';
      }
      this.dialogVisible = true;
    }
  },
};
</script>
<style lang="less" scoped>
.strain-flow-chart {
  width: 100%;
  height: 100%;
  min-height: 600px;
  background-color: #fff;
  padding: 24px;
  border-radius: 4px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  display: flex;
  flex-direction: column;
  .toolbar {
    margin-bottom: 16px;
    display: flex;
    gap: 12px;
    .el-button {
      margin-right: 0;
    }
  }
  #mountNode {
    flex: 1;
    width: 100%;
    min-height: 500px;
    border: 1px solid #eee;
    border-radius: 4px;
  }
}
</style>
culture/src/views/strainReportLibrary/reportLibraryOne/add.vue
New file
@@ -0,0 +1,194 @@
<template>
    <div>
        <Card>
            <div class="header-title" style="width: 100%;">
                <div class="header-title-left">
                    <img src="@/assets/public/headercard.png" />
                    <div>所属项目组</div>
                </div>
                <div class="header-title-right">
                    <el-button @click="showChoose = true" class="el-icon-circle-plus-outline" type="primary">
                        选择项目组</el-button>
                </div>
            </div>
            <Table :height="null" :queryForm="queryForm" :total="0">
                <template>
                    <el-table-column prop="name" label="项目组名称" />
                    <el-table-column prop="age" label="项目负责人" />
                    <el-table-column prop="age" label="项目组成员" />
                    <el-table-column prop="age" label="创建时间" />
                </template>
            </Table>
            <el-form ref="form" :model="form" :rules="rules" inline label-position="top" style="margin-top: 38px">
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>报告编号</div>
                    </div>
                </div>
                <el-form-item prop="name" style="margin-top: 38px">
                    <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                </el-form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>报告名称</div>
                    </div>
                </div>
                <el-form-item prop="name" style="margin-top: 38px">
                    <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                </el-form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>报告正文</div>
                    </div>
                </div>
                <el-form-item prop="name" style="margin-top: 38px">
                    <ai-editor v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                </el-form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>附件</div>
                    </div>
                </div>
                <el-form-item prop="name" style="margin-top: 38px">
                    <el-upload action="https://jsonplaceholder.typicode.com/posts/" :file-list="fileList">
                        <el-button size="small" type="primary">点击上传</el-button>
                    </el-upload>
                </el-form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>菌种实验员操作评定</div>
                    </div>
                </div>
                <div class="header-title" style="width: 100%;display: flex; align-items: center;">
                <div class="header-title-left mt-unset" >
                    <!-- <img src="@/assets/public/headercard.png" /> -->
                    <div>菌种实验员</div>
                </div>
                <div class="header-title-right">
                    <el-button @click="showChoose = true" class="el-icon-circle-plus-outline" type="primary">
                        选择菌种实验员</el-button>
                </div>
            </div>
                <div class="end-btn" style="margin-top: 38px">
                    <el-button type="primary">发送</el-button>
                    <el-button type="default">存草稿</el-button>
                </div>
            </el-form>
        </Card>
    </div>
</template>
<script>
import { Card } from 'element-ui';
import AiEditor from '@/components/AiEditor'
export default {
    components: { AiEditor },
    data() {
        return {
            form: {
                planName: "",
                planCode: "",
                stage: "",
                creator: "",
                createTime: "",
                approvalComment: "",
                status: "pending",
                approver: "",
                approveTime: ""
            },
            fileList: [], // 附件列表
            showChoose: false,
            radio1: 1,
            rules: {},
            status: "1",
            remark: "",
            queryForm: {
            }
        }
    }
}
</script>
<style lang="less" scoped>
.mt-unset{
    margin-top: unset !important;
}
.header-title {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 20px;
    gap: 13px;
    .header-title-left {
        display: flex;
        align-items: center;
        gap: 13px;
        margin-top: 38px;
        img {
            width: 12px;
            height: 19px;
        }
        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;
            }
        }
        span {
            flex-shrink: 0;
            font-weight: bold;
            font-size: 18px;
            color: #222222;
            line-height: 27px;
            font-family: "Source Han Sans CN Bold Bold";
        }
    }
    .header-title-left :first-child {
        margin-top: 0;
    }
}
.header-title:first-child {
    .header-title-left {
        margin-top: 0;
    }
}
.end-btn{
    display: flex;
    align-items: center;
    gap: 10px;
    button{
        width: 180px;
        height: 36px;
        // background: #409EFF;
    }
}
</style>
culture/src/views/strainReportLibrary/reportLibraryOne/components/approval/index.vue
New file
@@ -0,0 +1,393 @@
<template>
    <el-dialog :title="dialogTitle"  :visible.sync="visible" width="80%" po :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" style="width: 100%;">
                            <div class="header-title-left">
                                <img src="@/assets/public/headercard.png" />
                                <div>所属项目组</div>
                            </div>
                        </div>
                        <Table :height="null" :queryForm="queryForm" :total="0" @currentChange="handleCurrentChange"
                            @sizeChange="handleSizeChange">
                            <template>
                                <el-table-column prop="name" label="项目组名称" />
                                <el-table-column prop="age" label="项目负责人" />
                                <el-table-column prop="age" label="项目组成员" />
                                <el-table-column prop="age" label="创建时间" />
                            </template>
                        </Table>
                        <el-form ref="form" :model="form" :rules="rules" inline label-position="top"
                            style="margin-top: 38px">
                            <div class="header-title" style="width: 100%;">
                                <div class="header-title-left">
                                    <img src="@/assets/public/headercard.png" />
                                    <div>报告编号</div>
                                </div>
                            </div>
                            <form-item prop="name" style="margin-top: 38px">
                                <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </form-item>
                            <div class="header-title" style="width: 100%;">
                                <div class="header-title-left">
                                    <img src="@/assets/public/headercard.png" />
                                    <div>报告名称</div>
                                </div>
                            </div>
                            <form-item prop="name" style="margin-top: 38px">
                                <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </form-item>
                            <div class="header-title" style="width: 100%;">
                                <div class="header-title-left">
                                    <img src="@/assets/public/headercard.png" />
                                    <div>报告正文</div>
                                </div>
                            </div>
                            <form-item prop="name" style="margin-top: 38px">
                                <ai-editor v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </form-item>
                        </el-form>
                    </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>
            </div>
        </div>
        <div class="approval-dialog-approve">
            <el-row :span="24">
                <el-col :span="12">
                    <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>
                </el-col>
                <el-col :span="12">
                    <div class="remark">
                        <div class="remark-title">审批意见</div>
                        <el-input type="textarea" v-model="remark" placeholder="请输入审批意见" />
                    </div>
                </el-col>
            </el-row>
        </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>
</template>
<script>
import ApprovalProcess from '@/components/approvalProcess'
import AiEditor from '@/components/AiEditor'
export default {
    name: "ApprovalDialog",
    components: {
        ApprovalProcess,
        AiEditor
    },
    props: {
        visible: {
            type: Boolean,
            default: false,
        },
        type: {
            type: String,
            default: "approve", // approve-审批,view-查看
        },
        data: {
            type: Object,
            default: () => ({}),
        },
    },
    data() {
        return {
            form: {
                planName: "",
                planCode: "",
                stage: "",
                creator: "",
                createTime: "",
                approvalComment: "",
                status: "pending",
                approver: "",
                approveTime: ""
            },
            radio1: 1,
            rules: {},
            status: "1",
            remark: "",
        };
    },
    computed: {
        dialogTitle() {
            return this.type === "approve" ? "审批" : "审批详情";
        },
    },
    watch: {
        data: {
            handler(val) {
                if (val) {
                    this.form = { ...val };
                }
            },
            immediate: true,
        },
    },
    methods: {
        handleClose() {
            this.$emit("close");
            this.form.approvalComment = "";
        },
        handleApprove() {
            if (!this.form.approvalComment) {
                this.$message.warning("请输入审批意见");
                return;
            }
            this.$emit("approve", {
                ...this.form,
                status: "approved",
            });
        },
        handleReject() {
            if (!this.form.approvalComment) {
                this.$message.warning("请输入审批意见");
                return;
            }
            this.$emit("reject", {
                ...this.form,
                status: "rejected",
            });
        },
    },
};
</script>
<style scoped lang="less">
::v-deep .el-dialog__header {
    border-bottom: 1px solid #e4e7ed;
}
.approval-dialog {
    display: flex;
    height: 40vh;
    .approval-content {
        flex: 3;
        margin-right: 20px;
        background: #ffffff;
        box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08);
        border-radius: 10px;
    }
    .approval-flow {
        padding: 40px 20px;
        // width: 405px;
        flex: 2;
        background: #ffffff;
        box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08);
        border-radius: 10px;
        .flow-title {
            font-size: 16px;
            font-weight: bold;
            margin-bottom: 20px;
            color: #303133;
        }
        .flow-content {
            height: calc(100% - 40px);
            overflow-y: auto;
            .el-form--inline .el-form-item {
                margin-right: 83px;
            }
        }
    }
}
.approval-content-card {
    height: calc(100% - 100px) !important;
    box-shadow: none !important;
}
.header-title {
    // display: flex;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 20px;
    gap: 13px;
    .header-title-left {
        display: flex;
        align-items: center;
        gap: 13px;
        margin-top: 38px;
        img {
            width: 12px;
            height: 19px;
        }
        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;
            }
        }
        span {
            flex-shrink: 0;
            font-weight: bold;
            font-size: 18px;
            color: #222222;
            line-height: 27px;
            font-family: "Source Han Sans CN Bold Bold";
        }
    }
    .header-title-left :first-child {
        margin-top: 0;
    }
}
.header-title:first-child {
    .header-title-left {
        margin-top: 0;
    }
}
.item-title {
    padding-left: 25px;
    span {
        flex-shrink: 0;
        font-weight: bold;
        font-size: 14px;
        color: #222222;
        line-height: 27px;
        font-family: "Source Han Sans CN Bold Bold";
        margin: 18px 0;
        &:before {
            content: "*";
            color: #f56c6c;
            margin-right: 4px;
        }
    }
}
.approval-dialog-approve {
    padding: 38px 20px;
    // display: flex;
    align-content: center;
    .status {
        margin-right: 40px;
        max-width: 60%;
    }
    //   align-items: center;
    .status-title {
        color: #222222;
        font-family: "SourceHanSansCN-Medium";
        line-height: 14px;
        margin-bottom: 16px;
    }
    .status-content {
        display: flex;
        align-items: center;
        width: 100%;
        gap: 16px;
        background: #ffffff;
        border-radius: 10px;
        border: 1px solid rgba(4, 156, 154, 0.5);
        .resolve {
            border-radius: 10px;
            flex: 1;
            font-size: 16px;
            // padding: 5px 55px;
            font-weight: 400;
            color: #333333;
            cursor: pointer;
            line-height: 32px;
            display: flex;
            align-items: center;
            justify-content: center
        }
        .reject {
            flex: 1;
            border-radius: 10px;
            font-size: 16px;
            line-height: 32px;
            // padding: 5px 55px;
            font-weight: 400;
            color: #333333;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center
        }
        .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;
    }
}
.dialog-footer {
    align-items: center;
    display: flex;
    justify-content: center;
    gap: 20px;
    button {
        width: 150px;
    }
}
</style>
culture/src/views/strainReportLibrary/reportLibraryOne/index.vue
New file
@@ -0,0 +1,230 @@
<template>
    <div class="list">
        <el-card class="header-box">
            <div class="box-title">
                <img src="@/assets/public/notice.png" class="header-icon"> <span>设立课题规则</span>
            </div>
            <div class="header-content">
                <p>1、根据可研报告、产品构思设计的工艺研究路线,一条工艺路线设立一个课题。如果一个课题中有多个化合物需要开发研究,则每个化合物作为一个分题;分题归集到该课题中,最终形成课题报告。不同课题报告中的分题不能重复使用。
                </p>
                <p>2、在可行研究阶段,工艺开发升级,重新规划工艺研究路线,则以新规划的工艺路线方案来设定课题。</p>
            </div>
        </el-card>
        <TableCustom :queryForm="queryForm" :total="total" @currentChange="handleCurrentChange"
            @sizeChange="handleSizeChange">
            <template #search>
                <el-form :model="form" label-width="auto" inline>
                    <el-form-item label="所属项目组:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="报告名称:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="创建日期:">
                        <el-date-picker v-model="form.date" type="daterange" range-separator="至"
                            start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
                    </el-form-item>
                    <el-form-item label="状态:">
                        <el-select v-model="form.status" placeholder="请选择">
                            <el-option label="待审核" value="1"></el-option>
                            <el-option label="已通过" value="0"></el-option>
                            <el-option label="已驳回" value="2"></el-option>
                        </el-select>
                    </el-form-item>
                    <el-form-item label="" style="margin-left: 63px;">
                        <el-button type="default" style="margin-right: 10px;">重置</el-button>
                        <el-button type="primary">查询</el-button>
                    </el-form-item>
                </el-form>
            </template>
            <template #setting>
                <el-button @click="handleAddProject" class="el-icon-plus" type="primary">
                    新增报告</el-button>
                <div class="table-setting">
                    <div class="table-title">
                        报告列表
                    </div>
                    <div class="table-tit">
                        草稿箱
                    </div>
                </div>
            </template>
            <template #table>
                <el-table-column prop="name" label="所属项目组" />
                <el-table-column prop="age" label="报告编号" />
                <el-table-column prop="age" label="报告名称" />
                <el-table-column prop="age" label="菌种实验员" />
                <el-table-column prop="age" label="创建人" />
                <el-table-column prop="age" label="创建时间" />
                <el-table-column prop="age" label="状态">
                    <template #default="{ row }">
                        <el-tag v-if="row.status == 1" type="success">待审核</el-tag>
                        <el-tag v-else-if="row.status == 0" type="success">已通过</el-tag>
                        <el-tag v-else type="danger">已驳回</el-tag>
                    </template>
                </el-table-column>
                <el-table-column prop="age" label="操作">
                    <template #default="{ row }">
                        <el-button type="text">审核</el-button>
                        <el-button type="text">详情</el-button>
                    </template>
                </el-table-column>
            </template>
        </TableCustom>
        <Approval :visible="showApproval" @close="showApproval = false" />
        <ShowDelConfirm :show="showDelConfirm" @close="showDelConfirm = false" @confirm="handleDelConfirm" />
        <ShowDelConfirm :title="changeStatusTitle" :tip="changeStatusTip" :show="changeStatus"
            @close="changeStatus = false" @confirm="handleChangeStatusConfirm" />
    </div>
</template>
<script>
import Approval from './components/approval'
export default {
    name: 'ProjectList',
    components: {
        Approval
    },
    data() {
        return {
            form: {
                name: ''
            },
            showDelConfirm: false,
            rowId: '',
            changeStatus: false,
            showApproval: false,
            changeStatusTitle: '',
            changeStatusTip: '',
            queryForm: {
                pageSize: 10,
                pageNum: 1
            },
            total: 0
        }
    },
    methods: {
        handleAddProject() {
            this.$router.push('/strainReportLibrary/add')
        },
        handleDel(row) {
            this.rowId = row.id
            this.showDelConfirm = true
        },
        handleDelConfirm() {
            this.showDelConfirm = false
            this.msgsuccess('删除成功')
            this.rowId = ''
            this.getList()
        },
        handleChangeStatus(row, status) {
            this.rowId = row.id
            this.changeStatusTitle = status == 1 ? '确认要封存这个项目组吗?' : '确认要解封该项目组吗?'
            this.changeStatusTip = status == 1 ? '封存后项目组内人员看不到数据,审批人仍然可见数据。' : '解封后项目组内人员数据恢复。'
            this.changeStatus = true
        },
        handleChangeStatusConfirm() {
            this.changeStatus = false
            this.msgsuccess('操作成功')
            this.rowId = ''
            this.changeStatusTitle = ''
            this.changeStatusTip = ''
            this.getList()
        },
        handleCurrentChange(page) {
            this.queryForm.pageNum = page
            this.getList()
        },
        handleSizeChange(size) {
            this.queryForm.pageSize = size
            this.getList()
        },
        getList() {
        }
    }
}
</script>
<style scoped lang="less">
.el-icon-plus{
    margin-bottom: 20px;
}
.header-content {
    font-family: PingFangSC, PingFang SC;
    font-weight: 400;
    font-size: 14px;
    color: rgba(0, 0, 0, 0.88);
    margin-left: 30px;
}
.box-title {
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #222222;
    line-height: 27px;
    display: flex;
    align-items: center;
}
.header-icon {
    width: 20px;
    height: 20px;
    margin-right: 10px;
}
.header-box {
    border-radius: 16px;
    margin-bottom: 30px;
}
.table-setting {
    display: flex;
    gap: 14px;
}
.table-tit {
    background: #FAFAFC;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #DCDFE6;
    width: 166px;
    height: 50px;
    background: #FFFFFF;
    border-radius: 8px 8px 0px 0px;
    display: flex;
    align-items: center;
    justify-content: center;
    // margin-bottom: 21px;
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #049C9A;
    line-height: 27px;
}
.list {
    height: 100%;
}
.table-title {
    width: 166px;
    height: 50px;
    background: #FFFFFF;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #049C9A;
    display: flex;
    align-items: center;
    justify-content: center;
    // margin-bottom: 21px;
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #049C9A;
    line-height: 27px;
}
</style>
culture/src/views/strainReportLibrary/reportLibraryOneFour/add.vue
New file
@@ -0,0 +1,173 @@
<template>
    <div>
        <Card>
            <div class="header-title" style="width: 100%;">
                <div class="header-title-left">
                    <img src="@/assets/public/headercard.png" />
                    <div>所属项目组</div>
                </div>
                <div class="header-title-right">
                    <el-button @click="showChoose = true" class="el-icon-circle-plus-outline" type="primary">
                        选择项目组</el-button>
                </div>
            </div>
            <Table :height="null" :queryForm="queryForm" :total="0">
                <template>
                    <el-table-column prop="name" label="项目组名称" />
                    <el-table-column prop="age" label="项目负责人" />
                    <el-table-column prop="age" label="项目组成员" />
                    <el-table-column prop="age" label="创建时间" />
                </template>
            </Table>
            <el-form ref="form" :model="form" :rules="rules" inline label-position="top" style="margin-top: 38px">
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>报告编号</div>
                    </div>
                </div>
                <form-item prop="name" style="margin-top: 38px">
                    <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                </form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>报告名称</div>
                    </div>
                </div>
                <form-item prop="name" style="margin-top: 38px">
                    <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                </form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>报告正文</div>
                    </div>
                </div>
                <form-item prop="name" style="margin-top: 38px">
                    <ai-editor v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                </form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>附件</div>
                    </div>
                </div>
                <form-item prop="name" style="margin-top: 38px">
                    <el-upload action="https://jsonplaceholder.typicode.com/posts/" :file-list="fileList">
                        <el-button size="small" type="primary">点击上传</el-button>
                    </el-upload>
                </form-item>
                <div class="end-btn" style="margin-top: 38px">
                    <el-button type="primary">发送</el-button>
                    <el-button type="default">存草稿</el-button>
                </div>
            </el-form>
        </Card>
    </div>
</template>
<script>
import { Card } from 'element-ui';
import AiEditor from '@/components/AiEditor'
export default {
    components: { AiEditor },
    data() {
        return {
            form: {
                planName: "",
                planCode: "",
                stage: "",
                creator: "",
                createTime: "",
                approvalComment: "",
                status: "pending",
                approver: "",
                approveTime: ""
            },
            fileList: [], // 附件列表
            showChoose: false,
            radio1: 1,
            rules: {},
            status: "1",
            remark: "",
            queryForm: {
            }
        }
    }
}
</script>
<style lang="less" scoped>
.header-title {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 20px;
    gap: 13px;
    .header-title-left {
        display: flex;
        align-items: center;
        gap: 13px;
        margin-top: 38px;
        img {
            width: 12px;
            height: 19px;
        }
        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;
            }
        }
        span {
            flex-shrink: 0;
            font-weight: bold;
            font-size: 18px;
            color: #222222;
            line-height: 27px;
            font-family: "Source Han Sans CN Bold Bold";
        }
    }
    .header-title-left :first-child {
        margin-top: 0;
    }
}
.header-title:first-child {
    .header-title-left {
        margin-top: 0;
    }
}
.end-btn{
    display: flex;
    align-items: center;
    gap: 10px;
    button{
        width: 180px;
        height: 36px;
        // background: #409EFF;
    }
}
</style>
culture/src/views/strainReportLibrary/reportLibraryOneFour/components/approval/index.vue
New file
@@ -0,0 +1,393 @@
<template>
    <el-dialog :title="dialogTitle"  :visible.sync="visible" width="80%" po :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" style="width: 100%;">
                            <div class="header-title-left">
                                <img src="@/assets/public/headercard.png" />
                                <div>所属项目组</div>
                            </div>
                        </div>
                        <Table :height="null" :queryForm="queryForm" :total="0" @currentChange="handleCurrentChange"
                            @sizeChange="handleSizeChange">
                            <template>
                                <el-table-column prop="name" label="项目组名称" />
                                <el-table-column prop="age" label="项目负责人" />
                                <el-table-column prop="age" label="项目组成员" />
                                <el-table-column prop="age" label="创建时间" />
                            </template>
                        </Table>
                        <el-form ref="form" :model="form" :rules="rules" inline label-position="top"
                            style="margin-top: 38px">
                            <div class="header-title" style="width: 100%;">
                                <div class="header-title-left">
                                    <img src="@/assets/public/headercard.png" />
                                    <div>报告编号</div>
                                </div>
                            </div>
                            <form-item prop="name" style="margin-top: 38px">
                                <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </form-item>
                            <div class="header-title" style="width: 100%;">
                                <div class="header-title-left">
                                    <img src="@/assets/public/headercard.png" />
                                    <div>报告名称</div>
                                </div>
                            </div>
                            <form-item prop="name" style="margin-top: 38px">
                                <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </form-item>
                            <div class="header-title" style="width: 100%;">
                                <div class="header-title-left">
                                    <img src="@/assets/public/headercard.png" />
                                    <div>报告正文</div>
                                </div>
                            </div>
                            <form-item prop="name" style="margin-top: 38px">
                                <ai-editor v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </form-item>
                        </el-form>
                    </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>
            </div>
        </div>
        <div class="approval-dialog-approve">
            <el-row :span="24">
                <el-col :span="12">
                    <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>
                </el-col>
                <el-col :span="12">
                    <div class="remark">
                        <div class="remark-title">审批意见</div>
                        <el-input type="textarea" v-model="remark" placeholder="请输入审批意见" />
                    </div>
                </el-col>
            </el-row>
        </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>
</template>
<script>
import ApprovalProcess from '@/components/approvalProcess'
import AiEditor from '@/components/AiEditor'
export default {
    name: "ApprovalDialog",
    components: {
        ApprovalProcess,
        AiEditor
    },
    props: {
        visible: {
            type: Boolean,
            default: false,
        },
        type: {
            type: String,
            default: "approve", // approve-审批,view-查看
        },
        data: {
            type: Object,
            default: () => ({}),
        },
    },
    data() {
        return {
            form: {
                planName: "",
                planCode: "",
                stage: "",
                creator: "",
                createTime: "",
                approvalComment: "",
                status: "pending",
                approver: "",
                approveTime: ""
            },
            radio1: 1,
            rules: {},
            status: "1",
            remark: "",
        };
    },
    computed: {
        dialogTitle() {
            return this.type === "approve" ? "审批" : "审批详情";
        },
    },
    watch: {
        data: {
            handler(val) {
                if (val) {
                    this.form = { ...val };
                }
            },
            immediate: true,
        },
    },
    methods: {
        handleClose() {
            this.$emit("close");
            this.form.approvalComment = "";
        },
        handleApprove() {
            if (!this.form.approvalComment) {
                this.$message.warning("请输入审批意见");
                return;
            }
            this.$emit("approve", {
                ...this.form,
                status: "approved",
            });
        },
        handleReject() {
            if (!this.form.approvalComment) {
                this.$message.warning("请输入审批意见");
                return;
            }
            this.$emit("reject", {
                ...this.form,
                status: "rejected",
            });
        },
    },
};
</script>
<style scoped lang="less">
::v-deep .el-dialog__header {
    border-bottom: 1px solid #e4e7ed;
}
.approval-dialog {
    display: flex;
    height: 40vh;
    .approval-content {
        flex: 3;
        margin-right: 20px;
        background: #ffffff;
        box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08);
        border-radius: 10px;
    }
    .approval-flow {
        padding: 40px 20px;
        // width: 405px;
        flex: 2;
        background: #ffffff;
        box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08);
        border-radius: 10px;
        .flow-title {
            font-size: 16px;
            font-weight: bold;
            margin-bottom: 20px;
            color: #303133;
        }
        .flow-content {
            height: calc(100% - 40px);
            overflow-y: auto;
            .el-form--inline .el-form-item {
                margin-right: 83px;
            }
        }
    }
}
.approval-content-card {
    height: calc(100% - 100px) !important;
    box-shadow: none !important;
}
.header-title {
    // display: flex;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 20px;
    gap: 13px;
    .header-title-left {
        display: flex;
        align-items: center;
        gap: 13px;
        margin-top: 38px;
        img {
            width: 12px;
            height: 19px;
        }
        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;
            }
        }
        span {
            flex-shrink: 0;
            font-weight: bold;
            font-size: 18px;
            color: #222222;
            line-height: 27px;
            font-family: "Source Han Sans CN Bold Bold";
        }
    }
    .header-title-left :first-child {
        margin-top: 0;
    }
}
.header-title:first-child {
    .header-title-left {
        margin-top: 0;
    }
}
.item-title {
    padding-left: 25px;
    span {
        flex-shrink: 0;
        font-weight: bold;
        font-size: 14px;
        color: #222222;
        line-height: 27px;
        font-family: "Source Han Sans CN Bold Bold";
        margin: 18px 0;
        &:before {
            content: "*";
            color: #f56c6c;
            margin-right: 4px;
        }
    }
}
.approval-dialog-approve {
    padding: 38px 20px;
    // display: flex;
    align-content: center;
    .status {
        margin-right: 40px;
        max-width: 60%;
    }
    //   align-items: center;
    .status-title {
        color: #222222;
        font-family: "SourceHanSansCN-Medium";
        line-height: 14px;
        margin-bottom: 16px;
    }
    .status-content {
        display: flex;
        align-items: center;
        width: 100%;
        gap: 16px;
        background: #ffffff;
        border-radius: 10px;
        border: 1px solid rgba(4, 156, 154, 0.5);
        .resolve {
            border-radius: 10px;
            flex: 1;
            font-size: 16px;
            // padding: 5px 55px;
            font-weight: 400;
            color: #333333;
            cursor: pointer;
            line-height: 32px;
            display: flex;
            align-items: center;
            justify-content: center
        }
        .reject {
            flex: 1;
            border-radius: 10px;
            font-size: 16px;
            line-height: 32px;
            // padding: 5px 55px;
            font-weight: 400;
            color: #333333;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center
        }
        .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;
    }
}
.dialog-footer {
    align-items: center;
    display: flex;
    justify-content: center;
    gap: 20px;
    button {
        width: 150px;
    }
}
</style>
culture/src/views/strainReportLibrary/reportLibraryOneFour/index.vue
New file
@@ -0,0 +1,232 @@
<template>
    <div class="list">
        <el-card class="header-box">
            <div class="box-title">
                <img src="@/assets/public/notice.png" class="header-icon"> <span>设立课题规则</span>
            </div>
            <div class="header-content">
                <p>1、根据可研报告、产品构思设计的工艺研究路线,一条工艺路线设立一个课题。如果一个课题中有多个化合物需要开发研究,则每个化合物作为一个分题;分题归集到该课题中,最终形成课题报告。不同课题报告中的分题不能重复使用。
                </p>
                <p>2、在可行研究阶段,工艺开发升级,重新规划工艺研究路线,则以新规划的工艺路线方案来设定课题。</p>
            </div>
        </el-card>
        <TableCustom :queryForm="queryForm" :total="total" @currentChange="handleCurrentChange"
            @sizeChange="handleSizeChange">
            <template #search>
                <el-form :model="form" label-width="auto" inline>
                    <el-form-item label="所属项目组:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="报告名称:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="报告编号:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="创建日期:">
                        <el-date-picker v-model="form.date" type="daterange" range-separator="至"
                            start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
                    </el-form-item>
                    <el-form-item label="状态:">
                        <el-select v-model="form.status" placeholder="请选择">
                            <el-option label="待审核" value="1"></el-option>
                            <el-option label="已通过" value="0"></el-option>
                            <el-option label="已驳回" value="2"></el-option>
                        </el-select>
                    </el-form-item>
                    <el-form-item label="" style="margin-left: 63px;">
                        <el-button type="default" style="margin-right: 10px;">重置</el-button>
                        <el-button type="primary">查询</el-button>
                    </el-form-item>
                </el-form>
            </template>
            <template #setting>
                <el-button @click="handleAddProject" class="el-icon-plus" type="primary">
                    新增可研报告</el-button>
                <div class="table-setting">
                    <div class="table-title">
                        可研报告库
                    </div>
                    <div class="table-tit">
                        草稿箱
                    </div>
                </div>
            </template>
            <template #table>
                <el-table-column prop="name" label="所属项目组" />
                <el-table-column prop="age" label="报告编号" />
                <el-table-column prop="age" label="报告名称" />
                <el-table-column prop="age" label="创建人" />
                <el-table-column prop="age" label="创建时间" />
                <el-table-column prop="age" label="状态">
                    <template #default="{ row }">
                        <el-tag v-if="row.status == 1" type="success">待审核</el-tag>
                        <el-tag v-else-if="row.status == 0" type="success">已通过</el-tag>
                        <el-tag v-else type="danger">已驳回</el-tag>
                    </template>
                </el-table-column>
                <el-table-column prop="age" label="操作">
                    <template #default="{ row }">
                        <el-button type="text">审核</el-button>
                        <el-button type="text">详情</el-button>
                    </template>
                </el-table-column>
            </template>
        </TableCustom>
        <Approval :visible="showApproval" @close="showApproval = false" />
        <ShowDelConfirm :show="showDelConfirm" @close="showDelConfirm = false" @confirm="handleDelConfirm" />
        <ShowDelConfirm :title="changeStatusTitle" :tip="changeStatusTip" :show="changeStatus"
            @close="changeStatus = false" @confirm="handleChangeStatusConfirm" />
    </div>
</template>
<script>
import Approval from './components/approval'
export default {
    name: 'ProjectList',
    components: {
        Approval
    },
    data() {
        return {
            form: {
                name: ''
            },
            showDelConfirm: false,
            rowId: '',
            changeStatus: false,
            showApproval: false,
            changeStatusTitle: '',
            changeStatusTip: '',
            queryForm: {
                pageSize: 10,
                pageNum: 1
            },
            total: 0
        }
    },
    methods: {
        handleAddProject() {
            this.$router.push('/reportLibrary/add')
        },
        handleDel(row) {
            this.rowId = row.id
            this.showDelConfirm = true
        },
        handleDelConfirm() {
            this.showDelConfirm = false
            this.msgsuccess('删除成功')
            this.rowId = ''
            this.getList()
        },
        handleChangeStatus(row, status) {
            this.rowId = row.id
            this.changeStatusTitle = status == 1 ? '确认要封存这个项目组吗?' : '确认要解封该项目组吗?'
            this.changeStatusTip = status == 1 ? '封存后项目组内人员看不到数据,审批人仍然可见数据。' : '解封后项目组内人员数据恢复。'
            this.changeStatus = true
        },
        handleChangeStatusConfirm() {
            this.changeStatus = false
            this.msgsuccess('操作成功')
            this.rowId = ''
            this.changeStatusTitle = ''
            this.changeStatusTip = ''
            this.getList()
        },
        handleCurrentChange(page) {
            this.queryForm.pageNum = page
            this.getList()
        },
        handleSizeChange(size) {
            this.queryForm.pageSize = size
            this.getList()
        },
        getList() {
        }
    }
}
</script>
<style scoped lang="less">
.el-icon-plus{
    margin-bottom: 20px;
}
.header-content {
    font-family: PingFangSC, PingFang SC;
    font-weight: 400;
    font-size: 14px;
    color: rgba(0, 0, 0, 0.88);
    margin-left: 30px;
}
.box-title {
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #222222;
    line-height: 27px;
    display: flex;
    align-items: center;
}
.header-icon {
    width: 20px;
    height: 20px;
    margin-right: 10px;
}
.header-box {
    border-radius: 16px;
    margin-bottom: 30px;
}
.table-setting {
    display: flex;
    gap: 14px;
}
.table-tit {
    background: #FAFAFC;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #DCDFE6;
    width: 166px;
    height: 50px;
    background: #FFFFFF;
    border-radius: 8px 8px 0px 0px;
    display: flex;
    align-items: center;
    justify-content: center;
    // margin-bottom: 21px;
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #049C9A;
    line-height: 27px;
}
.list {
    height: 100%;
}
.table-title {
    width: 166px;
    height: 50px;
    background: #FFFFFF;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #049C9A;
    display: flex;
    align-items: center;
    justify-content: center;
    // margin-bottom: 21px;
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #049C9A;
    line-height: 27px;
}
</style>
culture/src/views/strainReportLibrary/reportLibraryOneTWO/add.vue
New file
@@ -0,0 +1,173 @@
<template>
    <div>
        <Card>
            <div class="header-title" style="width: 100%;">
                <div class="header-title-left">
                    <img src="@/assets/public/headercard.png" />
                    <div>所属项目组</div>
                </div>
                <div class="header-title-right">
                    <el-button @click="showChoose = true" class="el-icon-circle-plus-outline" type="primary">
                        选择项目组</el-button>
                </div>
            </div>
            <Table :height="null" :queryForm="queryForm" :total="0">
                <template>
                    <el-table-column prop="name" label="项目组名称" />
                    <el-table-column prop="age" label="项目负责人" />
                    <el-table-column prop="age" label="项目组成员" />
                    <el-table-column prop="age" label="创建时间" />
                </template>
            </Table>
            <el-form ref="form" :model="form" :rules="rules" inline label-position="top" style="margin-top: 38px">
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>报告编号</div>
                    </div>
                </div>
                <form-item prop="name" style="margin-top: 38px">
                    <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                </form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>报告名称</div>
                    </div>
                </div>
                <form-item prop="name" style="margin-top: 38px">
                    <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                </form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>报告正文</div>
                    </div>
                </div>
                <form-item prop="name" style="margin-top: 38px">
                    <ai-editor v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                </form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>附件</div>
                    </div>
                </div>
                <form-item prop="name" style="margin-top: 38px">
                    <el-upload action="https://jsonplaceholder.typicode.com/posts/" :file-list="fileList">
                        <el-button size="small" type="primary">点击上传</el-button>
                    </el-upload>
                </form-item>
                <div class="end-btn" style="margin-top: 38px">
                    <el-button type="primary">发送</el-button>
                    <el-button type="default">存草稿</el-button>
                </div>
            </el-form>
        </Card>
    </div>
</template>
<script>
import { Card } from 'element-ui';
import AiEditor from '@/components/AiEditor'
export default {
    components: { AiEditor },
    data() {
        return {
            form: {
                planName: "",
                planCode: "",
                stage: "",
                creator: "",
                createTime: "",
                approvalComment: "",
                status: "pending",
                approver: "",
                approveTime: ""
            },
            fileList: [], // 附件列表
            showChoose: false,
            radio1: 1,
            rules: {},
            status: "1",
            remark: "",
            queryForm: {
            }
        }
    }
}
</script>
<style lang="less" scoped>
.header-title {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 20px;
    gap: 13px;
    .header-title-left {
        display: flex;
        align-items: center;
        gap: 13px;
        margin-top: 38px;
        img {
            width: 12px;
            height: 19px;
        }
        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;
            }
        }
        span {
            flex-shrink: 0;
            font-weight: bold;
            font-size: 18px;
            color: #222222;
            line-height: 27px;
            font-family: "Source Han Sans CN Bold Bold";
        }
    }
    .header-title-left :first-child {
        margin-top: 0;
    }
}
.header-title:first-child {
    .header-title-left {
        margin-top: 0;
    }
}
.end-btn{
    display: flex;
    align-items: center;
    gap: 10px;
    button{
        width: 180px;
        height: 36px;
        // background: #409EFF;
    }
}
</style>
culture/src/views/strainReportLibrary/reportLibraryOneTWO/components/approval/index.vue
New file
@@ -0,0 +1,393 @@
<template>
    <el-dialog :title="dialogTitle"  :visible.sync="visible" width="80%" po :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" style="width: 100%;">
                            <div class="header-title-left">
                                <img src="@/assets/public/headercard.png" />
                                <div>所属项目组</div>
                            </div>
                        </div>
                        <Table :height="null" :queryForm="queryForm" :total="0" @currentChange="handleCurrentChange"
                            @sizeChange="handleSizeChange">
                            <template>
                                <el-table-column prop="name" label="项目组名称" />
                                <el-table-column prop="age" label="项目负责人" />
                                <el-table-column prop="age" label="项目组成员" />
                                <el-table-column prop="age" label="创建时间" />
                            </template>
                        </Table>
                        <el-form ref="form" :model="form" :rules="rules" inline label-position="top"
                            style="margin-top: 38px">
                            <div class="header-title" style="width: 100%;">
                                <div class="header-title-left">
                                    <img src="@/assets/public/headercard.png" />
                                    <div>报告编号</div>
                                </div>
                            </div>
                            <form-item prop="name" style="margin-top: 38px">
                                <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </form-item>
                            <div class="header-title" style="width: 100%;">
                                <div class="header-title-left">
                                    <img src="@/assets/public/headercard.png" />
                                    <div>报告名称</div>
                                </div>
                            </div>
                            <form-item prop="name" style="margin-top: 38px">
                                <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </form-item>
                            <div class="header-title" style="width: 100%;">
                                <div class="header-title-left">
                                    <img src="@/assets/public/headercard.png" />
                                    <div>报告正文</div>
                                </div>
                            </div>
                            <form-item prop="name" style="margin-top: 38px">
                                <ai-editor v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </form-item>
                        </el-form>
                    </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>
            </div>
        </div>
        <div class="approval-dialog-approve">
            <el-row :span="24">
                <el-col :span="12">
                    <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>
                </el-col>
                <el-col :span="12">
                    <div class="remark">
                        <div class="remark-title">审批意见</div>
                        <el-input type="textarea" v-model="remark" placeholder="请输入审批意见" />
                    </div>
                </el-col>
            </el-row>
        </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>
</template>
<script>
import ApprovalProcess from '@/components/approvalProcess'
import AiEditor from '@/components/AiEditor'
export default {
    name: "ApprovalDialog",
    components: {
        ApprovalProcess,
        AiEditor
    },
    props: {
        visible: {
            type: Boolean,
            default: false,
        },
        type: {
            type: String,
            default: "approve", // approve-审批,view-查看
        },
        data: {
            type: Object,
            default: () => ({}),
        },
    },
    data() {
        return {
            form: {
                planName: "",
                planCode: "",
                stage: "",
                creator: "",
                createTime: "",
                approvalComment: "",
                status: "pending",
                approver: "",
                approveTime: ""
            },
            radio1: 1,
            rules: {},
            status: "1",
            remark: "",
        };
    },
    computed: {
        dialogTitle() {
            return this.type === "approve" ? "审批" : "审批详情";
        },
    },
    watch: {
        data: {
            handler(val) {
                if (val) {
                    this.form = { ...val };
                }
            },
            immediate: true,
        },
    },
    methods: {
        handleClose() {
            this.$emit("close");
            this.form.approvalComment = "";
        },
        handleApprove() {
            if (!this.form.approvalComment) {
                this.$message.warning("请输入审批意见");
                return;
            }
            this.$emit("approve", {
                ...this.form,
                status: "approved",
            });
        },
        handleReject() {
            if (!this.form.approvalComment) {
                this.$message.warning("请输入审批意见");
                return;
            }
            this.$emit("reject", {
                ...this.form,
                status: "rejected",
            });
        },
    },
};
</script>
<style scoped lang="less">
::v-deep .el-dialog__header {
    border-bottom: 1px solid #e4e7ed;
}
.approval-dialog {
    display: flex;
    height: 40vh;
    .approval-content {
        flex: 3;
        margin-right: 20px;
        background: #ffffff;
        box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08);
        border-radius: 10px;
    }
    .approval-flow {
        padding: 40px 20px;
        // width: 405px;
        flex: 2;
        background: #ffffff;
        box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08);
        border-radius: 10px;
        .flow-title {
            font-size: 16px;
            font-weight: bold;
            margin-bottom: 20px;
            color: #303133;
        }
        .flow-content {
            height: calc(100% - 40px);
            overflow-y: auto;
            .el-form--inline .el-form-item {
                margin-right: 83px;
            }
        }
    }
}
.approval-content-card {
    height: calc(100% - 100px) !important;
    box-shadow: none !important;
}
.header-title {
    // display: flex;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 20px;
    gap: 13px;
    .header-title-left {
        display: flex;
        align-items: center;
        gap: 13px;
        margin-top: 38px;
        img {
            width: 12px;
            height: 19px;
        }
        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;
            }
        }
        span {
            flex-shrink: 0;
            font-weight: bold;
            font-size: 18px;
            color: #222222;
            line-height: 27px;
            font-family: "Source Han Sans CN Bold Bold";
        }
    }
    .header-title-left :first-child {
        margin-top: 0;
    }
}
.header-title:first-child {
    .header-title-left {
        margin-top: 0;
    }
}
.item-title {
    padding-left: 25px;
    span {
        flex-shrink: 0;
        font-weight: bold;
        font-size: 14px;
        color: #222222;
        line-height: 27px;
        font-family: "Source Han Sans CN Bold Bold";
        margin: 18px 0;
        &:before {
            content: "*";
            color: #f56c6c;
            margin-right: 4px;
        }
    }
}
.approval-dialog-approve {
    padding: 38px 20px;
    // display: flex;
    align-content: center;
    .status {
        margin-right: 40px;
        max-width: 60%;
    }
    //   align-items: center;
    .status-title {
        color: #222222;
        font-family: "SourceHanSansCN-Medium";
        line-height: 14px;
        margin-bottom: 16px;
    }
    .status-content {
        display: flex;
        align-items: center;
        width: 100%;
        gap: 16px;
        background: #ffffff;
        border-radius: 10px;
        border: 1px solid rgba(4, 156, 154, 0.5);
        .resolve {
            border-radius: 10px;
            flex: 1;
            font-size: 16px;
            // padding: 5px 55px;
            font-weight: 400;
            color: #333333;
            cursor: pointer;
            line-height: 32px;
            display: flex;
            align-items: center;
            justify-content: center
        }
        .reject {
            flex: 1;
            border-radius: 10px;
            font-size: 16px;
            line-height: 32px;
            // padding: 5px 55px;
            font-weight: 400;
            color: #333333;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center
        }
        .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;
    }
}
.dialog-footer {
    align-items: center;
    display: flex;
    justify-content: center;
    gap: 20px;
    button {
        width: 150px;
    }
}
</style>
culture/src/views/strainReportLibrary/reportLibraryOneTWO/index.vue
New file
@@ -0,0 +1,232 @@
<template>
    <div class="list">
        <el-card class="header-box">
            <div class="box-title">
                <img src="@/assets/public/notice.png" class="header-icon"> <span>设立课题规则</span>
            </div>
            <div class="header-content">
                <p>1、根据可研报告、产品构思设计的工艺研究路线,一条工艺路线设立一个课题。如果一个课题中有多个化合物需要开发研究,则每个化合物作为一个分题;分题归集到该课题中,最终形成课题报告。不同课题报告中的分题不能重复使用。
                </p>
                <p>2、在可行研究阶段,工艺开发升级,重新规划工艺研究路线,则以新规划的工艺路线方案来设定课题。</p>
            </div>
        </el-card>
        <TableCustom :queryForm="queryForm" :total="total" @currentChange="handleCurrentChange"
            @sizeChange="handleSizeChange">
            <template #search>
                <el-form :model="form" label-width="auto" inline>
                    <el-form-item label="所属项目组:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="报告名称:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="报告编号:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="创建日期:">
                        <el-date-picker v-model="form.date" type="daterange" range-separator="至"
                            start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
                    </el-form-item>
                    <el-form-item label="状态:">
                        <el-select v-model="form.status" placeholder="请选择">
                            <el-option label="待审核" value="1"></el-option>
                            <el-option label="已通过" value="0"></el-option>
                            <el-option label="已驳回" value="2"></el-option>
                        </el-select>
                    </el-form-item>
                    <el-form-item label="" style="margin-left: 63px;">
                        <el-button type="default" style="margin-right: 10px;">重置</el-button>
                        <el-button type="primary">查询</el-button>
                    </el-form-item>
                </el-form>
            </template>
            <template #setting>
                <el-button @click="handleAddProject" class="el-icon-plus" type="primary">
                    新增可研报告</el-button>
                <div class="table-setting">
                    <div class="table-title">
                        可研报告库
                    </div>
                    <div class="table-tit">
                        草稿箱
                    </div>
                </div>
            </template>
            <template #table>
                <el-table-column prop="name" label="所属项目组" />
                <el-table-column prop="age" label="报告编号" />
                <el-table-column prop="age" label="报告名称" />
                <el-table-column prop="age" label="创建人" />
                <el-table-column prop="age" label="创建时间" />
                <el-table-column prop="age" label="状态">
                    <template #default="{ row }">
                        <el-tag v-if="row.status == 1" type="success">待审核</el-tag>
                        <el-tag v-else-if="row.status == 0" type="success">已通过</el-tag>
                        <el-tag v-else type="danger">已驳回</el-tag>
                    </template>
                </el-table-column>
                <el-table-column prop="age" label="操作">
                    <template #default="{ row }">
                        <el-button type="text">审核</el-button>
                        <el-button type="text">详情</el-button>
                    </template>
                </el-table-column>
            </template>
        </TableCustom>
        <Approval :visible="showApproval" @close="showApproval = false" />
        <ShowDelConfirm :show="showDelConfirm" @close="showDelConfirm = false" @confirm="handleDelConfirm" />
        <ShowDelConfirm :title="changeStatusTitle" :tip="changeStatusTip" :show="changeStatus"
            @close="changeStatus = false" @confirm="handleChangeStatusConfirm" />
    </div>
</template>
<script>
import Approval from './components/approval'
export default {
    name: 'ProjectList',
    components: {
        Approval
    },
    data() {
        return {
            form: {
                name: ''
            },
            showDelConfirm: false,
            rowId: '',
            changeStatus: false,
            showApproval: false,
            changeStatusTitle: '',
            changeStatusTip: '',
            queryForm: {
                pageSize: 10,
                pageNum: 1
            },
            total: 0
        }
    },
    methods: {
        handleAddProject() {
            this.$router.push('/reportLibrary/add')
        },
        handleDel(row) {
            this.rowId = row.id
            this.showDelConfirm = true
        },
        handleDelConfirm() {
            this.showDelConfirm = false
            this.msgsuccess('删除成功')
            this.rowId = ''
            this.getList()
        },
        handleChangeStatus(row, status) {
            this.rowId = row.id
            this.changeStatusTitle = status == 1 ? '确认要封存这个项目组吗?' : '确认要解封该项目组吗?'
            this.changeStatusTip = status == 1 ? '封存后项目组内人员看不到数据,审批人仍然可见数据。' : '解封后项目组内人员数据恢复。'
            this.changeStatus = true
        },
        handleChangeStatusConfirm() {
            this.changeStatus = false
            this.msgsuccess('操作成功')
            this.rowId = ''
            this.changeStatusTitle = ''
            this.changeStatusTip = ''
            this.getList()
        },
        handleCurrentChange(page) {
            this.queryForm.pageNum = page
            this.getList()
        },
        handleSizeChange(size) {
            this.queryForm.pageSize = size
            this.getList()
        },
        getList() {
        }
    }
}
</script>
<style scoped lang="less">
.el-icon-plus{
    margin-bottom: 20px;
}
.header-content {
    font-family: PingFangSC, PingFang SC;
    font-weight: 400;
    font-size: 14px;
    color: rgba(0, 0, 0, 0.88);
    margin-left: 30px;
}
.box-title {
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #222222;
    line-height: 27px;
    display: flex;
    align-items: center;
}
.header-icon {
    width: 20px;
    height: 20px;
    margin-right: 10px;
}
.header-box {
    border-radius: 16px;
    margin-bottom: 30px;
}
.table-setting {
    display: flex;
    gap: 14px;
}
.table-tit {
    background: #FAFAFC;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #DCDFE6;
    width: 166px;
    height: 50px;
    background: #FFFFFF;
    border-radius: 8px 8px 0px 0px;
    display: flex;
    align-items: center;
    justify-content: center;
    // margin-bottom: 21px;
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #049C9A;
    line-height: 27px;
}
.list {
    height: 100%;
}
.table-title {
    width: 166px;
    height: 50px;
    background: #FFFFFF;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #049C9A;
    display: flex;
    align-items: center;
    justify-content: center;
    // margin-bottom: 21px;
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #049C9A;
    line-height: 27px;
}
</style>
culture/src/views/strainReportLibrary/reportLibraryOneThree/add.vue
New file
@@ -0,0 +1,173 @@
<template>
    <div>
        <Card>
            <div class="header-title" style="width: 100%;">
                <div class="header-title-left">
                    <img src="@/assets/public/headercard.png" />
                    <div>所属项目组</div>
                </div>
                <div class="header-title-right">
                    <el-button @click="showChoose = true" class="el-icon-circle-plus-outline" type="primary">
                        选择项目组</el-button>
                </div>
            </div>
            <Table :height="null" :queryForm="queryForm" :total="0">
                <template>
                    <el-table-column prop="name" label="项目组名称" />
                    <el-table-column prop="age" label="项目负责人" />
                    <el-table-column prop="age" label="项目组成员" />
                    <el-table-column prop="age" label="创建时间" />
                </template>
            </Table>
            <el-form ref="form" :model="form" :rules="rules" inline label-position="top" style="margin-top: 38px">
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>报告编号</div>
                    </div>
                </div>
                <form-item prop="name" style="margin-top: 38px">
                    <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                </form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>报告名称</div>
                    </div>
                </div>
                <form-item prop="name" style="margin-top: 38px">
                    <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                </form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>报告正文</div>
                    </div>
                </div>
                <form-item prop="name" style="margin-top: 38px">
                    <ai-editor v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                </form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>附件</div>
                    </div>
                </div>
                <form-item prop="name" style="margin-top: 38px">
                    <el-upload action="https://jsonplaceholder.typicode.com/posts/" :file-list="fileList">
                        <el-button size="small" type="primary">点击上传</el-button>
                    </el-upload>
                </form-item>
                <div class="end-btn" style="margin-top: 38px">
                    <el-button type="primary">发送</el-button>
                    <el-button type="default">存草稿</el-button>
                </div>
            </el-form>
        </Card>
    </div>
</template>
<script>
import { Card } from 'element-ui';
import AiEditor from '@/components/AiEditor'
export default {
    components: { AiEditor },
    data() {
        return {
            form: {
                planName: "",
                planCode: "",
                stage: "",
                creator: "",
                createTime: "",
                approvalComment: "",
                status: "pending",
                approver: "",
                approveTime: ""
            },
            fileList: [], // 附件列表
            showChoose: false,
            radio1: 1,
            rules: {},
            status: "1",
            remark: "",
            queryForm: {
            }
        }
    }
}
</script>
<style lang="less" scoped>
.header-title {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 20px;
    gap: 13px;
    .header-title-left {
        display: flex;
        align-items: center;
        gap: 13px;
        margin-top: 38px;
        img {
            width: 12px;
            height: 19px;
        }
        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;
            }
        }
        span {
            flex-shrink: 0;
            font-weight: bold;
            font-size: 18px;
            color: #222222;
            line-height: 27px;
            font-family: "Source Han Sans CN Bold Bold";
        }
    }
    .header-title-left :first-child {
        margin-top: 0;
    }
}
.header-title:first-child {
    .header-title-left {
        margin-top: 0;
    }
}
.end-btn{
    display: flex;
    align-items: center;
    gap: 10px;
    button{
        width: 180px;
        height: 36px;
        // background: #409EFF;
    }
}
</style>
culture/src/views/strainReportLibrary/reportLibraryOneThree/components/approval/index.vue
New file
@@ -0,0 +1,393 @@
<template>
    <el-dialog :title="dialogTitle"  :visible.sync="visible" width="80%" po :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" style="width: 100%;">
                            <div class="header-title-left">
                                <img src="@/assets/public/headercard.png" />
                                <div>所属项目组</div>
                            </div>
                        </div>
                        <Table :height="null" :queryForm="queryForm" :total="0" @currentChange="handleCurrentChange"
                            @sizeChange="handleSizeChange">
                            <template>
                                <el-table-column prop="name" label="项目组名称" />
                                <el-table-column prop="age" label="项目负责人" />
                                <el-table-column prop="age" label="项目组成员" />
                                <el-table-column prop="age" label="创建时间" />
                            </template>
                        </Table>
                        <el-form ref="form" :model="form" :rules="rules" inline label-position="top"
                            style="margin-top: 38px">
                            <div class="header-title" style="width: 100%;">
                                <div class="header-title-left">
                                    <img src="@/assets/public/headercard.png" />
                                    <div>报告编号</div>
                                </div>
                            </div>
                            <form-item prop="name" style="margin-top: 38px">
                                <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </form-item>
                            <div class="header-title" style="width: 100%;">
                                <div class="header-title-left">
                                    <img src="@/assets/public/headercard.png" />
                                    <div>报告名称</div>
                                </div>
                            </div>
                            <form-item prop="name" style="margin-top: 38px">
                                <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </form-item>
                            <div class="header-title" style="width: 100%;">
                                <div class="header-title-left">
                                    <img src="@/assets/public/headercard.png" />
                                    <div>报告正文</div>
                                </div>
                            </div>
                            <form-item prop="name" style="margin-top: 38px">
                                <ai-editor v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </form-item>
                        </el-form>
                    </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>
            </div>
        </div>
        <div class="approval-dialog-approve">
            <el-row :span="24">
                <el-col :span="12">
                    <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>
                </el-col>
                <el-col :span="12">
                    <div class="remark">
                        <div class="remark-title">审批意见</div>
                        <el-input type="textarea" v-model="remark" placeholder="请输入审批意见" />
                    </div>
                </el-col>
            </el-row>
        </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>
</template>
<script>
import ApprovalProcess from '@/components/approvalProcess'
import AiEditor from '@/components/AiEditor'
export default {
    name: "ApprovalDialog",
    components: {
        ApprovalProcess,
        AiEditor
    },
    props: {
        visible: {
            type: Boolean,
            default: false,
        },
        type: {
            type: String,
            default: "approve", // approve-审批,view-查看
        },
        data: {
            type: Object,
            default: () => ({}),
        },
    },
    data() {
        return {
            form: {
                planName: "",
                planCode: "",
                stage: "",
                creator: "",
                createTime: "",
                approvalComment: "",
                status: "pending",
                approver: "",
                approveTime: ""
            },
            radio1: 1,
            rules: {},
            status: "1",
            remark: "",
        };
    },
    computed: {
        dialogTitle() {
            return this.type === "approve" ? "审批" : "审批详情";
        },
    },
    watch: {
        data: {
            handler(val) {
                if (val) {
                    this.form = { ...val };
                }
            },
            immediate: true,
        },
    },
    methods: {
        handleClose() {
            this.$emit("close");
            this.form.approvalComment = "";
        },
        handleApprove() {
            if (!this.form.approvalComment) {
                this.$message.warning("请输入审批意见");
                return;
            }
            this.$emit("approve", {
                ...this.form,
                status: "approved",
            });
        },
        handleReject() {
            if (!this.form.approvalComment) {
                this.$message.warning("请输入审批意见");
                return;
            }
            this.$emit("reject", {
                ...this.form,
                status: "rejected",
            });
        },
    },
};
</script>
<style scoped lang="less">
::v-deep .el-dialog__header {
    border-bottom: 1px solid #e4e7ed;
}
.approval-dialog {
    display: flex;
    height: 40vh;
    .approval-content {
        flex: 3;
        margin-right: 20px;
        background: #ffffff;
        box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08);
        border-radius: 10px;
    }
    .approval-flow {
        padding: 40px 20px;
        // width: 405px;
        flex: 2;
        background: #ffffff;
        box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08);
        border-radius: 10px;
        .flow-title {
            font-size: 16px;
            font-weight: bold;
            margin-bottom: 20px;
            color: #303133;
        }
        .flow-content {
            height: calc(100% - 40px);
            overflow-y: auto;
            .el-form--inline .el-form-item {
                margin-right: 83px;
            }
        }
    }
}
.approval-content-card {
    height: calc(100% - 100px) !important;
    box-shadow: none !important;
}
.header-title {
    // display: flex;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 20px;
    gap: 13px;
    .header-title-left {
        display: flex;
        align-items: center;
        gap: 13px;
        margin-top: 38px;
        img {
            width: 12px;
            height: 19px;
        }
        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;
            }
        }
        span {
            flex-shrink: 0;
            font-weight: bold;
            font-size: 18px;
            color: #222222;
            line-height: 27px;
            font-family: "Source Han Sans CN Bold Bold";
        }
    }
    .header-title-left :first-child {
        margin-top: 0;
    }
}
.header-title:first-child {
    .header-title-left {
        margin-top: 0;
    }
}
.item-title {
    padding-left: 25px;
    span {
        flex-shrink: 0;
        font-weight: bold;
        font-size: 14px;
        color: #222222;
        line-height: 27px;
        font-family: "Source Han Sans CN Bold Bold";
        margin: 18px 0;
        &:before {
            content: "*";
            color: #f56c6c;
            margin-right: 4px;
        }
    }
}
.approval-dialog-approve {
    padding: 38px 20px;
    // display: flex;
    align-content: center;
    .status {
        margin-right: 40px;
        max-width: 60%;
    }
    //   align-items: center;
    .status-title {
        color: #222222;
        font-family: "SourceHanSansCN-Medium";
        line-height: 14px;
        margin-bottom: 16px;
    }
    .status-content {
        display: flex;
        align-items: center;
        width: 100%;
        gap: 16px;
        background: #ffffff;
        border-radius: 10px;
        border: 1px solid rgba(4, 156, 154, 0.5);
        .resolve {
            border-radius: 10px;
            flex: 1;
            font-size: 16px;
            // padding: 5px 55px;
            font-weight: 400;
            color: #333333;
            cursor: pointer;
            line-height: 32px;
            display: flex;
            align-items: center;
            justify-content: center
        }
        .reject {
            flex: 1;
            border-radius: 10px;
            font-size: 16px;
            line-height: 32px;
            // padding: 5px 55px;
            font-weight: 400;
            color: #333333;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center
        }
        .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;
    }
}
.dialog-footer {
    align-items: center;
    display: flex;
    justify-content: center;
    gap: 20px;
    button {
        width: 150px;
    }
}
</style>
culture/src/views/strainReportLibrary/reportLibraryOneThree/index.vue
New file
@@ -0,0 +1,232 @@
<template>
    <div class="list">
        <el-card class="header-box">
            <div class="box-title">
                <img src="@/assets/public/notice.png" class="header-icon"> <span>设立课题规则</span>
            </div>
            <div class="header-content">
                <p>1、根据可研报告、产品构思设计的工艺研究路线,一条工艺路线设立一个课题。如果一个课题中有多个化合物需要开发研究,则每个化合物作为一个分题;分题归集到该课题中,最终形成课题报告。不同课题报告中的分题不能重复使用。
                </p>
                <p>2、在可行研究阶段,工艺开发升级,重新规划工艺研究路线,则以新规划的工艺路线方案来设定课题。</p>
            </div>
        </el-card>
        <TableCustom :queryForm="queryForm" :total="total" @currentChange="handleCurrentChange"
            @sizeChange="handleSizeChange">
            <template #search>
                <el-form :model="form" label-width="auto" inline>
                    <el-form-item label="所属项目组:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="报告名称:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="报告编号:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="创建日期:">
                        <el-date-picker v-model="form.date" type="daterange" range-separator="至"
                            start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
                    </el-form-item>
                    <el-form-item label="状态:">
                        <el-select v-model="form.status" placeholder="请选择">
                            <el-option label="待审核" value="1"></el-option>
                            <el-option label="已通过" value="0"></el-option>
                            <el-option label="已驳回" value="2"></el-option>
                        </el-select>
                    </el-form-item>
                    <el-form-item label="" style="margin-left: 63px;">
                        <el-button type="default" style="margin-right: 10px;">重置</el-button>
                        <el-button type="primary">查询</el-button>
                    </el-form-item>
                </el-form>
            </template>
            <template #setting>
                <el-button @click="handleAddProject" class="el-icon-plus" type="primary">
                    新增可研报告</el-button>
                <div class="table-setting">
                    <div class="table-title">
                        可研报告库
                    </div>
                    <div class="table-tit">
                        草稿箱
                    </div>
                </div>
            </template>
            <template #table>
                <el-table-column prop="name" label="所属项目组" />
                <el-table-column prop="age" label="报告编号" />
                <el-table-column prop="age" label="报告名称" />
                <el-table-column prop="age" label="创建人" />
                <el-table-column prop="age" label="创建时间" />
                <el-table-column prop="age" label="状态">
                    <template #default="{ row }">
                        <el-tag v-if="row.status == 1" type="success">待审核</el-tag>
                        <el-tag v-else-if="row.status == 0" type="success">已通过</el-tag>
                        <el-tag v-else type="danger">已驳回</el-tag>
                    </template>
                </el-table-column>
                <el-table-column prop="age" label="操作">
                    <template #default="{ row }">
                        <el-button type="text">审核</el-button>
                        <el-button type="text">详情</el-button>
                    </template>
                </el-table-column>
            </template>
        </TableCustom>
        <Approval :visible="showApproval" @close="showApproval = false" />
        <ShowDelConfirm :show="showDelConfirm" @close="showDelConfirm = false" @confirm="handleDelConfirm" />
        <ShowDelConfirm :title="changeStatusTitle" :tip="changeStatusTip" :show="changeStatus"
            @close="changeStatus = false" @confirm="handleChangeStatusConfirm" />
    </div>
</template>
<script>
import Approval from './components/approval'
export default {
    name: 'ProjectList',
    components: {
        Approval
    },
    data() {
        return {
            form: {
                name: ''
            },
            showDelConfirm: false,
            rowId: '',
            changeStatus: false,
            showApproval: false,
            changeStatusTitle: '',
            changeStatusTip: '',
            queryForm: {
                pageSize: 10,
                pageNum: 1
            },
            total: 0
        }
    },
    methods: {
        handleAddProject() {
            this.$router.push('/reportLibrary/add')
        },
        handleDel(row) {
            this.rowId = row.id
            this.showDelConfirm = true
        },
        handleDelConfirm() {
            this.showDelConfirm = false
            this.msgsuccess('删除成功')
            this.rowId = ''
            this.getList()
        },
        handleChangeStatus(row, status) {
            this.rowId = row.id
            this.changeStatusTitle = status == 1 ? '确认要封存这个项目组吗?' : '确认要解封该项目组吗?'
            this.changeStatusTip = status == 1 ? '封存后项目组内人员看不到数据,审批人仍然可见数据。' : '解封后项目组内人员数据恢复。'
            this.changeStatus = true
        },
        handleChangeStatusConfirm() {
            this.changeStatus = false
            this.msgsuccess('操作成功')
            this.rowId = ''
            this.changeStatusTitle = ''
            this.changeStatusTip = ''
            this.getList()
        },
        handleCurrentChange(page) {
            this.queryForm.pageNum = page
            this.getList()
        },
        handleSizeChange(size) {
            this.queryForm.pageSize = size
            this.getList()
        },
        getList() {
        }
    }
}
</script>
<style scoped lang="less">
.el-icon-plus{
    margin-bottom: 20px;
}
.header-content {
    font-family: PingFangSC, PingFang SC;
    font-weight: 400;
    font-size: 14px;
    color: rgba(0, 0, 0, 0.88);
    margin-left: 30px;
}
.box-title {
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #222222;
    line-height: 27px;
    display: flex;
    align-items: center;
}
.header-icon {
    width: 20px;
    height: 20px;
    margin-right: 10px;
}
.header-box {
    border-radius: 16px;
    margin-bottom: 30px;
}
.table-setting {
    display: flex;
    gap: 14px;
}
.table-tit {
    background: #FAFAFC;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #DCDFE6;
    width: 166px;
    height: 50px;
    background: #FFFFFF;
    border-radius: 8px 8px 0px 0px;
    display: flex;
    align-items: center;
    justify-content: center;
    // margin-bottom: 21px;
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #049C9A;
    line-height: 27px;
}
.list {
    height: 100%;
}
.table-title {
    width: 166px;
    height: 50px;
    background: #FFFFFF;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #049C9A;
    display: flex;
    align-items: center;
    justify-content: center;
    // margin-bottom: 21px;
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #049C9A;
    line-height: 27px;
}
</style>
laboratory/src/App.vue
@@ -61,17 +61,17 @@
    width: 6px;
    height: 6px;
  }
  ::-webkit-scrollbar-track {
    background: #f1f1f1;
    border-radius: 3px;
  }
  ::-webkit-scrollbar-thumb {
    background: #009688;
    border-radius: 3px;
  }
  ::-webkit-scrollbar-thumb:hover {
    background: #00796b;
  }
@@ -92,6 +92,12 @@
.el-button--primary {
  background-color: #009688 !important;
  border-color: #009688 !important;
}
.el-radio-button--small.is-active {
  .el-radio-button__inner {
    background-color: #009688 !important;
    border-color: #009688 !important;
  }
}
.el-button--text {
@@ -150,6 +156,11 @@
    overflow: hidden;
  }
}
//  .pagination{
//   .el-input__inner {
//     width: unset !important;
//   }
// }
.el-input__inner:focus,
.el-select .el-input__inner:focus,
laboratory/src/assets/public/delete.png
laboratory/src/assets/public/edit.png
laboratory/src/components/AddComponentDialog/index.vue
@@ -78,7 +78,7 @@
      this.selectedType = item.type
    },
    handleClose() {
      this.$emit('update:visible', false)
      this.$emit('close', false)
      this.selectedType = ''
    },
    handleConfirm() {
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/addTableData.vue
New file
@@ -0,0 +1,537 @@
<template>
  <el-dialog
    title="添加表数据"
    :visible.sync="dialogVisible"
    width="60%"
    :close-on-click-modal="false"
    @close="handleClose"
  >
    <div class="sample-dialog">
      <div class="sample-content">
        <div class="form-content">
          <el-form ref="form" :model="form" :rules="rules" label-position="top">
            <el-row :gutter="20">
              <el-col :span="12">
                <el-form-item label="更新时间" prop="updateTime">
                  <el-input
                    v-model="form.updateTime"
                    placeholder="更新时间"
                    disabled
                  />
                </el-form-item>
              </el-col>
              <el-col
                :span="12"
                v-for="(header, index) in headerList"
                :key="index"
              >
                <el-form-item
                  :label="header.name"
                  :prop="header.name"
                  :rules="{
                    required: header.required === true || header.required === 'true',
                    message: header.message || `请输入${header.name}`,
                    trigger: ['blur', 'change']
                  }"
                  v-if="header.type == 'text'"
                >
                  <el-input
                    v-model="form[header.name]"
                    :placeholder="'请输入' + header.name"
                  />
                </el-form-item>
                <el-form-item
                  :label="header.name"
                  :prop="header.name"
                  :rules="{
                    required: header.required === true || header.required === 'true',
                    message: header.message || `请输入${header.name}`,
                    trigger: ['blur', 'change']
                  }"
                  v-if="header.type == 'image'"
                >
                  <el-upload
                    class="upload-demo"
                    action="#"
                    :file-list="spectrumList"
                    :auto-upload="false"
                    list-type="picture-card"
                    :on-change="handleSpectrumChange"
                    :on-remove="handleSpectrumRemove"
                  >
                    <i class="el-icon-plus"></i>
                  </el-upload>
                </el-form-item>
                <el-form-item
                  :label="header.name"
                  :prop="header.name"
                  :rules="{
                    required: header.required === true || header.required === 'true',
                    message: header.message || `请输入${header.name}`,
                    trigger: ['blur', 'change']
                  }"
                  v-if="header.type == 'date'"
                >
                  <el-date-picker
                    v-model="form[header.name]"
                    type="datetime"
                    placeholder="选择日期时间"
                    value-format="yyyy-MM-dd HH:mm:ss"
                    :picker-options="{
                      shortcuts: [{
                        text: '今天',
                        onClick(picker) {
                          picker.$emit('pick', new Date());
                        }
                      }, {
                        text: '昨天',
                        onClick(picker) {
                          const date = new Date();
                          date.setTime(date.getTime() - 3600 * 1000 * 24);
                          picker.$emit('pick', date);
                        }
                      }, {
                        text: '一周前',
                        onClick(picker) {
                          const date = new Date();
                          date.setTime(date.getTime() - 3600 * 1000 * 24 * 7);
                          picker.$emit('pick', date);
                        }
                      }]
                    }"
                  />
                </el-form-item>
                <el-form-item
                  :label="header.name"
                  :prop="header.name"
                  :rules="{
                    required: header.required === true || header.required === 'true',
                    message: header.message || `请输入${header.name}`,
                    trigger: ['blur', 'change']
                  }"
                  v-if="header.type == 'user'"
                >
                  <el-select
                    v-model="form[header.name]"
                    multiple
                    filterable
                    placeholder="请选择用户"
                  >
                    <el-option
                      v-for="item in userOptions"
                      :key="item.value"
                      :label="item.label"
                      :value="item.value"
                    />
                  </el-select>
                </el-form-item>
              </el-col>
            </el-row>
          </el-form>
        </div>
      </div>
    </div>
    <div slot="footer" class="dialog-footer select-member-footer">
      <el-button @click="handleClose">取 消</el-button>
      <el-button type="primary" @click="handleSubmit">确 定</el-button>
    </div>
  </el-dialog>
</template>
<script>
export default {
  name: "AddDialog",
  props: {
    visible: {
      type: Boolean,
      default: false,
    },
    headerList: {
      type: Array,
      default: () => [],
    },
    editData: {
      type: Object,
      default: () => ({}),
    },
    isEdit: {
      type: Boolean,
      default: false,
    }
  },
  data() {
    return {
      isIPad: /iPad/i.test(navigator.userAgent),
      showHeaderList: [{"name":"sd","type":"text","required":true,"message":"请输入表头名称","role":[]}],
      form: {},
      rules: {},
      photoList: [],
      spectrumList: [],
      userOptions: [
        { value: '1', label: '用户1' },
        { value: '2', label: '用户2' },
        { value: '3', label: '用户3' },
        { value: '4', label: '用户4' },
        { value: '5', label: '用户5' }
      ]
    };
  },
  computed: {
    dialogVisible: {
      get() {
        return this.visible;
      },
      set(val) {
        this.$emit("update:visible", val);
      },
    },
  },
  watch: {
    visible: {
      handler(newVal) {
        if (newVal) {
          console.log('弹窗打开,初始化数据');
          this.showHeaderList = JSON.parse(JSON.stringify(this.headerList));
          this.$forceUpdate();
          if (this.isEdit && this.editData) {
            // 编辑模式:设置回显数据
            this.setFormData(this.editData);
          } else {
            // 新增模式:初始化空表单
            this.initFormData();
          }
          this.initRules();
          console.log('初始化后的表单数据:', this.form);
          console.log('初始化后的校验规则:', this.rules);
        }
      },
    },
    headerList: {
      immediate: true,
      handler(newVal) {
        if (newVal && newVal.length) {
          console.log('headerList变化,重新初始化');
          if (this.isEdit && this.editData) {
            this.setFormData(this.editData);
          } else {
            this.initFormData();
          }
          this.initRules();
        }
      },
    },
    showHeaderList: {
      immediate: true,
      handler(newVal) {
        if (newVal ) {
          console.log("222222222222222222", JSON.stringify(newVal));
        }
      },
    },
  },
  methods: {
    initRules() {
      // 初始化校验规则
      const rules = {};
      if (this.headerList && this.headerList.length) {
        this.headerList.forEach(header => {
          // 处理required可能是字符串的情况
          const isRequired = header.required === true || header.required === 'true';
          if (isRequired) {
            rules[header.name] = [{
              required: true,
              message: header.message || `请输入${header.name}`,
              trigger: ['blur', 'change']
            }];
          }
        });
      }
      console.log('生成的校验规则:', rules);
      this.rules = rules;
    },
    initFormData() {
      // 初始化基础表单数据
      const formData = {
        updateTime: this.formatDateTime(new Date()),
      };
      // 根据headerList初始化表单数据
      if (this.headerList && this.headerList.length) {
        this.headerList.forEach(header => {
          if (header.type === 'user') {
            formData[header.name] = [];
          } else {
            formData[header.name] = '';
          }
        });
      }
      // 使用Vue.set确保响应式
      Object.keys(formData).forEach(key => {
        this.$set(this.form, key, formData[key]);
      });
      console.log('初始化后的表单数据:', this.form);
    },
    setFormData(data) {
      // 设置基础表单数据
      const formData = {
        updateTime: this.formatDateTime(new Date()),
      };
      // 根据headerList设置表单数据
      if (this.headerList && this.headerList.length) {
        this.headerList.forEach(header => {
          if (header.type === 'user') {
            formData[header.name] = data[header.name] || [];
          } else {
            formData[header.name] = data[header.name] || '';
          }
        });
      }
      // 使用Vue.set确保响应式
      Object.keys(formData).forEach(key => {
        this.$set(this.form, key, formData[key]);
      });
      // 设置照片列表
      if (data.photos && data.photos.length) {
        this.photoList = data.photos.map((photo) => ({
          name: photo.name,
          url: photo.url,
          status: "success",
        }));
      } else {
        this.photoList = [];
      }
      // 设置图谱列表
      if (data.spectrums && data.spectrums.length) {
        this.spectrumList = data.spectrums.map((spectrum) => ({
          name: spectrum.name,
          url: spectrum.url,
          status: "success",
        }));
      } else {
        this.spectrumList = [];
      }
      // 重置表单校验状态
      this.$nextTick(() => {
        this.$refs.form?.clearValidate();
      });
    },
    formatDateTime(date) {
      const year = date.getFullYear();
      const month = String(date.getMonth() + 1).padStart(2, '0');
      const day = String(date.getDate()).padStart(2, '0');
      const hours = String(date.getHours()).padStart(2, '0');
      const minutes = String(date.getMinutes()).padStart(2, '0');
      const seconds = String(date.getSeconds()).padStart(2, '0');
      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
    },
    handleClose() {
      this.dialogVisible = false;
      this.$refs.form?.resetFields();
      this.photoList = [];
      this.spectrumList = [];
      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);
          this.$emit("success", submitData);
          this.handleClose();
        } else {
          console.log('表单验证失败');
          this.$message.error('请填写必填项');
        }
      });
    },
    handlePhotoChange(file, fileList) {
      this.photoList = fileList;
      this.$refs.form.validateField("photos");
    },
    handleSpectrumChange(file, fileList) {
      this.spectrumList = fileList;
      this.$refs.form.validateField("spectrums");
    },
    handleSpectrumRemove(file) {
      // 处理文件移除逻辑
    },
    handleIPadSpectrum() {
      // TODO: 调用 iPad 选择图谱功能
      console.log("调用 iPad 选择图谱功能");
    },
  },
  mounted() {
    console.log("初始headerList:", this.headerList);
  },
};
</script>
<style scoped lang="less">
::v-deep .el-dialog__body {
  padding: 0;
  max-height: calc(100vh - 200px); // 设置最大高度
}
::v-deep .el-dialog {
  margin-top: 5vh !important; // 调整弹窗位置
  max-height: 90vh; // 设置弹窗最大高度
  display: flex;
  flex-direction: column;
  .el-dialog__body {
    flex: 1;
    overflow: hidden; // 防止出现双滚动条
  }
}
.sample-dialog {
  height: 100%;
  .sample-content {
    background: #ffffff;
    border-radius: 10px;
    padding: 20px;
    height: 100%;
    display: flex;
    flex-direction: column;
    .form-content {
      flex: 1;
      overflow-y: auto;
      padding: 0 10px;
      max-height: calc(90vh - 250px); // 设置内容区域最大高度
      &::-webkit-scrollbar {
        width: 6px;
      }
      &::-webkit-scrollbar-thumb {
        background: #c0c4cc;
        border-radius: 3px;
      }
      &::-webkit-scrollbar-track {
        background: #f5f7fa;
      }
    }
  }
}
.dialog-footer {
  align-items: center;
  display: flex;
  justify-content: center;
  padding: 15px 20px;
  border-top: 1px solid #e4e7ed;
  margin-top: auto; // 保持在底部
  button {
    width: 150px;
  }
}
.upload-demo {
  ::v-deep {
    .el-upload--picture-card {
      width: 120px;
      height: 120px;
      line-height: 120px;
    }
    .el-upload-list--picture-card {
      display: flex;
      flex-wrap: wrap;
      gap: 8px;
      .el-upload-list__item {
        width: 120px;
        height: 120px;
        margin: 0;
      }
    }
    // 让上传按钮始终显示在列表最后
    .el-upload--picture-card {
      order: 9999;
      margin: 0;
    }
  }
  // 包裹容器也使用flex布局
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.el-row {
  margin-bottom: 20px;
  &:last-child {
    margin-bottom: 0; // 最后一行不要margin
  }
}
::v-deep .el-form-item--small.el-form-item {
  margin-bottom: 0;
}
::v-deep .el-form-item__label {
  padding-bottom: 8px;
}
// 优化表单布局
::v-deep .el-form {
  .el-form-item {
    margin-bottom: 15px; // 减小表单项间距
    &:last-child {
      margin-bottom: 0;
    }
  }
}
// 优化上传区域布局
::v-deep .el-upload-list--picture-card {
  display: flex;
  flex-wrap: wrap;
  gap: 8px; // 设置图片间距
}
.upload-file {
  ::v-deep {
    .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;
      margin-top: 8px;
    }
  }
}
</style>
laboratory/src/components/DynamicComponent/addTableHeader.vue
New file
@@ -0,0 +1,350 @@
<template>
  <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">
          <el-form ref="form" :model="form" :rules="rules" label-position="top">
            <el-row :gutter="24">
              <el-col :span="24">
                <el-form-item label="表头名称" prop="sampleCode">
                  <el-input
                    v-model="form.name"
                    style="width: 100%"
                    placeholder="请输入表头名称"
                  />
                </el-form-item>
              </el-col>
              <el-col :span="24">
                <el-form-item label="表头类型" prop="sampleCode">
                  <el-radio-group v-model="form.type" style="width: 100%">
                    <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-group>
                </el-form-item>
              </el-col>
              <el-col :span="24">
                <el-form-item label="操作权限" prop="sampleCode">
                  <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>
                  </el-select>
                </el-form-item>
              </el-col>
              <el-col :span="24">
                <el-form-item label="提示文案" prop="sampleCode">
                  <el-input
                    v-model="form.message"
                    style="width: 100%"
                    placeholder="请输入提示文案"
                  />
                </el-form-item>
              </el-col>
              <el-col :span="24">
                <el-form-item label="是否必填" prop="testTypes">
                  <el-radio-group v-model="form.required">
                    <el-radio label="true">是</el-radio>
                    <el-radio label="false">否</el-radio>
                  </el-radio-group>
                </el-form-item>
              </el-col>
            </el-row>
          </el-form>
        </div>
      </div>
    </div>
    <div slot="footer" class="dialog-footer select-member-footer">
      <el-button @click="handleClose">取 消</el-button>
      <el-button type="primary" @click="handleSubmit">确 定</el-button>
    </div>
  </el-dialog>
</template>
<script>
export default {
  name: "AddDialog",
  props: {
    visible: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      isIPad: /iPad/i.test(navigator.userAgent),
      form: {
        name: "",
        type: "",
        role: "",
        message: "",
        required: "true",
      },
      rules: {
        name: [
          { required: true, message: "请输入表头名称", trigger: "blur" },
        ],
        type: [
          { required: true, message: "请选择表头类型", trigger: "blur" },
        ],
        role: [
          {
            type: "array",
            required: true,
            message: "请至少选择一种检测类型",
            trigger: "change",
          },
        ],
        message: [
          {
            required: true,
            message: "请输入提示文案",
            trigger: "blur",
          },
        ],
        required: [
          {
            required: true,
            message: "请选择是否必填",
            trigger: "change",
          },
        ],
      },
      options: [{
          value: '1',
          label: '黄金糕'
        }, {
          value: '2',
          label: '双皮奶'
        }, {
          value: '3',
          label: '蚵仔煎'
        }, {
          value: '4',
          label: '龙须面'
        }, {
          value: '5',
          label: '北京烤鸭'
        }],
        value: ''
    };
  },
  computed: {
    dialogVisible: {
      get() {
        return this.visible;
      },
      set(val) {
        this.$emit("update:visible", val);
      },
    },
  },
  mounted() {
    // 组件挂载时的初始化逻辑
    console.log('组件已挂载');
  },
  methods: {
    setFormData(data) {
      // 设置基础表单数据
      this.form.name = data.name;
      this.form.type = data.type;
      this.form.role = data.role;
      this.form.message = data.message;
      this.form.required = data.required || "true";
      // 重置表单校验状态
      this.$nextTick(() => {
        this.$refs.form?.clearValidate();
      });
    },
    handleClose() {
      this.dialogVisible = false;
      this.$refs.form?.resetFields();
      this.form = {
        name: "",
        type: "",
        role: "",
        message: "",
        required: "true",
      };
    },
    handleSubmit() {
      this.$refs.form.validate((valid) => {
        if (valid) {
          const submitData = {
            ...this.form,
          };
          this.$emit("confirm", submitData);
        }
      });
    },
  },
};
</script>
<style scoped lang="less">
::v-deep .el-dialog__body {
  padding: 0;
  max-height: calc(100vh - 200px); // 设置最大高度
}
::v-deep .el-dialog {
  margin-top: 5vh !important; // 调整弹窗位置
  max-height: 90vh; // 设置弹窗最大高度
  display: flex;
  flex-direction: column;
  .el-dialog__body {
    flex: 1;
    overflow: hidden; // 防止出现双滚动条
    .el-input__inner {
      width: 100% !important;
    }
  }
}
.sample-dialog {
  height: 100%;
  .sample-content {
    background: #ffffff;
    border-radius: 10px;
    padding: 20px;
    height: 100%;
    display: flex;
    padding-left: 10%;
    flex-direction: column;
    .form-content {
      flex: 1;
      overflow-y: auto;
      padding: 0 10px;
      max-height: calc(90vh - 250px); // 设置内容区域最大高度
      &::-webkit-scrollbar {
        width: 6px;
      }
      &::-webkit-scrollbar-thumb {
        background: #c0c4cc;
        border-radius: 3px;
      }
      &::-webkit-scrollbar-track {
        background: #f5f7fa;
      }
    }
  }
}
.dialog-footer {
  align-items: center;
  display: flex;
  justify-content: center;
  padding: 15px 20px;
  border-top: 1px solid #e4e7ed;
  margin-top: auto; // 保持在底部
  button {
    width: 150px;
  }
}
.upload-demo {
  ::v-deep {
    .el-upload--picture-card {
      width: 120px;
      height: 120px;
      line-height: 120px;
    }
    .el-upload-list--picture-card {
      display: flex;
      flex-wrap: wrap;
      gap: 8px;
      .el-upload-list__item {
        width: 120px;
        height: 120px;
        margin: 0;
      }
    }
    // 让上传按钮始终显示在列表最后
    .el-upload--picture-card {
      order: 9999;
      margin: 0;
    }
  }
  // 包裹容器也使用flex布局
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.el-row {
  margin-bottom: 20px;
  &:last-child {
    margin-bottom: 0; // 最后一行不要margin
  }
}
::v-deep .el-form-item--small.el-form-item {
  margin-bottom: 0;
}
::v-deep .el-form-item__label {
  padding-bottom: 8px;
}
// 优化表单布局
::v-deep .el-form {
  .el-form-item {
    width: 100% !important;
    margin-bottom: 15px !important; // 减小表单项间距
    &:last-child {
      margin-bottom: 15px !important;
    }
  }
}
// 优化上传区域布局
::v-deep .el-upload-list--picture-card {
  display: flex;
  flex-wrap: wrap;
  gap: 8px; // 设置图片间距
}
.upload-file {
  ::v-deep {
    .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;
      margin-top: 8px;
    }
  }
}
</style>
laboratory/src/components/DynamicComponent/index.vue
New file
@@ -0,0 +1,389 @@
<template>
  <div>
    <div class="choose-material" :class="title ? '' : 'has-title'">
      <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
        >
      </div>
      <!-- 选择组件弹窗 -->
      <AddComponentDialog
        :visible="showAddDialog"
        @confirm="addComponent"
        @close="showAddDialog = false"
      />
      <!-- 动态渲染组件 -->
      <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">
          <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>
          <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">
              <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>
            </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-button size="small" icon="el-icon-upload2">点击上传</el-button>
          </el-upload>
        </div>
        <!-- 图片上传 -->
        <div v-else-if="item.type == 'imageUpload'">
          <el-upload
            action="#"
            :file-list="item.data.imageList"
            :on-change="(file, fileList) => handleImageChange(idx, fileList)"
            :on-success="
              (res, file, fileList) =>
                handleImageSuccess(res, file, fileList, idx)
            "
            list-type="picture-card"
          >
            <i class="el-icon-plus"></i>
            <div class="upload-text">上传图片</div>
          </el-upload>
          <div class="uploaf-notice">支持.jpg .png格式</div>
        </div>
        <img
          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"
      @confirm="confirmAddHeader"
    ></addTableHeader>
    <addTableData
      :visible.sync="rowDialog.visible"
      :headerList="rowDialog.headers"
      :editData="rowDialog.form"
      :isEdit="rowDialog.isEdit"
      @success="confirmAddRow"
    >
    </addTableData>
  </div>
</template>
<script>
import AddComponentDialog from "../AddComponentDialog/index.vue";
import AiEditor from "../AiEditor/index.vue";
import Table from "../Table/index.vue";
import addTableHeader from "./addTableHeader.vue";
import addTableData from "./addTableData.vue";
export default {
  name: "DynamicComponent",
  components: {
    AddComponentDialog,
    AiEditor,
    Table,
    addTableHeader,
    addTableData,
  },
  props: {
    title: {
      type: String,
      default: "",
    },
  },
  data() {
    return {
      showAddDialog: false,
      components: [],
      tableHeaderDialog: {
        visible: false,
        idx: null,
        header: "",
      },
      rowDialog: {
        visible: false,
        idx: null,
        rowIndex: null,
        isEdit: false,
        headers: [],
        form: {},
      },
      headerList: [], //编辑的表头列表
    };
  },
  methods: {
    addComponent(type) {
      this.showAddDialog = false;
      const id = Date.now() + Math.random();
      let data = {};
      if (type === "richText") data = { content: "" };
      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 });
    },
    removeComponent(idx) {
      this.components.splice(idx, 1);
    },
    showTableHeaderDialog(idx) {
      this.tableHeaderDialog.visible = true;
      this.tableHeaderDialog.idx = idx;
      this.tableHeaderDialog.header = "";
    },
    confirmAddHeader(data) {
      console.log("data", data);
      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, []);
          } else {
            this.$set(row, data.name, '');
          }
        } else {
          // 如果行数据不是对象,转换为对象
          const newRow = {};
          this.components[idx].data.headers.forEach(header => {
            if (header.name === data.name) {
              if (header.type === 'user') {
                newRow[header.name] = [];
              } else {
                newRow[header.name] = '';
              }
            } else {
              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: "",
      };
    },
    showAddRowDialog(idx, headerList) {
      this.headerList = headerList;
      this.rowDialog.visible = true;
      this.rowDialog.idx = idx;
      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] = [];
        } else {
          this.rowDialog.form[header.name] = "";
        }
      });
    },
    handleEditRow(idx, rowIndex) {
      this.rowDialog.visible = true;
      this.rowDialog.idx = idx;
      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) {
      this.$confirm("确认删除该行数据吗?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      })
        .then(() => {
          this.components[idx].data.rows.splice(rowIndex, 1);
          this.$message.success("删除成功");
        })
        .catch(() => {});
    },
    handleFileChange(idx, fileList) {
      this.components[idx].data.fileList = fileList;
    },
    handleImageChange(idx, fileList) {
      this.components[idx].data.imageList = fileList;
    },
    handleImageSuccess(res, file, fileList, idx) {
      // 假设后端返回的图片地址在 res.url
      file.url = res.url;
      this.components[idx].data.imageList = fileList;
    },
  },
};
</script>
<style scoped lang="less">
.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;
  .delete-icon {
    width: 16px;
    height: 16px;
    cursor: pointer;
  }
}
::v-deep .el-upload {
  width: 104px;
  height: 104px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  .upload-text {
    font-weight: 400;
    font-size: 14px;
    color: rgba(0, 0, 0, 0.85);
    line-height: 22px;
    margin-top: 13px;
  }
}
.uploaf-notice {
  font-weight: 400;
  font-size: 14px;
  color: rgba(0, 0, 0, 0.85);
  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;
  }
}
</style>
laboratory/src/layouts/index.vue
@@ -3,7 +3,7 @@
    <!-- 判断是否在空白页打开 -->
    <template v-if="!isOneself">
      <div class="app-wrapper">
        <div class="left" :style="{ width: isFold ? '0px' : '258px' }">
        <div class="left" :style="{ width: isFold ? '0px' : '298px' }">
          <!-- 系统标题 -->
          <div v-if="!isFold" class="system-title">
            <div class="image">
@@ -19,7 +19,7 @@
          <div v-if="!isFold" class="sidebar-bottom-bg"></div>
        </div>
        <!-- 右侧展示内容 -->
        <div class="main-container" :style="{ width: isFold ? '100%' : 'calc(100% - 258px)' }">
        <div class="main-container" :style="{ width: isFold ? '100%' : 'calc(100% - 298px)' }">
          <HeaderNav class="header-main" />
          <AppContent class="app-main" />
        </div>
laboratory/src/router/index.js
@@ -183,6 +183,31 @@
                component: () => import("../views/dataManagement/confirmation-sheet/components/add.vue"),
            },
            {
                path: "scheme-management",
                name: "schemeManagement",
                meta: {
                    title: "实验方案管理",
                },
                component: () => import("../views/dataManagement/schemeManagement/list.vue"),
            },
            {
                path: "scheme-management/add",
                name: "addSchemeManagement",
                meta: {
                    title: "添加实验方案",
                    hide: true,
                },
                component: () => import("../views/dataManagement/schemeManagement/addPlan.vue"),
            }, {
                path: "scheme-management/stop-experiment",
                name: "schemeManagementStopExperiment",
                meta: {
                    title: "终止实验方案",
                    hide: true,
                },
                component: () => import("../views/dataManagement/schemeManagement/stop-experiment.vue"),
            },
            {
                path: "/sampleManage",
                meta: {
                    title: "样品管理",
@@ -317,6 +342,14 @@
                },
                component: () => import("../views/dataManagement/testResultReport/detail.vue"),
            },
            {
                path: "suspendExperiment",
                meta: {
                    title: "实验中止审批",
                    keepAlive: true,
                },
                component: () => import("../views/dataManagement/suspendExperiment/list.vue"),
            },
        ],
    },
    {
@@ -378,6 +411,92 @@
        ],
    },
    {
        path: "/chemistQa",
        component: Layouts,
        meta: {
            title: "化验师QA专题报告",
        },
        children: [
            {
                path: "projectTesting",
                meta: {
                    title: "项目检测项、检验包列表",
                    keepAlive: true,
                },
                component: () => import("../views/chemistQa/projectTesting/index.vue"),
            },
            {
                path: "add",
                meta: {
                    title: "新增检测项",
                    hide: true,
                    keepAlive: true,
                },
                component: () => import("../views/chemistQa/projectTesting/add.vue"),
            },
            {
                path: "addDetectionReport",
                meta: {
                    title: "新增报告",
                    hide: true,
                    keepAlive: true,
                },
                component: () => import("../views/chemistQa/projectTesting/addDetectionReport.vue"),
            },
            {
                path: "pilotAndProduction",
                meta: {
                    title: "中试、生产验证试验检验分析报告",
                    keepAlive: true,
                },
                component: () => import("../views/chemistQa/pilotAndProduction/index.vue"),
            },
            {
                path: "addPilot",
                meta: {
                    title: "新增中试、生产验证试验检验分析报告",
                    hide: true,
                    keepAlive: true,
                },
                component: () => import("../views/chemistQa/pilotAndProduction/add.vue"),
            },
            {
                path: "rawMaterials",
                meta: {
                    title: "原辅料、包材、竞品检验分析报告",
                    keepAlive: true,
                },
                component: () => import("../views/chemistQa/rawMaterials/index.vue"),
            },
            {
                path: "addRawMaterials",
                meta: {
                    title: "新增原辅料、包材、竞品检验分析报告",
                    hide: true,
                    keepAlive: true,
                },
                component: () => import("../views/chemistQa/rawMaterials/add.vue"),
            },
            {
                path: "productApproval",
                meta: {
                    title: "产品报批及项目工作总计报告",
                    keepAlive: true,
                },
                component: () => import("../views/chemistQa/productApproval/index.vue"),
            },
            {
                path: "addProductApproval",
                meta: {
                    title: "新增产品报批及项目工作总计报告",
                    hide: true,
                    keepAlive: true,
                },
                component: () => import("../views/chemistQa/productApproval/add.vue"),
            },
        ],
    },
    {
        path: "/deliveryAssessment",
        component: Layouts,
        meta: {
@@ -416,7 +535,7 @@
                component: () => import("../views/deliveryAssessment/restsTask"),
            },
            {
                // 超级管理员 工艺工程师
                // 超级管理员 工艺工程师 审批人
                path: "clinicalTrial",
                meta: {
                    title: "临床试验积分列表",
@@ -424,7 +543,7 @@
                component: () => import("../views/deliveryAssessment/clinicalTrial"),
            },
            {
                // 化验师 审批人
                // 化验师 审批人 工艺工程师
                path: "testingAndEvaluation",
                meta: {
                    title: "检测项评定列表",
@@ -448,7 +567,7 @@
                component: () => import("../views/deliveryAssessment/assayTaskList"),
            },
            {
                // 工艺工程师
                // 工艺工程师 审批人
                path: "processEngineerEvaluate",
                meta: {
                    title: "工艺工程师工作评定详情",
@@ -493,6 +612,46 @@
                },
                component: () => import("../views/deliveryAssessment/chemistEvaluate/add"),
            },
            {
                // 工艺工程师
                path: "QAList",
                name: 'QAList',
                meta: {
                    title: "化验师QA专题报告评定",
                    keepAlive: true,
                },
                component: () => import("../views/deliveryAssessment/QA"),
            },
            {
                // 审批人
                path: "experimenterJobEvaluation",
                name: 'ExperimenterJobEvaluation',
                meta: {
                    title: "实验员工作评定详情",
                    keepAlive: true,
                },
                component: () => import("../views/deliveryAssessment/experimenterJobEvaluation"),
            },
            {
                // 审批人
                path: "technicianJobEvaluation",
                name: 'TechnicianJobEvaluation',
                meta: {
                    title: "化验师工作评定详情",
                    keepAlive: true,
                },
                component: () => import("../views/deliveryAssessment/technicianJobEvaluation"),
            },
            {
                // 审批人
                path: "reportEvaluation",
                name: 'ReportEvaluation',
                meta: {
                    title: "专业报告库评定",
                    keepAlive: true,
                },
                component: () => import("../views/deliveryAssessment/reportEvaluation"),
            },
        ]
    }
];
laboratory/src/views/chemistQa/pilotAndProduction/add.vue
New file
@@ -0,0 +1,172 @@
<template>
    <div>
        <Card>
            <div class="header-title" style="width: 100%;">
                <div class="header-title-left">
                    <img src="@/assets/public/headercard.png" />
                    <div>所属项目组</div>
                </div>
                <div class="header-title-right">
                    <el-button @click="showChoose = true" class="el-icon-circle-plus-outline" type="primary">
                        选择项目组</el-button>
                </div>
            </div>
            <Table :height="null" :queryForm="queryForm" :total="0">
                <template>
                    <el-table-column prop="name" label="项目组名称" />
                    <el-table-column prop="age" label="项目负责人" />
                    <el-table-column prop="age" label="项目组成员" />
                    <el-table-column prop="age" label="创建时间" />
                </template>
            </Table>
            <el-form ref="form" :model="form" :rules="rules" inline label-position="top" style="margin-top: 38px">
                <el-row :gutter="20">
                    <el-col :span="24">
                        <el-form-item prop="name" label="报告内容">
                            <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告内容" />
                        </el-form-item>
                        <el-form-item prop="name" label="报告编号" style="margin-left: 100px;">
                            <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                        </el-form-item>
                    </el-col>
                    <el-col :span="24">
                        <el-form-item prop="name" label="制定人">
                            <el-input v-model="form.name" style="width: 100%;" placeholder="请输入制定人" />
                        </el-form-item>
                        <el-form-item prop="name" label="制定日期" style="margin-left: 100px;">
                            <el-date-picker :prefix-icon="null" v-model="form.createTime"  type="date" placeholder="请选择日期" />
                        </el-form-item>
                    </el-col>
                </el-row>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>报告正文</div>
                    </div>
                </div>
                <form-item prop="name" style="margin-top: 38px">
                    <ai-editor v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                </form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>附件</div>
                    </div>
                </div>
                <el-form-item prop="name" style="margin-top: 38px">
                    <el-upload action="https://jsonplaceholder.typicode.com/posts/" :file-list="fileList">
                        <el-button size="small" type="primary">点击上传</el-button>
                    </el-upload>
                </el-form-item>
                <div class="end-btn" style="margin-top: 38px">
                    <el-button type="primary">发送</el-button>
                    <el-button type="default">存草稿</el-button>
                </div>
            </el-form>
        </Card>
    </div>
</template>
<script>
import { Card } from 'element-ui';
import AiEditor from '@/components/AiEditor'
export default {
    components: { AiEditor },
    data() {
        return {
            form: {
                planName: "",
                planCode: "",
                stage: "",
                creator: "",
                createTime: "",
                approvalComment: "",
                status: "pending",
                approver: "",
                approveTime: ""
            },
            fileList: [], // 附件列表
            showChoose: false,
            radio1: 1,
            rules: {},
            status: "1",
            remark: "",
            queryForm: {
            }
        }
    }
}
</script>
<style lang="less" scoped>
.header-title {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 20px;
    gap: 13px;
    .header-title-left {
        display: flex;
        align-items: center;
        gap: 13px;
        margin-top: 38px;
        img {
            width: 12px;
            height: 19px;
        }
        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;
            }
        }
        span {
            flex-shrink: 0;
            font-weight: bold;
            font-size: 18px;
            color: #222222;
            line-height: 27px;
            font-family: "Source Han Sans CN Bold Bold";
        }
    }
    .header-title-left :first-child {
        margin-top: 0;
    }
}
.header-title:first-child {
    .header-title-left {
        margin-top: 0;
    }
}
.end-btn {
    display: flex;
    align-items: center;
    gap: 10px;
    button {
        width: 180px;
        height: 36px;
        // background: #409EFF;
    }
}
</style>
laboratory/src/views/chemistQa/pilotAndProduction/components/approval/index.vue
New file
@@ -0,0 +1,397 @@
<template>
    <el-dialog :title="dialogTitle" :visible.sync="visible" width="80%" po :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" style="width: 100%;">
                            <div class="header-title-left">
                                <img src="@/assets/public/headercard.png" />
                                <div>所属项目组</div>
                            </div>
                        </div>
                        <Table :height="null" :queryForm="queryForm" :total="0" @currentChange="handleCurrentChange"
                            @sizeChange="handleSizeChange">
                            <template>
                                <el-table-column prop="name" label="项目组名称" />
                                <el-table-column prop="age" label="项目负责人" />
                                <el-table-column prop="age" label="项目组成员" />
                                <el-table-column prop="age" label="创建时间" />
                            </template>
                        </Table>
                        <el-form ref="form" :model="form" :rules="rules" inline label-position="top"
                            style="margin-top: 38px">
                            <el-row :gutter="20">
                                <el-col :span="24">
                                    <el-form-item prop="name" label="报告内容">
                                        <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告内容" />
                                    </el-form-item>
                                    <el-form-item prop="name" label="报告编号" style="margin-left: 100px;">
                                        <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                                    </el-form-item>
                                </el-col>
                                <el-col :span="24">
                                    <el-form-item prop="name" label="制定人">
                                        <el-input v-model="form.name" style="width: 100%;" placeholder="请输入制定人" />
                                    </el-form-item>
                                    <el-form-item prop="name" label="制定日期" style="margin-left: 100px;">
                                        <el-date-picker :prefix-icon="null" v-model="form.createTime" type="date"
                                            placeholder="请选择日期" />
                                    </el-form-item>
                                </el-col>
                            </el-row>
                            <div class="header-title" style="width: 100%;">
                                <div class="header-title-left">
                                    <img src="@/assets/public/headercard.png" />
                                    <div>报告正文</div>
                                </div>
                            </div>
                            <el-form-item prop="name" style="margin-top: 38px">
                                <ai-editor v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </el-form-item>
                            <el-form-item prop="name" style="margin-top: 38px">
                                <el-upload action="https://jsonplaceholder.typicode.com/posts/" :file-list="fileList">
                                    <el-button size="small" type="primary">点击上传</el-button>
                                </el-upload>
                            </el-form-item>
                        </el-form>
                    </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>
            </div>
        </div>
        <div class="approval-dialog-approve">
            <el-row :span="24">
                <el-col :span="12">
                    <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>
                </el-col>
                <el-col :span="12">
                    <div class="remark">
                        <div class="remark-title">审批意见</div>
                        <el-input type="textarea" v-model="remark" placeholder="请输入审批意见" />
                    </div>
                </el-col>
            </el-row>
        </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>
</template>
<script>
import ApprovalProcess from '@/components/approvalProcess'
import AiEditor from '@/components/AiEditor'
export default {
    name: "ApprovalDialog",
    components: {
        ApprovalProcess,
        AiEditor
    },
    props: {
        visible: {
            type: Boolean,
            default: false,
        },
        type: {
            type: String,
            default: "approve", // approve-审批,view-查看
        },
        data: {
            type: Object,
            default: () => ({}),
        },
    },
    data() {
        return {
            form: {
                planName: "",
                planCode: "",
                stage: "",
                creator: "",
                createTime: "",
                approvalComment: "",
                status: "pending",
                approver: "",
                approveTime: ""
            },
            radio1: 1,
            rules: {},
            status: "1",
            remark: "",
        };
    },
    computed: {
        dialogTitle() {
            return this.type === "approve" ? "审批" : "审批详情";
        },
    },
    watch: {
        data: {
            handler(val) {
                if (val) {
                    this.form = { ...val };
                }
            },
            immediate: true,
        },
    },
    methods: {
        handleClose() {
            this.$emit("close");
            this.form.approvalComment = "";
        },
        handleApprove() {
            if (!this.form.approvalComment) {
                this.$message.warning("请输入审批意见");
                return;
            }
            this.$emit("approve", {
                ...this.form,
                status: "approved",
            });
        },
        handleReject() {
            if (!this.form.approvalComment) {
                this.$message.warning("请输入审批意见");
                return;
            }
            this.$emit("reject", {
                ...this.form,
                status: "rejected",
            });
        },
    },
};
</script>
<style scoped lang="less">
::v-deep .el-dialog__header {
    border-bottom: 1px solid #e4e7ed;
}
.approval-dialog {
    display: flex;
    height: 40vh;
    .approval-content {
        flex: 3;
        margin-right: 20px;
        background: #ffffff;
        box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08);
        border-radius: 10px;
    }
    .approval-flow {
        padding: 40px 20px;
        // width: 405px;
        flex: 2;
        background: #ffffff;
        box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08);
        border-radius: 10px;
        .flow-title {
            font-size: 16px;
            font-weight: bold;
            margin-bottom: 20px;
            color: #303133;
        }
        .flow-content {
            height: calc(100% - 40px);
            overflow-y: auto;
            .el-form--inline .el-form-item {
                margin-right: 83px;
            }
        }
    }
}
.approval-content-card {
    height: calc(100% - 100px) !important;
    box-shadow: none !important;
}
.header-title {
    // display: flex;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 20px;
    gap: 13px;
    .header-title-left {
        display: flex;
        align-items: center;
        gap: 13px;
        margin-top: 38px;
        img {
            width: 12px;
            height: 19px;
        }
        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;
            }
        }
        span {
            flex-shrink: 0;
            font-weight: bold;
            font-size: 18px;
            color: #222222;
            line-height: 27px;
            font-family: "Source Han Sans CN Bold Bold";
        }
    }
    .header-title-left :first-child {
        margin-top: 0;
    }
}
.header-title:first-child {
    .header-title-left {
        margin-top: 0;
    }
}
.item-title {
    padding-left: 25px;
    span {
        flex-shrink: 0;
        font-weight: bold;
        font-size: 14px;
        color: #222222;
        line-height: 27px;
        font-family: "Source Han Sans CN Bold Bold";
        margin: 18px 0;
        &:before {
            content: "*";
            color: #f56c6c;
            margin-right: 4px;
        }
    }
}
.approval-dialog-approve {
    padding: 38px 20px;
    // display: flex;
    align-content: center;
    .status {
        margin-right: 40px;
        max-width: 60%;
    }
    //   align-items: center;
    .status-title {
        color: #222222;
        font-family: "SourceHanSansCN-Medium";
        line-height: 14px;
        margin-bottom: 16px;
    }
    .status-content {
        display: flex;
        align-items: center;
        width: 100%;
        gap: 16px;
        background: #ffffff;
        border-radius: 10px;
        border: 1px solid rgba(4, 156, 154, 0.5);
        .resolve {
            border-radius: 10px;
            flex: 1;
            font-size: 16px;
            // padding: 5px 55px;
            font-weight: 400;
            color: #333333;
            cursor: pointer;
            line-height: 32px;
            display: flex;
            align-items: center;
            justify-content: center
        }
        .reject {
            flex: 1;
            border-radius: 10px;
            font-size: 16px;
            line-height: 32px;
            // padding: 5px 55px;
            font-weight: 400;
            color: #333333;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center
        }
        .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;
    }
}
.dialog-footer {
    align-items: center;
    display: flex;
    justify-content: center;
    gap: 20px;
    button {
        width: 150px;
    }
}
</style>
laboratory/src/views/chemistQa/pilotAndProduction/index.vue
New file
@@ -0,0 +1,211 @@
<template>
    <div class="list">
        <TableCustom :queryForm="queryForm" :total="total" @currentChange="handleCurrentChange"
            @sizeChange="handleSizeChange">
            <template #search>
                <el-form :model="form" label-width="auto" inline>
                    <el-form-item label="所属项目组:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="报告标题">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="报告编号">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="状态:">
                        <el-select v-model="form.status" placeholder="请选择">
                            <el-option label="待审核" value="1"></el-option>
                            <el-option label="已通过" value="0"></el-option>
                            <el-option label="已驳回" value="2"></el-option>
                        </el-select>
                    </el-form-item>
                    <el-form-item label="" style="margin-left: 63px;">
                        <el-button type="default" style="margin-right: 10px;">重置</el-button>
                        <el-button type="primary">查询</el-button>
                    </el-form-item>
                </el-form>
            </template>
            <template #setting>
                <el-button @click="handleAddProject" class="el-icon-plus" type="primary">
                    新增</el-button>
                <div class="table-setting">
                    <div class="table-title">
                        中试、生产验证试验检验分析报告
                    </div>
                    <div class="table-tit">
                        草稿箱
                    </div>
                </div>
            </template>
            <template #table>
                <el-table-column prop="name" label="所属项目组" />
                <el-table-column prop="age" label="报告标题" />
                <el-table-column prop="age" label="报告编号" />
                <el-table-column prop="age" label="制定人" />
                <el-table-column prop="age" label="制定日期" />
                <el-table-column prop="age" label="操作">
                    <template #default="{ row }">
                        <el-button type="text" @click="showApproval = true">详情</el-button>
                    </template>
                </el-table-column>
            </template>
        </TableCustom>
        <Approval :visible="showApproval" @close="showApproval = false" />
        <ShowDelConfirm :show="showDelConfirm" @close="showDelConfirm = false" @confirm="handleDelConfirm" />
        <ShowDelConfirm :title="changeStatusTitle" :tip="changeStatusTip" :show="changeStatus"
            @close="changeStatus = false" @confirm="handleChangeStatusConfirm" />
    </div>
</template>
<script>
import Approval from './components/approval'
export default {
    name: 'ProjectList',
    components: {
        Approval
    },
    data() {
        return {
            form: {
                name: ''
            },
            showDelConfirm: false,
            rowId: '',
            changeStatus: false,
            showApproval: false,
            changeStatusTitle: '',
            changeStatusTip: '',
            queryForm: {
                pageSize: 10,
                pageNum: 1
            },
            total: 0
        }
    },
    methods: {
        handleAddProject() {
            this.$router.push('/chemistQa/addPilot')
        },
        handleDel(row) {
            this.rowId = row.id
            this.showDelConfirm = true
        },
        handleDelConfirm() {
            this.showDelConfirm = false
            this.msgsuccess('删除成功')
            this.rowId = ''
            this.getList()
        },
        handleChangeStatus(row, status) {
            this.rowId = row.id
            this.changeStatusTitle = status == 1 ? '确认要封存这个项目组吗?' : '确认要解封该项目组吗?'
            this.changeStatusTip = status == 1 ? '封存后项目组内人员看不到数据,审批人仍然可见数据。' : '解封后项目组内人员数据恢复。'
            this.changeStatus = true
        },
        handleChangeStatusConfirm() {
            this.changeStatus = false
            this.msgsuccess('操作成功')
            this.rowId = ''
            this.changeStatusTitle = ''
            this.changeStatusTip = ''
            this.getList()
        },
        handleCurrentChange(page) {
            this.queryForm.pageNum = page
            this.getList()
        },
        handleSizeChange(size) {
            this.queryForm.pageSize = size
            this.getList()
        },
        getList() {
        }
    }
}
</script>
<style scoped lang="less">
.el-icon-plus{
    margin-bottom: 20px;
}
.header-content {
    font-family: PingFangSC, PingFang SC;
    font-weight: 400;
    font-size: 14px;
    color: rgba(0, 0, 0, 0.88);
    margin-left: 30px;
}
.box-title {
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #222222;
    line-height: 27px;
    display: flex;
    align-items: center;
}
.header-icon {
    width: 20px;
    height: 20px;
    margin-right: 10px;
}
.header-box {
    border-radius: 16px;
    margin-bottom: 30px;
}
.table-setting {
    display: flex;
    gap: 14px;
}
.table-tit {
    background: #FAFAFC;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #DCDFE6;
    width: 166px;
    height: 50px;
    background: #FFFFFF;
    border-radius: 8px 8px 0px 0px;
    display: flex;
    align-items: center;
    justify-content: center;
    // margin-bottom: 21px;
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #049C9A;
    line-height: 27px;
}
.list {
    height: 100%;
}
.table-title {
    // width: 166px;1
    height: 50px;
    padding: 0 29px;
    background: #FFFFFF;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #049C9A;
    display: flex;
    align-items: center;
    justify-content: center;
    // margin-bottom: 21px;
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #049C9A;
    line-height: 27px;
}
</style>
laboratory/src/views/chemistQa/productApproval/add.vue
New file
@@ -0,0 +1,172 @@
<template>
    <div>
        <Card>
            <div class="header-title" style="width: 100%;">
                <div class="header-title-left">
                    <img src="@/assets/public/headercard.png" />
                    <div>所属项目组</div>
                </div>
                <div class="header-title-right">
                    <el-button @click="showChoose = true" class="el-icon-circle-plus-outline" type="primary">
                        选择项目组</el-button>
                </div>
            </div>
            <Table :height="null" :queryForm="queryForm" :total="0">
                <template>
                    <el-table-column prop="name" label="项目组名称" />
                    <el-table-column prop="age" label="项目负责人" />
                    <el-table-column prop="age" label="项目组成员" />
                    <el-table-column prop="age" label="创建时间" />
                </template>
            </Table>
            <el-form ref="form" :model="form" :rules="rules" inline label-position="top" style="margin-top: 38px">
                <el-row :gutter="20">
                    <el-col :span="24">
                        <el-form-item prop="name" label="报告内容">
                            <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告内容" />
                        </el-form-item>
                        <el-form-item prop="name" label="报告编号" style="margin-left: 100px;">
                            <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                        </el-form-item>
                    </el-col>
                    <el-col :span="24">
                        <el-form-item prop="name" label="制定人">
                            <el-input v-model="form.name" style="width: 100%;" placeholder="请输入制定人" />
                        </el-form-item>
                        <el-form-item prop="name" label="制定日期" style="margin-left: 100px;">
                            <el-date-picker :prefix-icon="null" v-model="form.createTime"  type="date" placeholder="请选择日期" />
                        </el-form-item>
                    </el-col>
                </el-row>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>报告正文</div>
                    </div>
                </div>
                <form-item prop="name" style="margin-top: 38px">
                    <ai-editor v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                </form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>附件</div>
                    </div>
                </div>
                <el-form-item prop="name" style="margin-top: 38px">
                    <el-upload action="https://jsonplaceholder.typicode.com/posts/" :file-list="fileList">
                        <el-button size="small" type="primary">点击上传</el-button>
                    </el-upload>
                </el-form-item>
                <div class="end-btn" style="margin-top: 38px">
                    <el-button type="primary">发送</el-button>
                    <el-button type="default">存草稿</el-button>
                </div>
            </el-form>
        </Card>
    </div>
</template>
<script>
import { Card } from 'element-ui';
import AiEditor from '@/components/AiEditor'
export default {
    components: { AiEditor },
    data() {
        return {
            form: {
                planName: "",
                planCode: "",
                stage: "",
                creator: "",
                createTime: "",
                approvalComment: "",
                status: "pending",
                approver: "",
                approveTime: ""
            },
            fileList: [], // 附件列表
            showChoose: false,
            radio1: 1,
            rules: {},
            status: "1",
            remark: "",
            queryForm: {
            }
        }
    }
}
</script>
<style lang="less" scoped>
.header-title {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 20px;
    gap: 13px;
    .header-title-left {
        display: flex;
        align-items: center;
        gap: 13px;
        margin-top: 38px;
        img {
            width: 12px;
            height: 19px;
        }
        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;
            }
        }
        span {
            flex-shrink: 0;
            font-weight: bold;
            font-size: 18px;
            color: #222222;
            line-height: 27px;
            font-family: "Source Han Sans CN Bold Bold";
        }
    }
    .header-title-left :first-child {
        margin-top: 0;
    }
}
.header-title:first-child {
    .header-title-left {
        margin-top: 0;
    }
}
.end-btn {
    display: flex;
    align-items: center;
    gap: 10px;
    button {
        width: 180px;
        height: 36px;
        // background: #409EFF;
    }
}
</style>
laboratory/src/views/chemistQa/productApproval/components/approval/index.vue
New file
@@ -0,0 +1,397 @@
<template>
    <el-dialog :title="dialogTitle" :visible.sync="visible" width="80%" po :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" style="width: 100%;">
                            <div class="header-title-left">
                                <img src="@/assets/public/headercard.png" />
                                <div>所属项目组</div>
                            </div>
                        </div>
                        <Table :height="null" :queryForm="queryForm" :total="0" @currentChange="handleCurrentChange"
                            @sizeChange="handleSizeChange">
                            <template>
                                <el-table-column prop="name" label="项目组名称" />
                                <el-table-column prop="age" label="项目负责人" />
                                <el-table-column prop="age" label="项目组成员" />
                                <el-table-column prop="age" label="创建时间" />
                            </template>
                        </Table>
                        <el-form ref="form" :model="form" :rules="rules" inline label-position="top"
                            style="margin-top: 38px">
                            <el-row :gutter="20">
                                <el-col :span="24">
                                    <el-form-item prop="name" label="报告内容">
                                        <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告内容" />
                                    </el-form-item>
                                    <el-form-item prop="name" label="报告编号" style="margin-left: 100px;">
                                        <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                                    </el-form-item>
                                </el-col>
                                <el-col :span="24">
                                    <el-form-item prop="name" label="制定人">
                                        <el-input v-model="form.name" style="width: 100%;" placeholder="请输入制定人" />
                                    </el-form-item>
                                    <el-form-item prop="name" label="制定日期" style="margin-left: 100px;">
                                        <el-date-picker :prefix-icon="null" v-model="form.createTime" type="date"
                                            placeholder="请选择日期" />
                                    </el-form-item>
                                </el-col>
                            </el-row>
                            <div class="header-title" style="width: 100%;">
                                <div class="header-title-left">
                                    <img src="@/assets/public/headercard.png" />
                                    <div>报告正文</div>
                                </div>
                            </div>
                            <el-form-item prop="name" style="margin-top: 38px">
                                <ai-editor v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </el-form-item>
                            <el-form-item prop="name" style="margin-top: 38px">
                                <el-upload action="https://jsonplaceholder.typicode.com/posts/" :file-list="fileList">
                                    <el-button size="small" type="primary">点击上传</el-button>
                                </el-upload>
                            </el-form-item>
                        </el-form>
                    </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>
            </div>
        </div>
        <div class="approval-dialog-approve">
            <el-row :span="24">
                <el-col :span="12">
                    <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>
                </el-col>
                <el-col :span="12">
                    <div class="remark">
                        <div class="remark-title">审批意见</div>
                        <el-input type="textarea" v-model="remark" placeholder="请输入审批意见" />
                    </div>
                </el-col>
            </el-row>
        </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>
</template>
<script>
import ApprovalProcess from '@/components/approvalProcess'
import AiEditor from '@/components/AiEditor'
export default {
    name: "ApprovalDialog",
    components: {
        ApprovalProcess,
        AiEditor
    },
    props: {
        visible: {
            type: Boolean,
            default: false,
        },
        type: {
            type: String,
            default: "approve", // approve-审批,view-查看
        },
        data: {
            type: Object,
            default: () => ({}),
        },
    },
    data() {
        return {
            form: {
                planName: "",
                planCode: "",
                stage: "",
                creator: "",
                createTime: "",
                approvalComment: "",
                status: "pending",
                approver: "",
                approveTime: ""
            },
            radio1: 1,
            rules: {},
            status: "1",
            remark: "",
        };
    },
    computed: {
        dialogTitle() {
            return this.type === "approve" ? "审批" : "审批详情";
        },
    },
    watch: {
        data: {
            handler(val) {
                if (val) {
                    this.form = { ...val };
                }
            },
            immediate: true,
        },
    },
    methods: {
        handleClose() {
            this.$emit("close");
            this.form.approvalComment = "";
        },
        handleApprove() {
            if (!this.form.approvalComment) {
                this.$message.warning("请输入审批意见");
                return;
            }
            this.$emit("approve", {
                ...this.form,
                status: "approved",
            });
        },
        handleReject() {
            if (!this.form.approvalComment) {
                this.$message.warning("请输入审批意见");
                return;
            }
            this.$emit("reject", {
                ...this.form,
                status: "rejected",
            });
        },
    },
};
</script>
<style scoped lang="less">
::v-deep .el-dialog__header {
    border-bottom: 1px solid #e4e7ed;
}
.approval-dialog {
    display: flex;
    height: 40vh;
    .approval-content {
        flex: 3;
        margin-right: 20px;
        background: #ffffff;
        box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08);
        border-radius: 10px;
    }
    .approval-flow {
        padding: 40px 20px;
        // width: 405px;
        flex: 2;
        background: #ffffff;
        box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08);
        border-radius: 10px;
        .flow-title {
            font-size: 16px;
            font-weight: bold;
            margin-bottom: 20px;
            color: #303133;
        }
        .flow-content {
            height: calc(100% - 40px);
            overflow-y: auto;
            .el-form--inline .el-form-item {
                margin-right: 83px;
            }
        }
    }
}
.approval-content-card {
    height: calc(100% - 100px) !important;
    box-shadow: none !important;
}
.header-title {
    // display: flex;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 20px;
    gap: 13px;
    .header-title-left {
        display: flex;
        align-items: center;
        gap: 13px;
        margin-top: 38px;
        img {
            width: 12px;
            height: 19px;
        }
        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;
            }
        }
        span {
            flex-shrink: 0;
            font-weight: bold;
            font-size: 18px;
            color: #222222;
            line-height: 27px;
            font-family: "Source Han Sans CN Bold Bold";
        }
    }
    .header-title-left :first-child {
        margin-top: 0;
    }
}
.header-title:first-child {
    .header-title-left {
        margin-top: 0;
    }
}
.item-title {
    padding-left: 25px;
    span {
        flex-shrink: 0;
        font-weight: bold;
        font-size: 14px;
        color: #222222;
        line-height: 27px;
        font-family: "Source Han Sans CN Bold Bold";
        margin: 18px 0;
        &:before {
            content: "*";
            color: #f56c6c;
            margin-right: 4px;
        }
    }
}
.approval-dialog-approve {
    padding: 38px 20px;
    // display: flex;
    align-content: center;
    .status {
        margin-right: 40px;
        max-width: 60%;
    }
    //   align-items: center;
    .status-title {
        color: #222222;
        font-family: "SourceHanSansCN-Medium";
        line-height: 14px;
        margin-bottom: 16px;
    }
    .status-content {
        display: flex;
        align-items: center;
        width: 100%;
        gap: 16px;
        background: #ffffff;
        border-radius: 10px;
        border: 1px solid rgba(4, 156, 154, 0.5);
        .resolve {
            border-radius: 10px;
            flex: 1;
            font-size: 16px;
            // padding: 5px 55px;
            font-weight: 400;
            color: #333333;
            cursor: pointer;
            line-height: 32px;
            display: flex;
            align-items: center;
            justify-content: center
        }
        .reject {
            flex: 1;
            border-radius: 10px;
            font-size: 16px;
            line-height: 32px;
            // padding: 5px 55px;
            font-weight: 400;
            color: #333333;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center
        }
        .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;
    }
}
.dialog-footer {
    align-items: center;
    display: flex;
    justify-content: center;
    gap: 20px;
    button {
        width: 150px;
    }
}
</style>
laboratory/src/views/chemistQa/productApproval/index.vue
New file
@@ -0,0 +1,211 @@
<template>
    <div class="list">
        <TableCustom :queryForm="queryForm" :total="total" @currentChange="handleCurrentChange"
            @sizeChange="handleSizeChange">
            <template #search>
                <el-form :model="form" label-width="auto" inline>
                    <el-form-item label="所属项目组:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="报告标题">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="报告编号">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="状态:">
                        <el-select v-model="form.status" placeholder="请选择">
                            <el-option label="待审核" value="1"></el-option>
                            <el-option label="已通过" value="0"></el-option>
                            <el-option label="已驳回" value="2"></el-option>
                        </el-select>
                    </el-form-item>
                    <el-form-item label="" style="margin-left: 63px;">
                        <el-button type="default" style="margin-right: 10px;">重置</el-button>
                        <el-button type="primary">查询</el-button>
                    </el-form-item>
                </el-form>
            </template>
            <template #setting>
                <el-button @click="handleAddProject" class="el-icon-plus" type="primary">
                    新增</el-button>
                <div class="table-setting">
                    <div class="table-title">
                        原辅料、包材、竞品检验分析报告
                    </div>
                    <div class="table-tit">
                        草稿箱
                    </div>
                </div>
            </template>
            <template #table>
                <el-table-column prop="name" label="所属项目组" />
                <el-table-column prop="age" label="报告标题" />
                <el-table-column prop="age" label="报告编号" />
                <el-table-column prop="age" label="制定人" />
                <el-table-column prop="age" label="制定日期" />
                <el-table-column prop="age" label="操作">
                    <template #default="{ row }">
                        <el-button type="text" @click="showApproval = true">详情</el-button>
                    </template>
                </el-table-column>
            </template>
        </TableCustom>
        <Approval :visible="showApproval" @close="showApproval = false" />
        <ShowDelConfirm :show="showDelConfirm" @close="showDelConfirm = false" @confirm="handleDelConfirm" />
        <ShowDelConfirm :title="changeStatusTitle" :tip="changeStatusTip" :show="changeStatus"
            @close="changeStatus = false" @confirm="handleChangeStatusConfirm" />
    </div>
</template>
<script>
import Approval from './components/approval'
export default {
    name: 'ProjectList',
    components: {
        Approval
    },
    data() {
        return {
            form: {
                name: ''
            },
            showDelConfirm: false,
            rowId: '',
            changeStatus: false,
            showApproval: false,
            changeStatusTitle: '',
            changeStatusTip: '',
            queryForm: {
                pageSize: 10,
                pageNum: 1
            },
            total: 0
        }
    },
    methods: {
        handleAddProject() {
            this.$router.push('/chemistQa/addPilot')
        },
        handleDel(row) {
            this.rowId = row.id
            this.showDelConfirm = true
        },
        handleDelConfirm() {
            this.showDelConfirm = false
            this.msgsuccess('删除成功')
            this.rowId = ''
            this.getList()
        },
        handleChangeStatus(row, status) {
            this.rowId = row.id
            this.changeStatusTitle = status == 1 ? '确认要封存这个项目组吗?' : '确认要解封该项目组吗?'
            this.changeStatusTip = status == 1 ? '封存后项目组内人员看不到数据,审批人仍然可见数据。' : '解封后项目组内人员数据恢复。'
            this.changeStatus = true
        },
        handleChangeStatusConfirm() {
            this.changeStatus = false
            this.msgsuccess('操作成功')
            this.rowId = ''
            this.changeStatusTitle = ''
            this.changeStatusTip = ''
            this.getList()
        },
        handleCurrentChange(page) {
            this.queryForm.pageNum = page
            this.getList()
        },
        handleSizeChange(size) {
            this.queryForm.pageSize = size
            this.getList()
        },
        getList() {
        }
    }
}
</script>
<style scoped lang="less">
.el-icon-plus{
    margin-bottom: 20px;
}
.header-content {
    font-family: PingFangSC, PingFang SC;
    font-weight: 400;
    font-size: 14px;
    color: rgba(0, 0, 0, 0.88);
    margin-left: 30px;
}
.box-title {
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #222222;
    line-height: 27px;
    display: flex;
    align-items: center;
}
.header-icon {
    width: 20px;
    height: 20px;
    margin-right: 10px;
}
.header-box {
    border-radius: 16px;
    margin-bottom: 30px;
}
.table-setting {
    display: flex;
    gap: 14px;
}
.table-tit {
    background: #FAFAFC;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #DCDFE6;
    width: 166px;
    height: 50px;
    background: #FFFFFF;
    border-radius: 8px 8px 0px 0px;
    display: flex;
    align-items: center;
    justify-content: center;
    // margin-bottom: 21px;
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #049C9A;
    line-height: 27px;
}
.list {
    height: 100%;
}
.table-title {
    // width: 166px;1
    height: 50px;
    padding: 0 29px;
    background: #FFFFFF;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #049C9A;
    display: flex;
    align-items: center;
    justify-content: center;
    // margin-bottom: 21px;
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #049C9A;
    line-height: 27px;
}
</style>
laboratory/src/views/chemistQa/projectTesting/add.vue
New file
@@ -0,0 +1,173 @@
<template>
    <div>
        <Card>
            <div class="header-title" style="width: 100%;">
                <div class="header-title-left">
                    <img src="@/assets/public/headercard.png" />
                    <div>所属项目组</div>
                </div>
                <div class="header-title-right">
                    <el-button @click="showChoose = true" class="el-icon-circle-plus-outline" type="primary">
                        选择项目组</el-button>
                </div>
            </div>
            <Table :height="null" :queryForm="queryForm" :total="0">
                <template>
                    <el-table-column prop="name" label="项目组名称" />
                    <el-table-column prop="age" label="项目负责人" />
                    <el-table-column prop="age" label="项目组成员" />
                    <el-table-column prop="age" label="创建时间" />
                </template>
            </Table>
            <el-form ref="form" :model="form" :rules="rules" inline label-position="top" style="margin-top: 38px">
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>检测项名称</div>
                    </div>
                </div>
                <form-item prop="name" style="margin-top: 38px">
                    <el-input v-model="form.name" style="width: 100%;" placeholder="请输入检测项名称" />
                </form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>检测项编号</div>
                    </div>
                </div>
                <form-item prop="name" style="margin-top: 38px">
                    <el-input v-model="form.name" style="width: 100%;" placeholder="请输入检测项编号" />
                </form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>备注内容</div>
                    </div>
                </div>
                <form-item prop="name" style="margin-top: 38px">
                    <el-input v-model="form.name" type="textarea" :rows="4" style="width: 100%;" placeholder="请输入备注内容" />
                </form-item>
                <!-- <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>附件</div>
                    </div>
                </div> -->
                <!-- <form-item prop="name" style="margin-top: 38px">
                    <el-upload action="https://jsonplaceholder.typicode.com/posts/" :file-list="fileList">
                        <el-button size="small" type="primary">点击上传</el-button>
                    </el-upload>
                </form-item> -->
                <div class="end-btn" style="margin-top: 38px">
                    <el-button type="primary">发送</el-button>
                    <el-button type="default">存草稿</el-button>
                </div>
            </el-form>
        </Card>
    </div>
</template>
<script>
import { Card } from 'element-ui';
import AiEditor from '@/components/AiEditor'
export default {
    components: { AiEditor },
    data() {
        return {
            form: {
                planName: "",
                planCode: "",
                stage: "",
                creator: "",
                createTime: "",
                approvalComment: "",
                status: "pending",
                approver: "",
                approveTime: ""
            },
            fileList: [], // 附件列表
            showChoose: false,
            radio1: 1,
            rules: {},
            status: "1",
            remark: "",
            queryForm: {
            }
        }
    }
}
</script>
<style lang="less" scoped>
.header-title {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 20px;
    gap: 13px;
    .header-title-left {
        display: flex;
        align-items: center;
        gap: 13px;
        margin-top: 38px;
        img {
            width: 12px;
            height: 19px;
        }
        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;
            }
        }
        span {
            flex-shrink: 0;
            font-weight: bold;
            font-size: 18px;
            color: #222222;
            line-height: 27px;
            font-family: "Source Han Sans CN Bold Bold";
        }
    }
    .header-title-left :first-child {
        margin-top: 0;
    }
}
.header-title:first-child {
    .header-title-left {
        margin-top: 0;
    }
}
.end-btn{
    display: flex;
    align-items: center;
    gap: 10px;
    button{
        width: 180px;
        height: 36px;
        // background: #409EFF;
    }
}
</style>
laboratory/src/views/chemistQa/projectTesting/addDetectionReport.vue
New file
@@ -0,0 +1,172 @@
<template>
    <div>
        <Card>
            <div class="header-title" style="width: 100%;">
                <div class="header-title-left">
                    <img src="@/assets/public/headercard.png" />
                    <div>所属项目组</div>
                </div>
                <div class="header-title-right">
                    <el-button @click="showChoose = true" class="el-icon-circle-plus-outline" type="primary">
                        选择项目组</el-button>
                </div>
            </div>
            <Table :height="null" :queryForm="queryForm" :total="0">
                <template>
                    <el-table-column prop="name" label="项目组名称" />
                    <el-table-column prop="age" label="项目负责人" />
                    <el-table-column prop="age" label="项目组成员" />
                    <el-table-column prop="age" label="创建时间" />
                </template>
            </Table>
            <el-form ref="form" :model="form" :rules="rules" inline label-position="top" style="margin-top: 38px">
                <el-row :gutter="20">
                    <el-col :span="24">
                        <el-form-item prop="name" label="报告内容">
                            <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告内容" />
                        </el-form-item>
                        <el-form-item prop="name" label="报告编号" style="margin-left: 100px;">
                            <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                        </el-form-item>
                    </el-col>
                    <el-col :span="24">
                        <el-form-item prop="name" label="制定人">
                            <el-input v-model="form.name" style="width: 100%;" placeholder="请输入制定人" />
                        </el-form-item>
                        <el-form-item prop="name" label="制定日期" style="margin-left: 100px;">
                            <el-date-picker :prefix-icon="null" v-model="form.createTime"  type="date" placeholder="请选择日期" />
                        </el-form-item>
                    </el-col>
                </el-row>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>报告正文</div>
                    </div>
                </div>
                <form-item prop="name" style="margin-top: 38px">
                    <ai-editor v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                </form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>附件</div>
                    </div>
                </div>
                <el-form-item prop="name" style="margin-top: 38px">
                    <el-upload action="https://jsonplaceholder.typicode.com/posts/" :file-list="fileList">
                        <el-button size="small" type="primary">点击上传</el-button>
                    </el-upload>
                </el-form-item>
                <div class="end-btn" style="margin-top: 38px">
                    <el-button type="primary">发送</el-button>
                    <el-button type="default">存草稿</el-button>
                </div>
            </el-form>
        </Card>
    </div>
</template>
<script>
import { Card } from 'element-ui';
import AiEditor from '@/components/AiEditor'
export default {
    components: { AiEditor },
    data() {
        return {
            form: {
                planName: "",
                planCode: "",
                stage: "",
                creator: "",
                createTime: "",
                approvalComment: "",
                status: "pending",
                approver: "",
                approveTime: ""
            },
            fileList: [], // 附件列表
            showChoose: false,
            radio1: 1,
            rules: {},
            status: "1",
            remark: "",
            queryForm: {
            }
        }
    }
}
</script>
<style lang="less" scoped>
.header-title {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 20px;
    gap: 13px;
    .header-title-left {
        display: flex;
        align-items: center;
        gap: 13px;
        margin-top: 38px;
        img {
            width: 12px;
            height: 19px;
        }
        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;
            }
        }
        span {
            flex-shrink: 0;
            font-weight: bold;
            font-size: 18px;
            color: #222222;
            line-height: 27px;
            font-family: "Source Han Sans CN Bold Bold";
        }
    }
    .header-title-left :first-child {
        margin-top: 0;
    }
}
.header-title:first-child {
    .header-title-left {
        margin-top: 0;
    }
}
.end-btn {
    display: flex;
    align-items: center;
    gap: 10px;
    button {
        width: 180px;
        height: 36px;
        // background: #409EFF;
    }
}
</style>
laboratory/src/views/chemistQa/projectTesting/components/approval/index.vue
New file
@@ -0,0 +1,391 @@
<template>
    <el-dialog :title="dialogTitle" :visible.sync="visible" width="80%" po :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" style="width: 100%;">
                            <div class="header-title-left">
                                <img src="@/assets/public/headercard.png" />
                                <div>所属项目组</div>
                            </div>
                        </div>
                        <Table :height="null" :queryForm="queryForm" :total="0" @currentChange="handleCurrentChange"
                            @sizeChange="handleSizeChange">
                            <template>
                                <el-table-column prop="name" label="项目组名称" />
                                <el-table-column prop="age" label="项目负责人" />
                                <el-table-column prop="age" label="项目组成员" />
                                <el-table-column prop="age" label="创建时间" />
                            </template>
                        </Table>
                        <el-form ref="form" :model="form" :rules="rules" inline label-position="top"
                            style="margin-top: 38px">
                            <el-form-item prop="name" label="报告内容">
                                <el-select v-model="form.name" placeholder="请选择报告内容">
                                    <el-option label="报告内容1" value="1"></el-option>
                                    <el-option label="报告内容2" value="2"></el-option>
                                </el-select>
                            </el-form-item>
                            <div class="header-title" style="width: 100%;">
                                <div class="header-title-left">
                                    <img src="@/assets/public/headercard.png" />
                                    <div>报告正文</div>
                                </div>
                            </div>
                            <el-form-item prop="name" style="margin-top: 38px">
                                <ai-editor v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </el-form-item>
                            <div class="header-title" style="width: 100%;">
                                <div class="header-title-left">
                                    <img src="@/assets/public/headercard.png" />
                                    <div>附件</div>
                                </div>
                            </div>
                            <el-form-item prop="name" style="margin-top: 38px">
                                <el-upload action="https://jsonplaceholder.typicode.com/posts/" :file-list="fileList">
                                    <el-button size="small" type="primary">点击上传</el-button>
                                </el-upload>
                            </el-form-item>
                        </el-form>
                    </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>
            </div>
        </div>
        <div class="approval-dialog-approve">
            <el-row :span="24">
                <el-col :span="12">
                    <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>
                </el-col>
                <el-col :span="12">
                    <div class="remark">
                        <div class="remark-title">审批意见</div>
                        <el-input type="textarea" v-model="remark" placeholder="请输入审批意见" />
                    </div>
                </el-col>
            </el-row>
        </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>
</template>
<script>
import ApprovalProcess from '@/components/approvalProcess'
import AiEditor from '@/components/AiEditor'
export default {
    name: "ApprovalDialog",
    components: {
        ApprovalProcess,
        AiEditor
    },
    props: {
        visible: {
            type: Boolean,
            default: false,
        },
        type: {
            type: String,
            default: "approve", // approve-审批,view-查看
        },
        data: {
            type: Object,
            default: () => ({}),
        },
    },
    data() {
        return {
            form: {
                planName: "",
                planCode: "",
                stage: "",
                creator: "",
                createTime: "",
                approvalComment: "",
                status: "pending",
                approver: "",
                approveTime: ""
            },
            radio1: 1,
            rules: {},
            status: "1",
            remark: "",
        };
    },
    computed: {
        dialogTitle() {
            return this.type === "approve" ? "审批" : "审批详情";
        },
    },
    watch: {
        data: {
            handler(val) {
                if (val) {
                    this.form = { ...val };
                }
            },
            immediate: true,
        },
    },
    methods: {
        handleClose() {
            this.$emit("close");
            this.form.approvalComment = "";
        },
        handleApprove() {
            if (!this.form.approvalComment) {
                this.$message.warning("请输入审批意见");
                return;
            }
            this.$emit("approve", {
                ...this.form,
                status: "approved",
            });
        },
        handleReject() {
            if (!this.form.approvalComment) {
                this.$message.warning("请输入审批意见");
                return;
            }
            this.$emit("reject", {
                ...this.form,
                status: "rejected",
            });
        },
    },
};
</script>
<style scoped lang="less">
::v-deep .el-dialog__header {
    border-bottom: 1px solid #e4e7ed;
}
.approval-dialog {
    display: flex;
    height: 40vh;
    .approval-content {
        flex: 3;
        margin-right: 20px;
        background: #ffffff;
        box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08);
        border-radius: 10px;
    }
    .approval-flow {
        padding: 40px 20px;
        // width: 405px;
        flex: 2;
        background: #ffffff;
        box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08);
        border-radius: 10px;
        .flow-title {
            font-size: 16px;
            font-weight: bold;
            margin-bottom: 20px;
            color: #303133;
        }
        .flow-content {
            height: calc(100% - 40px);
            overflow-y: auto;
            .el-form--inline .el-form-item {
                margin-right: 83px;
            }
        }
    }
}
.approval-content-card {
    height: calc(100% - 100px) !important;
    box-shadow: none !important;
}
.header-title {
    // display: flex;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 20px;
    gap: 13px;
    .header-title-left {
        display: flex;
        align-items: center;
        gap: 13px;
        margin-top: 38px;
        img {
            width: 12px;
            height: 19px;
        }
        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;
            }
        }
        span {
            flex-shrink: 0;
            font-weight: bold;
            font-size: 18px;
            color: #222222;
            line-height: 27px;
            font-family: "Source Han Sans CN Bold Bold";
        }
    }
    .header-title-left :first-child {
        margin-top: 0;
    }
}
.header-title:first-child {
    .header-title-left {
        margin-top: 0;
    }
}
.item-title {
    padding-left: 25px;
    span {
        flex-shrink: 0;
        font-weight: bold;
        font-size: 14px;
        color: #222222;
        line-height: 27px;
        font-family: "Source Han Sans CN Bold Bold";
        margin: 18px 0;
        &:before {
            content: "*";
            color: #f56c6c;
            margin-right: 4px;
        }
    }
}
.approval-dialog-approve {
    padding: 38px 20px;
    // display: flex;
    align-content: center;
    .status {
        margin-right: 40px;
        max-width: 60%;
    }
    //   align-items: center;
    .status-title {
        color: #222222;
        font-family: "SourceHanSansCN-Medium";
        line-height: 14px;
        margin-bottom: 16px;
    }
    .status-content {
        display: flex;
        align-items: center;
        width: 100%;
        gap: 16px;
        background: #ffffff;
        border-radius: 10px;
        border: 1px solid rgba(4, 156, 154, 0.5);
        .resolve {
            border-radius: 10px;
            flex: 1;
            font-size: 16px;
            // padding: 5px 55px;
            font-weight: 400;
            color: #333333;
            cursor: pointer;
            line-height: 32px;
            display: flex;
            align-items: center;
            justify-content: center
        }
        .reject {
            flex: 1;
            border-radius: 10px;
            font-size: 16px;
            line-height: 32px;
            // padding: 5px 55px;
            font-weight: 400;
            color: #333333;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center
        }
        .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;
    }
}
.dialog-footer {
    align-items: center;
    display: flex;
    justify-content: center;
    gap: 20px;
    button {
        width: 150px;
    }
}
</style>
laboratory/src/views/chemistQa/projectTesting/index.vue
New file
@@ -0,0 +1,308 @@
<template>
    <div class="list">
        <TableCustom :queryForm="queryForm" :tableData="dataList" :total="total"  @currentChange="handleCurrentChange"
            @sizeChange="handleSizeChange">
            <template #search>
                <el-form :model="form" label-width="auto" inline>
                    <el-form-item label="所属项目组:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="检测项名称:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="检测项编号:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="报告内容:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="状态:">
                        <el-select v-model="form.status" placeholder="请选择">
                            <el-option label="待审核" value="1"></el-option>
                            <el-option label="已通过" value="0"></el-option>
                            <el-option label="已驳回" value="2"></el-option>
                        </el-select>
                    </el-form-item>
                    <el-form-item label="" style="margin-left: 63px;">
                        <el-button type="default" style="margin-right: 10px;">重置</el-button>
                        <el-button type="primary">查询</el-button>
                    </el-form-item>
                </el-form>
            </template>
            <template #setting>
                <el-button @click="handleAddProject" class="el-icon-plus" type="primary">
                    新增检测项</el-button>
                <div class="table-setting">
                    <div class="table-title">
                        项目检测项、检验包列表
                    </div>
                    <div class="table-tit">
                        草稿箱
                    </div>
                </div>
            </template>
            <template #tableCustom>
                <Table :data="tableData" :total="0" @row-click="handleRowClick" row-key="id"
                    :expand-row-keys="expandRowKeys">
                    <el-table-column type="expand" width="1">
                        <template #default="{ row }">
                            <div class="expand-box">
                                <div style="display: flex;align-items: center;"><div class="expand-box-title">报告列表 </div>  <el-button style="margin-left: 10px;" @click="handleAdd" class="el-icon-plus" type="primary">
                                    新增报告 </el-button> </div>
                                <Table :total="0" :height="null">
                                    <el-table-column prop="name" label="报告内容" />
                                    <el-table-column prop="name" label="制订人" />
                                    <el-table-column prop="name" label="制订日期" />
                                    <el-table-column prop="name" label="审批人" />
                                    <el-table-column prop="name" label="审批时间" />
                                    <el-table-column prop="age" label="状态">
                                        <template #default="{ row }">
                                            <el-tag v-if="row.status == 0" type="info" color="#fff">待审核</el-tag>
                                            <el-tag v-if="row.status == 1" type="success" color="#fff">已通过</el-tag>
                                            <el-tag v-else type="danger">未通过</el-tag>
                                        </template>
                                    </el-table-column>
                                    <el-table-column prop="age" label="操作">
                                        <template #default="{ row }">
                                            <el-button type="text">详情</el-button>
                                        </template>
                                    </el-table-column>
                                </Table>
                            </div>
                        </template>
                    </el-table-column>
                    <el-table-column prop="name" label="所属项目组" />
                    <el-table-column prop="age" label="检测项名称" />
                    <el-table-column prop="age" label="检测项编号" />
                    <el-table-column prop="age" label="报告内容" />
                    <el-table-column prop="age" label="创建人" />
                    <el-table-column prop="age" label="创建时间" />
                    <el-table-column prop="age" label="审批时间" />
                    <el-table-column prop="age" label="状态">
                        <template #default="{ row }">
                            <el-tag v-if="row.status == 1" type="info" color="#fff">已评定</el-tag>
                            <el-tag v-else type="success">待评定</el-tag>
                        </template>
                    </el-table-column>
                    <el-table-column prop="age" label="操作">
                        <template #default="{ row }">
                            <el-button type="text" @click="showApproval = true">详情</el-button>
                        </template>
                    </el-table-column>
                </Table>
            </template>
        </TableCustom>
        <Approval :visible="showApproval" @close="showApproval = false" />
        <ShowDelConfirm :show="showDelConfirm" @close="showDelConfirm = false" @confirm="handleDelConfirm" />
        <ShowDelConfirm :title="changeStatusTitle" :tip="changeStatusTip" :show="changeStatus"
            @close="changeStatus = false" @confirm="handleChangeStatusConfirm" />
    </div>
</template>
<script>
import Approval from './components/approval'
export default {
    name: 'ProjectList',
    components: {
        Approval
    },
    data() {
        return {
            form: {
                name: ''
            },
            showDelConfirm: false,
            rowId: '',
            changeStatus: false,
            showApproval: false,
            changeStatusTitle: '',
            changeStatusTip: '',
            queryForm: {
                pageSize: 10,
                pageNum: 1
            },
            tableData: [
                {
                    name: '项目组1',
                    age1: '检测项名称1',
                    age2: '检测项编号1',
                    age3: '报告内容1',
                    age4: '创建人1',
                    age5: '创建时间1',
                    age6: '审批时间1',
                    age7: '状态1'
                }
            ],
            expandRowKeys: [],
            total: 0
        }
    },
    methods: {
        handleAddProject() {
            this.$router.push('/chemistQa/add')
        },
        handleAdd() {
            this.$router.push('/chemistQa/addDetectionReport')
        },
        handleRowClick(row, column, event) {
            if (column.label === '操作') return
            if (this.expandRowKeys.includes(row.id)) {
                this.expandRowKeys = this.expandRowKeys.filter(key => key !== row.id);
            } else {
                this.expandRowKeys.push(row.id);
            }
        },
        handleDel(row) {
            this.rowId = row.id
            this.showDelConfirm = true
        },
        handleDelConfirm() {
            this.showDelConfirm = false
            this.msgsuccess('删除成功')
            this.rowId = ''
            this.getList()
        },
        handleChangeStatus(row, status) {
            this.rowId = row.id
            this.changeStatusTitle = status == 1 ? '确认要封存这个项目组吗?' : '确认要解封该项目组吗?'
            this.changeStatusTip = status == 1 ? '封存后项目组内人员看不到数据,审批人仍然可见数据。' : '解封后项目组内人员数据恢复。'
            this.changeStatus = true
        },
        handleChangeStatusConfirm() {
            this.changeStatus = false
            this.msgsuccess('操作成功')
            this.rowId = ''
            this.changeStatusTitle = ''
            this.changeStatusTip = ''
            this.getList()
        },
        handleCurrentChange(page) {
            this.queryForm.pageNum = page
            this.getList()
        },
        handleSizeChange(size) {
            this.queryForm.pageSize = size
            this.getList()
        },
        getList() {
        }
    }
}
</script>
<style scoped lang="less">
.el-icon-plus{
    margin-bottom: 20px;
}
.header-content {
    font-family: PingFangSC, PingFang SC;
    font-weight: 400;
    font-size: 14px;
    color: rgba(0, 0, 0, 0.88);
    margin-left: 30px;
}
.box-title {
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #222222;
    line-height: 27px;
    display: flex;
    align-items: center;
}
.header-icon {
    width: 20px;
    height: 20px;
    margin-right: 10px;
}
.header-box {
    border-radius: 16px;
    margin-bottom: 30px;
}
.table-setting {
    display: flex;
    gap: 14px;
}
.table-tit {
    background: #FAFAFC;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #DCDFE6;
    width: 166px;
    height: 50px;
    background: #FFFFFF;
    border-radius: 8px 8px 0px 0px;
    display: flex;
    align-items: center;
    justify-content: center;
    // margin-bottom: 21px;
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #049C9A;
    line-height: 27px;
}
.list {
    height: 100%;
}
.table-title {
    // width: 166px;1
    height: 50px;
    padding: 0 29px;
    background: #FFFFFF;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #049C9A;
    display: flex;
    align-items: center;
    justify-content: center;
    // margin-bottom: 21px;
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #049C9A;
    line-height: 27px;
}
.table-title {
    width: 220px;
    height: 50px;
    background: #FFFFFF;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #049C9A;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #049C9A;
    line-height: 27px;
}
.expand-box {
    padding: 20px;
    background: linear-gradient(180deg, #049C9A 0%, #0ACBCA 100%);
    border-radius: 20px;
    &-title {
        font-weight: 500;
        font-size: 16px;
        color: #FFFFFF;
        line-height: 24px;
        margin-bottom: 20px;
    }
}
</style>
laboratory/src/views/chemistQa/rawMaterials/add.vue
New file
@@ -0,0 +1,172 @@
<template>
    <div>
        <Card>
            <div class="header-title" style="width: 100%;">
                <div class="header-title-left">
                    <img src="@/assets/public/headercard.png" />
                    <div>所属项目组</div>
                </div>
                <div class="header-title-right">
                    <el-button @click="showChoose = true" class="el-icon-circle-plus-outline" type="primary">
                        选择项目组</el-button>
                </div>
            </div>
            <Table :height="null" :queryForm="queryForm" :total="0">
                <template>
                    <el-table-column prop="name" label="项目组名称" />
                    <el-table-column prop="age" label="项目负责人" />
                    <el-table-column prop="age" label="项目组成员" />
                    <el-table-column prop="age" label="创建时间" />
                </template>
            </Table>
            <el-form ref="form" :model="form" :rules="rules" inline label-position="top" style="margin-top: 38px">
                <el-row :gutter="20">
                    <el-col :span="24">
                        <el-form-item prop="name" label="报告内容">
                            <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告内容" />
                        </el-form-item>
                        <el-form-item prop="name" label="报告编号" style="margin-left: 100px;">
                            <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                        </el-form-item>
                    </el-col>
                    <el-col :span="24">
                        <el-form-item prop="name" label="制定人">
                            <el-input v-model="form.name" style="width: 100%;" placeholder="请输入制定人" />
                        </el-form-item>
                        <el-form-item prop="name" label="制定日期" style="margin-left: 100px;">
                            <el-date-picker :prefix-icon="null" v-model="form.createTime"  type="date" placeholder="请选择日期" />
                        </el-form-item>
                    </el-col>
                </el-row>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>报告正文</div>
                    </div>
                </div>
                <form-item prop="name" style="margin-top: 38px">
                    <ai-editor v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                </form-item>
                <div class="header-title" style="width: 100%;">
                    <div class="header-title-left">
                        <img src="@/assets/public/headercard.png" />
                        <div>附件</div>
                    </div>
                </div>
                <el-form-item prop="name" style="margin-top: 38px">
                    <el-upload action="https://jsonplaceholder.typicode.com/posts/" :file-list="fileList">
                        <el-button size="small" type="primary">点击上传</el-button>
                    </el-upload>
                </el-form-item>
                <div class="end-btn" style="margin-top: 38px">
                    <el-button type="primary">发送</el-button>
                    <el-button type="default">存草稿</el-button>
                </div>
            </el-form>
        </Card>
    </div>
</template>
<script>
import { Card } from 'element-ui';
import AiEditor from '@/components/AiEditor'
export default {
    components: { AiEditor },
    data() {
        return {
            form: {
                planName: "",
                planCode: "",
                stage: "",
                creator: "",
                createTime: "",
                approvalComment: "",
                status: "pending",
                approver: "",
                approveTime: ""
            },
            fileList: [], // 附件列表
            showChoose: false,
            radio1: 1,
            rules: {},
            status: "1",
            remark: "",
            queryForm: {
            }
        }
    }
}
</script>
<style lang="less" scoped>
.header-title {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 20px;
    gap: 13px;
    .header-title-left {
        display: flex;
        align-items: center;
        gap: 13px;
        margin-top: 38px;
        img {
            width: 12px;
            height: 19px;
        }
        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;
            }
        }
        span {
            flex-shrink: 0;
            font-weight: bold;
            font-size: 18px;
            color: #222222;
            line-height: 27px;
            font-family: "Source Han Sans CN Bold Bold";
        }
    }
    .header-title-left :first-child {
        margin-top: 0;
    }
}
.header-title:first-child {
    .header-title-left {
        margin-top: 0;
    }
}
.end-btn {
    display: flex;
    align-items: center;
    gap: 10px;
    button {
        width: 180px;
        height: 36px;
        // background: #409EFF;
    }
}
</style>
laboratory/src/views/chemistQa/rawMaterials/components/approval/index.vue
New file
@@ -0,0 +1,397 @@
<template>
    <el-dialog :title="dialogTitle" :visible.sync="visible" width="80%" po :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" style="width: 100%;">
                            <div class="header-title-left">
                                <img src="@/assets/public/headercard.png" />
                                <div>所属项目组</div>
                            </div>
                        </div>
                        <Table :height="null" :queryForm="queryForm" :total="0" @currentChange="handleCurrentChange"
                            @sizeChange="handleSizeChange">
                            <template>
                                <el-table-column prop="name" label="项目组名称" />
                                <el-table-column prop="age" label="项目负责人" />
                                <el-table-column prop="age" label="项目组成员" />
                                <el-table-column prop="age" label="创建时间" />
                            </template>
                        </Table>
                        <el-form ref="form" :model="form" :rules="rules" inline label-position="top"
                            style="margin-top: 38px">
                            <el-row :gutter="20">
                                <el-col :span="24">
                                    <el-form-item prop="name" label="报告内容">
                                        <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告内容" />
                                    </el-form-item>
                                    <el-form-item prop="name" label="报告编号" style="margin-left: 100px;">
                                        <el-input v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                                    </el-form-item>
                                </el-col>
                                <el-col :span="24">
                                    <el-form-item prop="name" label="制定人">
                                        <el-input v-model="form.name" style="width: 100%;" placeholder="请输入制定人" />
                                    </el-form-item>
                                    <el-form-item prop="name" label="制定日期" style="margin-left: 100px;">
                                        <el-date-picker :prefix-icon="null" v-model="form.createTime" type="date"
                                            placeholder="请选择日期" />
                                    </el-form-item>
                                </el-col>
                            </el-row>
                            <div class="header-title" style="width: 100%;">
                                <div class="header-title-left">
                                    <img src="@/assets/public/headercard.png" />
                                    <div>报告正文</div>
                                </div>
                            </div>
                            <el-form-item prop="name" style="margin-top: 38px">
                                <ai-editor v-model="form.name" style="width: 100%;" placeholder="请输入报告编号" />
                            </el-form-item>
                            <el-form-item prop="name" style="margin-top: 38px">
                                <el-upload action="https://jsonplaceholder.typicode.com/posts/" :file-list="fileList">
                                    <el-button size="small" type="primary">点击上传</el-button>
                                </el-upload>
                            </el-form-item>
                        </el-form>
                    </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>
            </div>
        </div>
        <div class="approval-dialog-approve">
            <el-row :span="24">
                <el-col :span="12">
                    <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>
                </el-col>
                <el-col :span="12">
                    <div class="remark">
                        <div class="remark-title">审批意见</div>
                        <el-input type="textarea" v-model="remark" placeholder="请输入审批意见" />
                    </div>
                </el-col>
            </el-row>
        </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>
</template>
<script>
import ApprovalProcess from '@/components/approvalProcess'
import AiEditor from '@/components/AiEditor'
export default {
    name: "ApprovalDialog",
    components: {
        ApprovalProcess,
        AiEditor
    },
    props: {
        visible: {
            type: Boolean,
            default: false,
        },
        type: {
            type: String,
            default: "approve", // approve-审批,view-查看
        },
        data: {
            type: Object,
            default: () => ({}),
        },
    },
    data() {
        return {
            form: {
                planName: "",
                planCode: "",
                stage: "",
                creator: "",
                createTime: "",
                approvalComment: "",
                status: "pending",
                approver: "",
                approveTime: ""
            },
            radio1: 1,
            rules: {},
            status: "1",
            remark: "",
        };
    },
    computed: {
        dialogTitle() {
            return this.type === "approve" ? "审批" : "审批详情";
        },
    },
    watch: {
        data: {
            handler(val) {
                if (val) {
                    this.form = { ...val };
                }
            },
            immediate: true,
        },
    },
    methods: {
        handleClose() {
            this.$emit("close");
            this.form.approvalComment = "";
        },
        handleApprove() {
            if (!this.form.approvalComment) {
                this.$message.warning("请输入审批意见");
                return;
            }
            this.$emit("approve", {
                ...this.form,
                status: "approved",
            });
        },
        handleReject() {
            if (!this.form.approvalComment) {
                this.$message.warning("请输入审批意见");
                return;
            }
            this.$emit("reject", {
                ...this.form,
                status: "rejected",
            });
        },
    },
};
</script>
<style scoped lang="less">
::v-deep .el-dialog__header {
    border-bottom: 1px solid #e4e7ed;
}
.approval-dialog {
    display: flex;
    height: 40vh;
    .approval-content {
        flex: 3;
        margin-right: 20px;
        background: #ffffff;
        box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08);
        border-radius: 10px;
    }
    .approval-flow {
        padding: 40px 20px;
        // width: 405px;
        flex: 2;
        background: #ffffff;
        box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08);
        border-radius: 10px;
        .flow-title {
            font-size: 16px;
            font-weight: bold;
            margin-bottom: 20px;
            color: #303133;
        }
        .flow-content {
            height: calc(100% - 40px);
            overflow-y: auto;
            .el-form--inline .el-form-item {
                margin-right: 83px;
            }
        }
    }
}
.approval-content-card {
    height: calc(100% - 100px) !important;
    box-shadow: none !important;
}
.header-title {
    // display: flex;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 20px;
    gap: 13px;
    .header-title-left {
        display: flex;
        align-items: center;
        gap: 13px;
        margin-top: 38px;
        img {
            width: 12px;
            height: 19px;
        }
        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;
            }
        }
        span {
            flex-shrink: 0;
            font-weight: bold;
            font-size: 18px;
            color: #222222;
            line-height: 27px;
            font-family: "Source Han Sans CN Bold Bold";
        }
    }
    .header-title-left :first-child {
        margin-top: 0;
    }
}
.header-title:first-child {
    .header-title-left {
        margin-top: 0;
    }
}
.item-title {
    padding-left: 25px;
    span {
        flex-shrink: 0;
        font-weight: bold;
        font-size: 14px;
        color: #222222;
        line-height: 27px;
        font-family: "Source Han Sans CN Bold Bold";
        margin: 18px 0;
        &:before {
            content: "*";
            color: #f56c6c;
            margin-right: 4px;
        }
    }
}
.approval-dialog-approve {
    padding: 38px 20px;
    // display: flex;
    align-content: center;
    .status {
        margin-right: 40px;
        max-width: 60%;
    }
    //   align-items: center;
    .status-title {
        color: #222222;
        font-family: "SourceHanSansCN-Medium";
        line-height: 14px;
        margin-bottom: 16px;
    }
    .status-content {
        display: flex;
        align-items: center;
        width: 100%;
        gap: 16px;
        background: #ffffff;
        border-radius: 10px;
        border: 1px solid rgba(4, 156, 154, 0.5);
        .resolve {
            border-radius: 10px;
            flex: 1;
            font-size: 16px;
            // padding: 5px 55px;
            font-weight: 400;
            color: #333333;
            cursor: pointer;
            line-height: 32px;
            display: flex;
            align-items: center;
            justify-content: center
        }
        .reject {
            flex: 1;
            border-radius: 10px;
            font-size: 16px;
            line-height: 32px;
            // padding: 5px 55px;
            font-weight: 400;
            color: #333333;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center
        }
        .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;
    }
}
.dialog-footer {
    align-items: center;
    display: flex;
    justify-content: center;
    gap: 20px;
    button {
        width: 150px;
    }
}
</style>
laboratory/src/views/chemistQa/rawMaterials/index.vue
New file
@@ -0,0 +1,211 @@
<template>
    <div class="list">
        <TableCustom :queryForm="queryForm" :total="total" @currentChange="handleCurrentChange"
            @sizeChange="handleSizeChange">
            <template #search>
                <el-form :model="form" label-width="auto" inline>
                    <el-form-item label="所属项目组:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="报告标题">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="报告编号">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="状态:">
                        <el-select v-model="form.status" placeholder="请选择">
                            <el-option label="待审核" value="1"></el-option>
                            <el-option label="已通过" value="0"></el-option>
                            <el-option label="已驳回" value="2"></el-option>
                        </el-select>
                    </el-form-item>
                    <el-form-item label="" style="margin-left: 63px;">
                        <el-button type="default" style="margin-right: 10px;">重置</el-button>
                        <el-button type="primary">查询</el-button>
                    </el-form-item>
                </el-form>
            </template>
            <template #setting>
                <el-button @click="handleAddProject" class="el-icon-plus" type="primary">
                    新增</el-button>
                <div class="table-setting">
                    <div class="table-title">
                        原辅料、包材、竞品检验分析报告
                    </div>
                    <div class="table-tit">
                        草稿箱
                    </div>
                </div>
            </template>
            <template #table>
                <el-table-column prop="name" label="所属项目组" />
                <el-table-column prop="age" label="报告标题" />
                <el-table-column prop="age" label="报告编号" />
                <el-table-column prop="age" label="制定人" />
                <el-table-column prop="age" label="制定日期" />
                <el-table-column prop="age" label="操作">
                    <template #default="{ row }">
                        <el-button type="text" @click="showApproval = true">详情</el-button>
                    </template>
                </el-table-column>
            </template>
        </TableCustom>
        <Approval :visible="showApproval" @close="showApproval = false" />
        <ShowDelConfirm :show="showDelConfirm" @close="showDelConfirm = false" @confirm="handleDelConfirm" />
        <ShowDelConfirm :title="changeStatusTitle" :tip="changeStatusTip" :show="changeStatus"
            @close="changeStatus = false" @confirm="handleChangeStatusConfirm" />
    </div>
</template>
<script>
import Approval from './components/approval'
export default {
    name: 'ProjectList',
    components: {
        Approval
    },
    data() {
        return {
            form: {
                name: ''
            },
            showDelConfirm: false,
            rowId: '',
            changeStatus: false,
            showApproval: false,
            changeStatusTitle: '',
            changeStatusTip: '',
            queryForm: {
                pageSize: 10,
                pageNum: 1
            },
            total: 0
        }
    },
    methods: {
        handleAddProject() {
            this.$router.push('/chemistQa/addPilot')
        },
        handleDel(row) {
            this.rowId = row.id
            this.showDelConfirm = true
        },
        handleDelConfirm() {
            this.showDelConfirm = false
            this.msgsuccess('删除成功')
            this.rowId = ''
            this.getList()
        },
        handleChangeStatus(row, status) {
            this.rowId = row.id
            this.changeStatusTitle = status == 1 ? '确认要封存这个项目组吗?' : '确认要解封该项目组吗?'
            this.changeStatusTip = status == 1 ? '封存后项目组内人员看不到数据,审批人仍然可见数据。' : '解封后项目组内人员数据恢复。'
            this.changeStatus = true
        },
        handleChangeStatusConfirm() {
            this.changeStatus = false
            this.msgsuccess('操作成功')
            this.rowId = ''
            this.changeStatusTitle = ''
            this.changeStatusTip = ''
            this.getList()
        },
        handleCurrentChange(page) {
            this.queryForm.pageNum = page
            this.getList()
        },
        handleSizeChange(size) {
            this.queryForm.pageSize = size
            this.getList()
        },
        getList() {
        }
    }
}
</script>
<style scoped lang="less">
.el-icon-plus{
    margin-bottom: 20px;
}
.header-content {
    font-family: PingFangSC, PingFang SC;
    font-weight: 400;
    font-size: 14px;
    color: rgba(0, 0, 0, 0.88);
    margin-left: 30px;
}
.box-title {
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #222222;
    line-height: 27px;
    display: flex;
    align-items: center;
}
.header-icon {
    width: 20px;
    height: 20px;
    margin-right: 10px;
}
.header-box {
    border-radius: 16px;
    margin-bottom: 30px;
}
.table-setting {
    display: flex;
    gap: 14px;
}
.table-tit {
    background: #FAFAFC;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #DCDFE6;
    width: 166px;
    height: 50px;
    background: #FFFFFF;
    border-radius: 8px 8px 0px 0px;
    display: flex;
    align-items: center;
    justify-content: center;
    // margin-bottom: 21px;
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #049C9A;
    line-height: 27px;
}
.list {
    height: 100%;
}
.table-title {
    // width: 166px;1
    height: 50px;
    padding: 0 29px;
    background: #FFFFFF;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #049C9A;
    display: flex;
    align-items: center;
    justify-content: center;
    // margin-bottom: 21px;
    font-family: SourceHanSansCN, SourceHanSansCN;
    font-weight: bold;
    font-size: 18px;
    color: #049C9A;
    line-height: 27px;
}
</style>
laboratory/src/views/dataManagement/schemeManagement/addPlan.vue
New file
@@ -0,0 +1,684 @@
<template>
  <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 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>
          <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="groupName" label="组别"></el-table-column>
          <el-table-column prop="remark" label="备注"></el-table-column>
          <el-table-column label="操作" width="200">
            <template slot-scope="scope">
              <el-button type="text" @click="handleEditGroup(scope.row)">编辑</el-button>
              <el-button type="text" @click="handleDeleteGroup(scope.row)">移除</el-button>
            </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>
          </div>
        </div>
        <div class="add-group">
          <span>组别列表</span>
          <!-- <el-button type="primary" class="el-icon-plus" @click="handleAddGroup">添加组别</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="groupName" label="组别"></el-table-column>
          <el-table-column prop="remark" label="备注"></el-table-column>
        </Table>
        <div style="padding-left: 25px">
          <el-form-item prop="name" label="试验日期">
            <el-input v-model="form.name" placeholder="请输入" />
          </el-form-item>
        </div>
        <div class="add-group">
          <div>*</div>
          <span>参加人员</span>
          <el-button type="primary" class="el-icon-plus" @click="addMember">选择参加人员</el-button>
        </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 ? 'member-name-box' : 'flex1'">
                <div :class="item == 1 || item == 2 ? '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">
                <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="content-box">
          <AiEditor ref="purposeEditor" v-model="editorContents.purpose" height="200px" placeholder="请输入实验目的..." />
        </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="content-box">
          <AiEditor ref="processEditor" v-model="editorContents.process" height="200px" placeholder="请输入工艺参数及路线..." />
        </div>
        <div class="header-title" style="margin-bottom: 38px">
          <div class="header-title-left">
            <img src="@/assets/public/headercard.png" />
            <div>三、实验材料及设备</div>
          </div>
        </div>
        <DynamicComponent ref="materialComponent" title="实验材料" @submit="handleMaterialSubmit" />
        <DynamicComponent ref="equipmentComponent" title="实验所用设备" @submit="handleEquipmentSubmit" />
        <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>
        </div>
        <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 class="step-list-item-control">
              <div class="controlBtn edit" @click="handleEditStep(idx)">
                <img src="@/assets/public/edit.png" alt="编辑" class="edit-icon" />
                编辑
              </div>
              <div class="controlBtn delete" @click="handleDeleteStep(idx)">
                <img src="@/assets/public/delete.png" alt="删除" class="delete-icon" />
                删除
              </div>
            </div>
          </div>
          <DynamicComponent :ref="'stepContent' + idx" @submit="(content) => handleStepContentSubmit(idx, content)" />
        </div>
        <div class="add-project-footer">
          <el-button type="primary" class="save-btn" @click="handleSave">发送</el-button>
          <el-button @click="handleSaveDraft">存草稿</el-button>
        </div>
      </el-form>
    </template>
    <SelectMember ref="selectMember" />
    <experimentalScheduling :show="showScheduling" />
    <AddStep ref="addStepDialog" @submit="handleStepSubmit" />
  </Card>
</template>
<script>
import SelectMember from "@/components/SelectMember";
import experimentalScheduling from "../confirmation-sheet/components/experimental-scheduling.vue";
import DynamicComponent from "@/components/DynamicComponent";
import AddStep from "./components/add-step.vue";
import AiEditor from "@/components/AiEditor";
export default {
  name: "AddProject",
  components: {
    SelectMember,
    experimentalScheduling,
    DynamicComponent,
    AddStep,
    AiEditor,
  },
  data() {
    return {
      showScheduling: false,
      form: {
        material: null,
        equipment: null,
      },
      editorContents: {
        purpose: "",
        process: "",
      },
      stepList: [],
      editingStepIndex: -1,
      rules: {
        name: [
          { required: true, message: "请输入项目组名称", trigger: "blur" },
        ],
        description: [
          { required: true, message: "请输入项目组描述", trigger: "blur" },
        ],
        material: [
          { required: true, message: "请添加实验材料", trigger: "change" },
        ],
        equipment: [
          { required: true, message: "请添加实验设备", trigger: "change" },
        ],
      },
      groupTableData: [],
      taskTableData: [],
    };
  },
  methods: {
    submitForm() {
      this.$refs.form.validate((valid) => {
        if (valid) {
          console.log("submit!");
        }
      });
    },
    addMember() {
      this.$refs.selectMember.open();
    },
    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;
      }
    },
    handleAddGroup() {
      this.$refs.addGroupDialog.open();
    },
    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.material = data;
    },
    handleEquipmentSubmit(data) {
      this.form.equipment = data;
    },
    handleSave() {
      this.$refs.form.validate((valid) => {
        if (valid && this.validateContent()) {
          this.$refs.materialComponent.submit();
          this.$refs.equipmentComponent.submit();
          const formData = {
            ...this.form,
            ...this.getAllEditorContent(),
            steps: this.stepList,
          };
          console.log("提交的数据:", formData);
          this.$message.success("保存成功");
        }
      });
    },
    handleSaveDraft() {
      this.$refs.materialComponent.submit();
      this.$refs.equipmentComponent.submit();
      const formData = {
        ...this.form,
        ...this.getAllEditorContent(),
        steps: this.stepList,
        status: "draft",
      };
      console.log("草稿数据:", formData);
      this.$message.success("草稿保存成功");
    },
    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,
        });
      }
    },
    handleDeleteStep(index) {
      this.$confirm("确认删除该步骤吗?删除后步骤内容将无法恢复", "警告", {
        confirmButtonText: "确定删除",
        cancelButtonText: "取消",
        type: "warning",
        confirmButtonClass: "confirm-danger-btn",
      })
        .then(() => {
          this.stepList.splice(index, 1);
          this.$message.success("删除成功");
        })
        .catch(() => { });
    },
    handleEditStep(index) {
      this.editingStepIndex = index;
      this.$refs.addStepDialog.open(true);
      this.$refs.addStepDialog.setStepName(this.stepList[index].stepName);
    },
    handleStepContentSubmit(index, content) {
      this.stepList[index].content = content;
    },
    getAllEditorContent() {
      return {
        purpose: this.$refs.purposeEditor.getContent(),
        process: this.$refs.processEditor.getContent(),
      };
    },
    validateContent() {
      const contents = this.getAllEditorContent();
      if (!contents.purpose) {
        this.$message.error("请填写实验目的");
        return false;
      }
      if (!contents.process) {
        this.$message.error("请填写工艺参数及路线");
        return false;
      }
      return true;
    },
    handleStopExperiment() {
      this.$router.push("/dataManagement/scheme-management/stop-experiment");
    },
  },
};
</script>
<style scoped lang="less">
.el-form--inline .el-form-item {
  margin-right: 83px;
}
.header-title {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 13px;
  margin-top: 38px;
  .header-title-left {
    display: flex;
    align-items: center;
    gap: 13px;
    img {
      width: 12px;
      height: 19px;
    }
    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;
      }
    }
    span {
      flex-shrink: 0;
      font-weight: bold;
      font-size: 18px;
      color: #222222;
      line-height: 27px;
      font-family: "Source Han Sans CN Bold Bold";
    }
  }
  .header-title-left :first-child {
    margin-top: 0;
  }
}
.item-title {
  padding-left: 25px;
  span {
    flex-shrink: 0;
    font-weight: bold;
    font-size: 14px;
    color: #222222;
    line-height: 27px;
    font-family: "Source Han Sans CN Bold Bold";
    margin: 18px 0;
    &:before {
      content: "*";
      color: #f56c6c;
      margin-right: 4px;
    }
  }
}
.header-title:first-child {
  margin-top: 0px;
  .header-title-left {
    margin-top: 0;
  }
}
.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: 340px;
    height: 400px;
    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;
        }
      }
    }
  }
}
.add-project-footer {
  margin-top: 43px;
  button {
    width: 220px;
  }
  .save-btn {
    margin-right: 20px;
  }
}
.choose-material {
  background: #eff8fa;
  padding: 20px;
  margin-top: 37px;
}
.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;
    }
    .step-list-item-control {
      display: flex;
      align-items: center;
      .controlBtn {
        height: 24px;
        background: #ffffff;
        border-radius: 4px;
        padding: 0px 8px;
        display: flex;
        align-items: center;
      }
      .edit {
        border: 1px solid #44be09;
        font-family: PingFangSC, PingFang SC;
        font-weight: 400;
        font-size: 14px;
        color: #52c41a;
        line-height: 24px;
        margin-right: 50px;
      }
      .delete {
        border: 1px solid #ff4d4f;
        font-family: PingFangSC, PingFang SC;
        font-weight: 400;
        font-size: 14px;
        color: #ff4d4f;
        line-height: 24px;
      }
      .edit-icon {
        width: 14px;
        height: 14px;
        margin-right: 8px;
      }
      .delete-icon {
        width: 13px;
        height: 13px;
        margin-right: 9px;
      }
    }
  }
}
.content-box {
  padding: 0 25px;
  margin-bottom: 30px;
  width: 65%;
}
</style>
laboratory/src/views/dataManagement/schemeManagement/components/add-step.vue
New file
@@ -0,0 +1,79 @@
<template>
  <el-dialog
    :title="dialogTitle"
    :visible.sync="dialogVisible"
    width="500px"
    :before-close="handleClose"
  >
    <el-form
      ref="form"
      :model="form"
      :rules="rules"
      label-width="100px"
      label-position="right"
    >
      <el-form-item label="步骤名称" prop="stepName">
        <el-input v-model="form.stepName" placeholder="请输入步骤名称"></el-input>
      </el-form-item>
    </el-form>
    <span slot="footer" class="dialog-footer">
      <el-button @click="handleClose">取 消</el-button>
      <el-button type="primary" @click="handleSubmit" style="margin-left: 10px;">确 定</el-button>
    </span>
  </el-dialog>
</template>
<script>
export default {
  name: 'AddStep',
  data() {
    return {
      dialogVisible: false,
      isEdit: false,
      form: {
        stepName: ''
      },
      rules: {
        stepName: [
          { required: true, message: '请输入步骤名称', trigger: 'blur' },
          { min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
        ]
      }
    }
  },
  computed: {
    dialogTitle() {
      return this.isEdit ? '编辑步骤' : '新增步骤'
    }
  },
  methods: {
    open(isEdit = false) {
      this.isEdit = isEdit
      this.dialogVisible = true
    },
    setStepName(name) {
      this.form.stepName = name
    },
    handleClose() {
      this.isEdit = false
      this.$refs.form.resetFields()
      this.dialogVisible = false
    },
    handleSubmit() {
      this.$refs.form.validate(valid => {
        if (valid) {
          this.$emit('submit', {...this.form})
          this.handleClose()
        }
      })
    }
  }
}
</script>
<style lang="less" scoped>
.el-input-number {
  width: 120px;
}
</style>
laboratory/src/views/dataManagement/schemeManagement/components/approvalDialog.vue
New file
@@ -0,0 +1,919 @@
<template>
  <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" 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 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>
    </el-dialog>
    <SignatureCanvas
      :visible="signatureDialogVisible"
      @confirm="handleSignatureConfirm"
    />
  </div>
</template>
<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";
export default {
  name: "ApprovalDialog",
  components: {
    ApprovalProcess,
    SignatureCanvas,
    ViewDynamicComponent,
    AiEditor,
  },
  props: {
    visible: {
      type: Boolean,
      default: false,
    },
    type: {
      type: String,
      default: "approve", // approve-审批,view-查看
    },
    data: {
      type: Object,
      default: () => ({}),
    },
  },
  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",
                },
              ],
            },
          },
        ],
      },
      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" ? "确认实验调度" : "实验调度详情";
    },
  },
  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);
        }
      },
      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 = "";
    },
    handleApprove() {
      if (!this.form.approvalComment) {
        this.$message.warning("请输入审批意见");
        return;
      }
      this.$emit("approve", {
        ...this.form,
        status: "approved",
      });
    },
    handleReject() {
      if (!this.form.approvalComment) {
        this.$message.warning("请输入审批意见");
        return;
      }
      this.$emit("reject", {
        ...this.form,
        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>
<style scoped lang="less">
::v-deep .el-dialog__header {
  border-bottom: 1px solid #e4e7ed;
}
.approval-dialog {
  display: flex;
  height: 60vh;
  .approval-content {
    flex: 1;
    margin-right: 20px;
    background: #ffffff;
    box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08);
    border-radius: 10px;
  }
  .approval-flow {
    padding: 40px 20px;
    width: 405px;
    background: #ffffff;
    box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08);
    border-radius: 10px;
    .flow-title {
      font-size: 16px;
      font-weight: bold;
      margin-bottom: 20px;
      color: #303133;
    }
    .flow-content {
      height: calc(100% - 40px);
      overflow-y: auto;
      .el-form--inline .el-form-item {
        margin-right: 83px;
      }
    }
  }
}
.approval-dialog-approve {
  margin-top: 26px;
}
.approval-content-card {
  height: calc(100% - 100px) !important;
  box-shadow: none !important;
}
.header-title {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 13px;
  margin-top: 38px;
  .header-title-left {
    display: flex;
    align-items: center;
    gap: 13px;
    img {
      width: 12px;
      height: 19px;
    }
    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;
      }
    }
    span {
      flex-shrink: 0;
      font-weight: bold;
      font-size: 18px;
      color: #222222;
      line-height: 27px;
      font-family: "Source Han Sans CN Bold Bold";
    }
  }
  .header-title-left :first-child {
    margin-top: 0;
  }
}
.header-title:first-child {
  margin-top: 0 !important;
  .header-title-left {
    margin-top: 0;
  }
}
.item-title {
  padding-left: 25px;
  span {
    flex-shrink: 0;
    font-weight: bold;
    font-size: 14px;
    color: #222222;
    line-height: 27px;
    font-family: "Source Han Sans CN Bold Bold";
    margin: 18px 0;
    &:before {
      content: "*";
      color: #f56c6c;
      margin-right: 4px;
    }
  }
}
.approval-dialog-approve {
  img {
    border: 2px dashed #049c9a;
  }
}
.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>
laboratory/src/views/dataManagement/schemeManagement/list.vue
New file
@@ -0,0 +1,480 @@
<template>
  <div class="list">
    <TableCustom :queryForm="form" :tableData="tableData" :total="total">
      <template #search>
        <el-form :model="form" labelWidth="auto" inline>
          <el-form-item label="项目项目课题方案名称:">
            <el-input v-model="form.planName" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="实验编号:">
            <el-input v-model="form.planCode" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="创建时间:">
            <el-date-picker
              v-model="form.createTime"
              type="daterange"
              range-separator="至"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              value-format="yyyy-MM-dd"
            ></el-date-picker>
          </el-form-item>
          <el-form-item label="状态:">
            <el-input v-model="form.approver" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="">
            <el-button type="default" @click="resetForm">重置</el-button>
            <el-button type="primary" @click="handleSearch" style="margin-left: 10px;">查询</el-button>
          </el-form-item>
        </el-form>
      </template>
      <template #setting>
        <div class="tableTitle">
          <div class="flex a-center flex-wrap">
            <div
            class="title"
              :class="{active:currentType === 'list'}"
              @click="handleTypeChange('list')"
            >项目课题方案列表</div>
            <div
            class="drafts"
              :class="{active:currentType === 'draft'}"
              @click="handleTypeChange('draft')"
            >草稿箱</div>
          </div>
          <el-button @click="handleAddPlan" class="el-icon-plus" type="primary">
            新增实验方案</el-button
          >
        </div>
      </template>
      <template #table>
        <el-table-column
          prop="planName"
          label="项目课题方案名称"
        ></el-table-column>
        <el-table-column
          prop="planCode"
          label="实验编号"
        ></el-table-column>
        <el-table-column prop="stage" label="实验名称"></el-table-column>
        <el-table-column prop="testDate" label="试验日期"></el-table-column>
        <el-table-column prop="tester" label="试验员"></el-table-column>
        <el-table-column prop="createTime" label="创建日期"></el-table-column>
        <el-table-column prop="creator" 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-column label="操作" width="250">
          <template slot-scope="scope">
            <el-button
              v-if="scope.row.status === 'rejected'"
              type="text"
              @click="handleEdit(scope.row)"
              >编辑</el-button
            >
            <el-button type="text" @click="handleDetail(scope.row)"
              >详情</el-button
            >
          </template>
        </el-table-column>
      </template>
    </TableCustom>
    <!-- 审批弹窗 -->
    <approval-dialog
      :visible.sync="approvalDialogVisible"
      :type="approvalDialogType"
      :data="currentApprovalData"
      @approve="handleApproveSubmit"
      @reject="handleRejectSubmit"
    />
  </div>
</template>
<script>
import ApprovalDialog from './components/approvalDialog.vue'
export default {
  name: "ProjectList",
  components: {
    ApprovalDialog
  },
  data() {
    return {
      currentType: 'list', // 当前显示类型:list-列表,draft-草稿箱
      form: {
        planName: "",
        planCode: "",
        creator: "",
        createTime: [],
        approver: "",
        status: "",
      },
      tableData: [],
      total: 0,
      // 模拟数据
      mockListData: [
        {
          planCode: 'PLAN-2024-001',
          planName: '2024年度实验室设备升级方案',
          stage: '设备升级实验',
          testDate: '2024-03-15',
          tester: '张三',
          creator: '张三',
          createTime: '2024-03-15',
          status: 'pending',
          approver: '李四',
          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',
          planName: '实验室安全管理制度更新方案',
          stage: '安全测试实验',
          testDate: '2024-03-14',
          tester: '王五',
          creator: '王五',
          createTime: '2024-03-14',
          status: 'approved',
          approver: '赵六',
          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',
          planName: '实验室人员培训计划',
          stage: '培训效果实验',
          testDate: '2024-03-13',
          tester: '孙七',
          creator: '孙七',
          createTime: '2024-03-13',
          status: 'rejected',
          approver: '周八',
          approveTime: '2024-03-14'
        }
      ],
      mockDraftData: [
        {
          planCode: 'DRAFT-2024-001',
          planName: '实验室设备采购计划(草稿)',
          stage: '规划阶段',
          creator: '张三',
          createTime: '2024-03-16',
          status: 'draft',
          approver: '',
          approveTime: ''
        },
        {
          planCode: 'DRAFT-2024-002',
          planName: '实验室改造方案(草稿)',
          stage: '准备阶段',
          creator: '李四',
          createTime: '2024-03-15',
          status: 'draft',
          approver: '',
          approveTime: ''
        }
      ],
      approvalDialogVisible: false,
      approvalDialogType: 'approve',
      currentApprovalData: null,
    };
  },
  created() {
    this.getTableData();
  },
  methods: {
    resetForm() {
      this.form = {
        planName: "",
        planCode: "",
        creator: "",
        createTime: [],
        approver: "",
        status: "",
      };
    },
    handleSearch() {
      // 实现查询逻辑
      console.log("查询条件:", this.form);
    },
    getStatusType(status) {
      const statusMap = {
        pending: "warning",
        rejected: "danger",
        approved: "success",
        archived: "info",
        draft: "info"
      };
      return statusMap[status] || "info";
    },
    getStatusText(status) {
      const statusMap = {
        pending: "待审批",
        rejected: "已驳回",
        approved: "已通过",
        archived: "已封存",
        draft: "草稿"
      };
      return statusMap[status] || "未知";
    },
    handleAddPlan() {
      this.$router.push({
        path: "/dataManagement/scheme-management/add",
      });
    },
    handleApprove(row) {
      this.currentApprovalData = row;
      this.approvalDialogType = 'approve';
      this.approvalDialogVisible = true;
    },
    handleApproveSubmit(data) {
      // 处理审批通过
      console.log('审批通过:', data);
      this.approvalDialogVisible = false;
      this.$message.success('审批通过成功');
      this.getTableData();
    },
    handleRejectSubmit(data) {
      // 处理审批驳回
      console.log('审批驳回:', data);
      this.approvalDialogVisible = false;
      this.$message.success('审批驳回成功');
      this.getTableData();
    },
    handleRevokeApprove(row) {
      // 实现撤销审批逻辑
      console.log("撤销审批数据:", row);
    },
    handleEdit(row) {
      this.$router.push({
        path: "/dataManagement/scheme-management/add",
        query: {
          id: row.planCode,
          type: 'edit'
        }
      });
    },
    handleDelete(row) {
      // 实现删除逻辑
      console.log("删除数据:", row);
    },
    handleDetail(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;
      this.getTableData();
    },
    getTableData() {
      // 根据currentType请求不同的数据
      if (this.currentType === 'list') {
        this.tableData = this.mockListData;
        this.total = this.mockListData.length;
      } else {
        this.tableData = this.mockDraftData;
        this.total = this.mockDraftData.length;
      }
    },
  },
};
</script>
<style scoped lang="less">
.list {
  height: 100%;
}
.flex {
  display: flex;
  align-items: center;
}
.tableTitle {
  display: flex;
  padding-bottom: 20px;
  justify-content: space-between;
  align-items: center;
  .title {
    background: #fafafc;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #dcdfe6;
    padding: 16px 29px;
    font-weight: bold;
    font-size: 18px;
    color: #606266;
    width: unset;
    cursor: pointer;
  }
  .drafts {
    padding: 16px 65px;
    background: #fafafc;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #dcdfe6;
    font-weight: 400;
    font-size: 18px;
    color: #606266;
    margin-left: 16px;
    cursor: pointer;
  }
  .active{
    color: #049c9a;
    background: #ffffff;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #049c9a;
  }
}
</style>
laboratory/src/views/dataManagement/schemeManagement/stop-experiment.vue
New file
@@ -0,0 +1,302 @@
<template>
  <Card>
    <template style="position: relative">
      <div class="header-title">
        <div class="header-title-left">
          <img src="@/assets/public/headercard.png" />
          <span>所属实验调度</span>
        </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>
      </div>
      <div class="header-title">
        <div class="header-title-left">
          <img src="@/assets/public/headercard.png" />
          <span>申请说明</span>
        </div>
      </div>
      <div class="content-box">
        <AiEditor
          ref="reasonEditor"
          v-model="editorContent"
          height="200px"
          placeholder="请输入申请说明..."
        />
      </div>
      <div class="upload-section">
        <div class="upload-title">
          <span>上传文件</span>
        </div>
        <el-upload
          class="upload-demo"
          action=""
          :auto-upload="false"
          :on-change="handleFileChange"
          :file-list="fileList"
        >
          <el-button size="small" type="primary">选择文件</el-button>
          <div slot="tip" class="el-upload__tip">支持格式:.rar .zip .doc .docx .pdf .jpg...</div>
        </el-upload>
      </div>
      <div class="footer-section">
        <div class="footer-content">
          <el-button type="primary" @click="openSignatureDialog" :disabled="!agreement">提交</el-button>
          <el-checkbox v-model="agreement">我确认,已仔细实验说明,准确描述本次实验中止全部情况及原因,特此申请中止本次实验。</el-checkbox>
        </div>
      </div>
    </template>
    <!-- 签字确认弹窗 -->
    <el-dialog
      title="实验中止确认"
      :visible.sync="signatureDialogVisible"
      width="30%"
      :close-on-click-modal="false"
      @close="handleDialogClose"
    >
      <div class="signature-dialog">
        <div class="add-group">
          <div class="required">*</div>
          <span>签字确认</span>
          <el-button type="primary" class="el-icon-plus" @click="openSignature">签名</el-button>
        </div>
        <img
          v-if="imgSrc"
          :src="imgSrc"
          alt="签名"
          class="signature-preview"
        />
      </div>
      <div slot="footer" class="dialog-footer">
        <el-button @click="handleDialogClose">取 消</el-button>
        <el-button type="primary" @click="handleConfirm">确 认</el-button>
      </div>
    </el-dialog>
    <SignatureCanvas
      :visible="signatureCanvasVisible"
      @confirm="handleSignatureConfirm"
    />
  </Card>
</template>
<script>
import AiEditor from '@/components/AiEditor'
import SignatureCanvas from "@/components/SignatureCanvas.vue"
export default {
  name: 'StopExperiment',
  components: {
    AiEditor,
    SignatureCanvas
  },
  data() {
    return {
      experimentData: [{
        name: '金标准研究项',
        code: 'DD-EX001',
        title: '金标准研究项',
        startTime: '2025-1-2 14:50:19',
        endTime: '2025-02-27',
        participants: '范兵, 李天霸, 张三, 李四',
        status: '已确认'
      }],
      editorContent: '',
      fileList: [],
      agreement: false,
      signatureDialogVisible: false,
      signatureCanvasVisible: false,
      imgSrc: "",
    }
  },
  methods: {
    handleFileChange(file, fileList) {
      this.fileList = fileList
    },
    getEditorContent() {
      return this.$refs.reasonEditor.getContent()
    },
    validateForm() {
      const content = this.getEditorContent()
      if (!content) {
        this.$message.error('请填写申请说明')
        return false
      }
      if (!this.agreement) {
        this.$message.error('请确认申请说明')
        return false
      }
      return true
    },
    openSignatureDialog() {
      this.signatureDialogVisible = true
    },
    handleDialogClose() {
      this.signatureDialogVisible = false
      this.imgSrc = ""
    },
    openSignature() {
      this.signatureCanvasVisible = true
    },
    handleSignatureConfirm(imageData) {
      this.signatureCanvasVisible = false
      this.imgSrc = imageData
    },
    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
        }
        console.log('提交的数据:', formData)
        this.$message.success('提交成功')
        this.handleDialogClose()
      }
    }
  }
}
</script>
<style scoped lang="less">
.header-title {
  display: flex;
  align-items: center;
  gap: 13px;
  margin-top: 38px;
  margin-bottom: 20px;
  .header-title-left {
    display: flex;
    align-items: center;
    gap: 13px;
    img {
      width: 12px;
      height: 19px;
    }
    span {
      font-weight: bold;
      font-size: 18px;
      color: #222222;
      line-height: 27px;
      font-family: "Source Han Sans CN Bold Bold";
    }
  }
}
.header-title:first-child {
  margin-top: 0;
}
.table-container {
  margin: 0 25px;
}
.content-box {
  padding: 0 25px;
  margin-bottom: 30px;
  width: 65%;
}
.upload-section {
  padding: 0 25px;
  margin-bottom: 30px;
  .upload-title {
    margin-bottom: 15px;
    span {
      font-size: 14px;
      color: #222222;
      &::before {
        content: "*";
        color: #f56c6c;
        margin-right: 4px;
      }
    }
  }
  .el-upload__tip {
    color: #909399;
    font-size: 12px;
    margin-top: 8px;
  }
}
.footer-section {
  padding: 20px 25px;
  // border-top: 1px solid #e4e7ed;
  margin-top: 60px;
  .footer-content {
    display: flex;
    align-items: center;
    gap: 20px;
    .el-button {
      width: 120px;
    }
  }
}
.signature-dialog {
  padding: 0 20px;
  .add-group {
    display: flex;
    align-items: center;
    margin-bottom: 15px;
    .required {
      color: #f56c6c;
      margin-right: 4px;
    }
    span {
      margin-right: 15px;
      font-size: 14px;
      color: #303133;
    }
  }
  .signature-preview {
    width: 200px;
    height: 100px;
    border: 2px dashed #049c9a;
    border-radius: 4px;
    object-fit: contain;
  }
}
.dialog-footer {
  display: flex;
  justify-content: center;
  align-items: center;
  padding-top: 20px;
  .el-button {
    width: 150px;
    margin: 0 10px;
  }
}
</style>
laboratory/src/views/dataManagement/suspendExperiment/components/approvalDialog.vue
New file
@@ -0,0 +1,345 @@
<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" />
                <div>所属实验调度</div>
              </div>
            </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="stage" label="实验名称"></el-table-column>
              <el-table-column prop="creator" label="通知时间"></el-table-column>
              <el-table-column prop="createTime" label="实验开始时间"></el-table-column>
              <el-table-column prop="approver" label="实验结束时间"></el-table-column>
              <el-table-column prop="approveTime" label="参加人员"></el-table-column>
              <el-table-column prop="status" label="状态">
              </el-table-column>
            </Table>
            <div class="header-title">
              <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="请输入文字" />
          </template>
        </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>
      </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'"
        style="margin-left: 20px;">通过</el-button>
    </div>
  </el-dialog>
</template>
<script>
import ApprovalProcess from '@/components/approvalProcess'
import AiEditor from '@/components/AiEditor'
export default {
  name: "ApprovalDialog",
  components: {
    ApprovalProcess, AiEditor
  },
  props: {
    visible: {
      type: Boolean,
      default: false,
    },
    type: {
      type: String,
      default: "approve", // approve-审批,view-查看
    },
    data: {
      type: Object,
      default: () => ({}),
    },
  },
  data() {
    return {
      form: {
        planName: "",
        planCode: "",
        stage: "",
        creator: "",
        createTime: "",
        approvalComment: "",
        status: "pending",
        approver: "",
        approveTime: ""
      },
      radio1: 1,
      rules: {},
      status: "1",
      remark: "",
    };
  },
  computed: {
    dialogTitle() {
      return this.type === "approve" ? "审批实验中止申请" : "实验中止申请审批详情";
    },
  },
  watch: {
    data: {
      handler(val) {
        if (val) {
          this.form = { ...val };
        }
      },
      immediate: true,
    },
  },
  methods: {
    handleClose() {
      this.$emit("update:visible", false);
      this.form.approvalComment = "";
    },
    handleApprove() {
      if (!this.form.approvalComment) {
        this.$message.warning("请输入审批意见");
        return;
      }
      this.$emit("approve", {
        ...this.form,
        status: "approved",
      });
    },
    handleReject() {
      if (!this.form.approvalComment) {
        this.$message.warning("请输入审批意见");
        return;
      }
      this.$emit("reject", {
        ...this.form,
        status: "rejected",
      });
    },
  },
};
</script>
<style scoped lang="less">
::v-deep .el-dialog__header {
  border-bottom: 1px solid #e4e7ed;
}
.approval-dialog {
  display: flex;
  height: 300px;
  .approval-content {
    flex: 1;
    margin-right: 20px;
    background: #ffffff;
    box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08);
    border-radius: 10px;
  }
  .approval-flow {
    padding: 40px 20px;
    width: 405px;
    background: #ffffff;
    box-shadow: 0px 4px 12px 4px rgba(0, 0, 0, 0.08);
    border-radius: 10px;
    .flow-title {
      font-size: 16px;
      font-weight: bold;
      margin-bottom: 20px;
      color: #303133;
    }
    .flow-content {
      height: calc(100% - 40px);
      overflow-y: auto;
      .el-form--inline .el-form-item {
        margin-right: 83px;
      }
    }
  }
}
.approval-content-card {
  height: calc(100% - 100px) !important;
  box-shadow: none !important;
}
.header-title {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 13px;
  margin-bottom: 38px;
  .header-title-left {
    display: flex;
    align-items: center;
    gap: 13px;
    margin-top: 38px;
    img {
      width: 12px;
      height: 19px;
    }
    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;
      }
    }
    span {
      flex-shrink: 0;
      font-weight: bold;
      font-size: 18px;
      color: #222222;
      line-height: 27px;
      font-family: "Source Han Sans CN Bold Bold";
    }
  }
  .header-title-left :first-child {
    margin-top: 0;
  }
}
.header-title:first-child {
  .header-title-left {
    margin-top: 0;
  }
}
.item-title {
  padding-left: 25px;
  span {
    flex-shrink: 0;
    font-weight: bold;
    font-size: 14px;
    color: #222222;
    line-height: 27px;
    font-family: "Source Han Sans CN Bold Bold";
    margin: 18px 0;
    &:before {
      content: "*";
      color: #f56c6c;
      margin-right: 4px;
    }
  }
}
.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;
  }
}
.dialog-footer {
  align-items: center;
  display: flex;
  justify-content: center;
  button {
    width: 150px;
  }
}
</style>
laboratory/src/views/dataManagement/suspendExperiment/list.vue
New file
@@ -0,0 +1,290 @@
<template>
  <div class="list">
    <TableCustom :queryForm="form" :tableData="tableData" :total="total">
      <template #search>
        <el-form :model="form" labelWidth="auto" inline>
          <el-form-item label="所属项目课题方案:">
            <el-input v-model="form.planName" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="实验编号:">
            <el-input v-model="form.planCode" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="创建时间:">
            <el-date-picker v-model="form.createTime" type="daterange" range-separator="至" start-placeholder="开始日期"
              end-placeholder="结束日期" value-format="yyyy-MM-dd"></el-date-picker>
          </el-form-item>
          <el-form-item label="状态">
            <el-input v-model="form.approver" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="">
            <el-button type="default" @click="resetForm">重置</el-button>
            <el-button type="primary" @click="handleSearch" style="margin-left: 20px;">查询</el-button>
          </el-form-item>
        </el-form>
      </template>
      <template #setting>
        <div class="tableTitle">
          <div class="flex a-center">
            <div class="title" :class="{ active: currentType === 'list' }" @click="handleTypeChange('list')">申请中止试验方案列表
            </div>
          </div>
        </div>
      </template>
      <template #table>
        <el-table-column prop="planCode" label="所属项目课题方案"></el-table-column>
        <el-table-column prop="planName" label="实验编号"></el-table-column>
        <el-table-column prop="stage" label="实验名称"></el-table-column>
        <el-table-column prop="creator" label="试验日期"></el-table-column>
        <el-table-column prop="createTime" label="实验员"></el-table-column>
        <el-table-column prop="approver" label="提交时间"></el-table-column>
        <el-table-column prop="approveTime" 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-column label="操作" width="250">
          <template slot-scope="scope">
            <el-button v-if="scope.row.status === 'pending'" type="text"
              @click="handleApprove(scope.row)">审批</el-button>
            <el-button type="text" @click="handleDetail(scope.row)">详情</el-button>
          </template>
        </el-table-column>
      </template>
    </TableCustom>
    <!-- 审批弹窗 -->
    <approval-dialog :visible.sync="approvalDialogVisible" :type="approvalDialogType" :data="currentApprovalData"
      @approve="handleApproveSubmit" @reject="handleRejectSubmit" />
  </div>
</template>
<script>
import ApprovalDialog from './components/approvalDialog.vue'
export default {
  name: "ProjectList",
  components: {
    ApprovalDialog
  },
  data() {
    return {
      currentType: 'list', // 当前显示类型:list-列表,draft-草稿箱
      form: {
        planName: "",
        planCode: "",
        creator: "",
        createTime: [],
        approver: "",
        status: "",
      },
      tableData: [],
      total: 0,
      // 模拟数据
      mockListData: [
        {
          planCode: 'PLAN-2024-001',
          planName: '2024年度实验室设备升级方案',
          stage: '规划阶段',
          creator: '张三',
          createTime: '2024-03-15',
          status: 'pending',
          approver: '李四',
          approveTime: '2024-03-16'
        },
        {
          planCode: 'PLAN-2024-002',
          planName: '实验室安全管理制度更新方案',
          stage: '实施阶段',
          creator: '王五',
          createTime: '2024-03-14',
          status: 'approved',
          approver: '赵六',
          approveTime: '2024-03-15'
        },
        {
          planCode: 'PLAN-2024-003',
          planName: '实验室人员培训计划',
          stage: '准备阶段',
          creator: '孙七',
          createTime: '2024-03-13',
          status: 'rejected',
          approver: '周八',
          approveTime: '2024-03-14'
        }
      ],
      mockDraftData: [
        {
          planCode: 'DRAFT-2024-001',
          planName: '实验室设备采购计划(草稿)',
          stage: '规划阶段',
          creator: '张三',
          createTime: '2024-03-16',
          status: 'draft',
          approver: '',
          approveTime: ''
        },
        {
          planCode: 'DRAFT-2024-002',
          planName: '实验室改造方案(草稿)',
          stage: '准备阶段',
          creator: '李四',
          createTime: '2024-03-15',
          status: 'draft',
          approver: '',
          approveTime: ''
        }
      ],
      approvalDialogVisible: false,
      approvalDialogType: 'approve',
      currentApprovalData: null,
    };
  },
  created() {
    this.getTableData();
  },
  methods: {
    resetForm() {
      this.form = {
        planName: "",
        planCode: "",
        creator: "",
        createTime: [],
        approver: "",
        status: "",
      };
    },
    handleSearch() {
      // 实现查询逻辑
      console.log("查询条件:", this.form);
    },
    getStatusType(status) {
      const statusMap = {
        pending: "warning",
        rejected: "danger",
        approved: "success",
        archived: "info",
        draft: "info"
      };
      return statusMap[status] || "info";
    },
    getStatusText(status) {
      const statusMap = {
        pending: "待审批",
        rejected: "已驳回",
        approved: "已通过",
        archived: "已封存",
        draft: "草稿"
      };
      return statusMap[status] || "未知";
    },
    handleAddPlan() {
      this.$router.push({
        path: "/dataManagement/addPlan",
      });
    },
    handleApprove(row) {
      this.currentApprovalData = row;
      this.approvalDialogType = 'approve';
      this.approvalDialogVisible = true;
    },
    handleApproveSubmit(data) {
      // 处理审批通过
      console.log('审批通过:', data);
      this.approvalDialogVisible = false;
      this.$message.success('审批通过成功');
      this.getTableData();
    },
    handleRejectSubmit(data) {
      // 处理审批驳回
      console.log('审批驳回:', data);
      this.approvalDialogVisible = false;
      this.$message.success('审批驳回成功');
      this.getTableData();
    },
    handleRevokeApprove(row) {
      // 实现撤销审批逻辑
      console.log("撤销审批数据:", row);
    },
    handleEdit(row) {
      // 实现编辑逻辑
      console.log("编辑数据:", row);
    },
    handleDelete(row) {
      // 实现删除逻辑
      console.log("删除数据:", row);
    },
    handleDetail(row) {
      this.currentApprovalData = row;
      this.approvalDialogType = 'view';
      this.approvalDialogVisible = true;
    },
    handleTypeChange(type) {
      this.currentType = type;
      this.getTableData();
    },
    getTableData() {
      // 根据currentType请求不同的数据
      if (this.currentType === 'list') {
        this.tableData = this.mockListData;
        this.total = this.mockListData.length;
      } else {
        this.tableData = this.mockDraftData;
        this.total = this.mockDraftData.length;
      }
    },
  },
};
</script>
<style scoped lang="less">
.list {
  height: 100%;
}
.flex {
  display: flex;
  align-items: center;
}
.tableTitle {
  display: flex;
  padding-bottom: 20px;
  justify-content: space-between;
  align-items: center;
  .title {
    background: #fafafc;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #dcdfe6;
    padding: 16px 29px;
    font-weight: bold;
    font-size: 18px;
    color: #606266;
    width: unset;
    cursor: pointer;
  }
  .drafts {
    padding: 16px 65px;
    background: #fafafc;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #dcdfe6;
    font-weight: 400;
    font-size: 18px;
    color: #606266;
    margin-left: 16px;
    cursor: pointer;
  }
  .active {
    color: #049c9a;
    background: #ffffff;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #049c9a;
  }
}
</style>
laboratory/src/views/deliveryAssessment/QA/components/AssessmentDialog.vue
File was renamed from culture/src/views/deliveryAssessment/assayTaskList/components/AssessmentDialog.vue
@@ -27,7 +27,7 @@
                                <el-input-number v-model="row.score" :min="0" :max="row.fullScore" :step="1" />
                            </template>
                        </el-table-column>
                        <el-table-column prop="rule" label="创新型课题报告评分规则">
                        <el-table-column label="创新型课题报告评分规则">
                            <template>
                                <div>
                                    <div>1、规程型课题评定总分的满分为5分。</div>
@@ -43,10 +43,11 @@
                </el-col>
            </el-row>
        </div>
        <div class="assessed">
            <div>评定时间:2025-2-20 11:08:00</div>
            <div>评定人:张三</div>
        </div>
        <template #footer>
            <span class="select-member-footer">
                <el-button type="primary">提交评定结果</el-button>
            </span>
        </template>
    </el-dialog>
</template>
@@ -196,13 +197,5 @@
            line-height: 23px;
        }
    }
}
.assessed {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 25px;
    margin-top: 20px;
}
</style>
laboratory/src/views/deliveryAssessment/QA/index.vue
copy from culture/src/views/deliveryAssessment/assayTaskList/index.vue copy to laboratory/src/views/deliveryAssessment/QA/index.vue
File was copied from culture/src/views/deliveryAssessment/assayTaskList/index.vue
@@ -4,16 +4,16 @@
            @sizeChange="handleSizeChange">
            <template #search>
                <el-form :model="form" label-width="140px" inline>
                    <el-form-item label="项目组名称:">
                    <el-form-item label="所属项目组:">
                        <el-input v-model="form.name" placeholder="请输入" />
                    </el-form-item>
                    <el-form-item label="课题类型:">
                    <el-form-item label="检测项名称:">
                        <el-input v-model="form.name" placeholder="请输入" />
                    </el-form-item>
                    <el-form-item label="课题名称:">
                    <el-form-item label="检测项编号:">
                        <el-input v-model="form.name" placeholder="请输入" />
                    </el-form-item>
                    <el-form-item label="提交人:">
                    <el-form-item label="报告内容:">
                        <el-input v-model="form.name" placeholder="请输入" />
                    </el-form-item>
                    <el-form-item label="状态:">
@@ -26,22 +26,19 @@
                </el-form>
            </template>
            <template #setting>
                <div class="top-box-integral">
                    <div style="background-color:rgba(232, 250, 246, 1)" v-for="item in 3" :key="item"
                        class="top-box-integral-card">
                        <div class="top-box-integral-card-title">{{ ['课题合计数量', '待评定', '已评定'][item - 1] }}</div>
                        <div style="color:rgba(4, 156, 154, 1)" class="top-box-integral-card-num">99.9</div>
                    </div>
                <div class="tableTitle">
                    <div class="title active">
                        化验师QA专题报告列表</div>
                </div>
            </template>
            <template #table>
                <el-table-column prop="name" label="报告类型" />
                <el-table-column prop="name" label="所属项目组" />
                <el-table-column prop="age" label="课题类型" />
                <el-table-column prop="age" label="报告编号" />
                <el-table-column prop="age" label="报告名称" />
                <el-table-column prop="age" label="提交人" />
                <el-table-column prop="age" label="评定结果" />
                <el-table-column prop="age" label="累积分值" />
                <el-table-column prop="age" label="报告编号" />
                <el-table-column prop="age" label="制定人" />
                <el-table-column prop="age" label="制定日期" />
                <el-table-column prop="age" label="评定人" />
                <el-table-column prop="age" label="评定时间" />
                <el-table-column prop="age" label="状态">
                    <template #default="{ row }">
@@ -57,23 +54,18 @@
            </template>
        </TableCustom>
        <!-- 化验师 审批人 -->
        <AssessmentDialog :modelValue="assessmentVisible" :reportData="currentReport" />
        <!-- 工艺工程师 -->
        <CraftDialog :modelValue="craftVisible" :reportData="currentReport" />
        <AssessmentDialog :modelValue="assessmentVisible" :reportData="currentReport" />
    </div>
</template>
<script>
import AssessmentDialog from './components/AssessmentDialog.vue'
import CraftDialog from './components/CraftDialog.vue'
export default {
    name: 'AssayTaskList',
    name: 'QAList',
    components: {
        AssessmentDialog,
        CraftDialog
    },
    data() {
        return {
@@ -85,9 +77,8 @@
                pageNum: 1
            },
            total: 0,
            assessmentVisible: false,
            assessmentVisible: true,
            currentReport: {},
            craftVisible: false,
        }
    },
    methods: {
@@ -151,4 +142,27 @@
        }
    }
}
.tableTitle {
    display: flex;
    .title {
        background: #fafafc;
        border-radius: 8px 8px 0px 0px;
        border: 1px solid #dcdfe6;
        padding: 16px 29px;
        font-weight: bold;
        font-size: 18px;
        color: #606266;
        width: unset;
        cursor: pointer;
    }
    .active {
        color: #049c9a;
        background: #ffffff;
        border-radius: 8px 8px 0px 0px;
        border: 1px solid #049c9a;
    }
}
</style>
laboratory/src/views/deliveryAssessment/chemistEvaluate/index.vue
@@ -179,7 +179,6 @@
.tableTitle {
    display: flex;
    padding-bottom: 20px;
    justify-content: space-between;
    align-items: center;
laboratory/src/views/deliveryAssessment/experimenterJobEvaluation/index.vue
File was renamed from culture/src/views/deliveryAssessment/experimentResults/index.vue
@@ -4,13 +4,10 @@
            @sizeChange="handleSizeChange">
            <template #search>
                <el-form :model="form" label-width="140px" inline>
                    <el-form-item label="项目课题方案名称:">
                    <el-form-item label="所属项目课题方案:">
                        <el-input v-model="form.name" placeholder="请输入" />
                    </el-form-item>
                    <el-form-item label="所属实验编号:">
                        <el-input v-model="form.name" placeholder="请输入" />
                    </el-form-item>
                    <el-form-item label="实验名称:">
                    <el-form-item label="实验编号:">
                        <el-input v-model="form.name" placeholder="请输入" />
                    </el-form-item>
                    <el-form-item label="创建时间:">
@@ -18,7 +15,7 @@
                            end-placeholder="结束日期">
                        </el-date-picker>
                    </el-form-item>
                    <el-form-item label="项目阶段:">
                    <el-form-item label="状态:">
                        <el-select v-model="form.status" placeholder="请选择">
                            <el-option label="是" value="1"></el-option>
                            <el-option label="否" value="0"></el-option>
@@ -32,37 +29,34 @@
            </template>
            <template #table>
                <el-table-column prop="name" label="所属项目课题方案" />
                <el-table-column prop="age" label="项目阶段" />
                <el-table-column prop="age" label="实验编号" />
                <el-table-column prop="age" label="实验名称" />
                <el-table-column prop="age" label="工艺工程师" />
                <el-table-column prop="age" label="化验师" />
                <el-table-column prop="age" label="实验员" />
                <el-table-column prop="age" label="是否评定">
                <el-table-column prop="age" label="创建日期" />
                <el-table-column prop="age" label="状态">
                    <template #default="{ row }">
                        <span :style="{ color: ['green', 'red'][row.status - 1] }">{{ ['是', '否'][row.status - 1]
                        }}</span>
                            }}</span>
                    </template>
                </el-table-column>
                <el-table-column prop="age" label="创建日期" />
                <el-table-column prop="age" label="操作">
                    <template #default="{ row }">
                        <el-button type="text">评定详情</el-button>
                        <el-button type="text">详情</el-button>
                        <el-button type="text">评定</el-button>
                    </template>
                </el-table-column>
            </template>
        </TableCustom>
        <Detail :modelValue="assessmentVisible" :reportData="currentReport" />
    </div>
</template>
<script>
import Detail from './components/detail.vue'
export default {
    name: 'ExperimentResults',
    name: 'ExperimenterJobEvaluation',
    components: {
        Detail
    },
    data() {
        return {
@@ -74,8 +68,6 @@
                pageNum: 1
            },
            total: 0,
            assessmentVisible: false,
            currentReport: {}
        }
    },
    methods: {
laboratory/src/views/deliveryAssessment/reportEvaluation/components/CraftDialog.vue
File was renamed from culture/src/views/deliveryAssessment/assayTaskList/components/CraftDialog.vue
@@ -55,6 +55,11 @@
            <div>评定时间:2025-2-20 11:08:00</div>
            <div>评定人:张三</div>
        </div>
        <template #footer>
            <span class="select-member-footer">
                <el-button type="primary">提交评定结果</el-button>
            </span>
        </template>
    </el-dialog>
</template>
laboratory/src/views/deliveryAssessment/reportEvaluation/index.vue
File was renamed from culture/src/views/deliveryAssessment/assayTaskList/index.vue
@@ -52,27 +52,23 @@
                <el-table-column prop="age" label="操作">
                    <template #default="{ row }">
                        <el-button type="text">详情</el-button>
                        <el-button type="text">评定</el-button>
                    </template>
                </el-table-column>
            </template>
        </TableCustom>
        <!-- 化验师 审批人 -->
        <AssessmentDialog :modelValue="assessmentVisible" :reportData="currentReport" />
        <!-- 工艺工程师 -->
        <!-- 审批人 -->
        <CraftDialog :modelValue="craftVisible" :reportData="currentReport" />
    </div>
</template>
<script>
import AssessmentDialog from './components/AssessmentDialog.vue'
import CraftDialog from './components/CraftDialog.vue'
export default {
    name: 'AssayTaskList',
    name: 'ReportEvaluation',
    components: {
        AssessmentDialog,
        CraftDialog
    },
    data() {
@@ -85,7 +81,6 @@
                pageNum: 1
            },
            total: 0,
            assessmentVisible: false,
            currentReport: {},
            craftVisible: false,
        }
laboratory/src/views/deliveryAssessment/technicianJobEvaluation/index.vue
copy from culture/src/views/deliveryAssessment/experimentResults/index.vue copy to laboratory/src/views/deliveryAssessment/technicianJobEvaluation/index.vue
File was copied from culture/src/views/deliveryAssessment/experimentResults/index.vue
@@ -4,13 +4,10 @@
            @sizeChange="handleSizeChange">
            <template #search>
                <el-form :model="form" label-width="140px" inline>
                    <el-form-item label="项目课题方案名称:">
                    <el-form-item label="所属项目课题方案:">
                        <el-input v-model="form.name" placeholder="请输入" />
                    </el-form-item>
                    <el-form-item label="所属实验编号:">
                        <el-input v-model="form.name" placeholder="请输入" />
                    </el-form-item>
                    <el-form-item label="实验名称:">
                    <el-form-item label="实验编号:">
                        <el-input v-model="form.name" placeholder="请输入" />
                    </el-form-item>
                    <el-form-item label="创建时间:">
@@ -18,7 +15,7 @@
                            end-placeholder="结束日期">
                        </el-date-picker>
                    </el-form-item>
                    <el-form-item label="项目阶段:">
                    <el-form-item label="状态:">
                        <el-select v-model="form.status" placeholder="请选择">
                            <el-option label="是" value="1"></el-option>
                            <el-option label="否" value="0"></el-option>
@@ -32,37 +29,34 @@
            </template>
            <template #table>
                <el-table-column prop="name" label="所属项目课题方案" />
                <el-table-column prop="age" label="项目阶段" />
                <el-table-column prop="age" label="实验编号" />
                <el-table-column prop="age" label="实验名称" />
                <el-table-column prop="age" label="工艺工程师" />
                <el-table-column prop="age" label="化验师" />
                <el-table-column prop="age" label="实验员" />
                <el-table-column prop="age" label="是否评定">
                <el-table-column prop="age" label="创建日期" />
                <el-table-column prop="age" label="状态">
                    <template #default="{ row }">
                        <span :style="{ color: ['green', 'red'][row.status - 1] }">{{ ['是', '否'][row.status - 1]
                        }}</span>
                            }}</span>
                    </template>
                </el-table-column>
                <el-table-column prop="age" label="创建日期" />
                <el-table-column prop="age" label="操作">
                    <template #default="{ row }">
                        <el-button type="text">评定详情</el-button>
                        <el-button type="text">详情</el-button>
                        <el-button type="text">评定</el-button>
                    </template>
                </el-table-column>
            </template>
        </TableCustom>
        <Detail :modelValue="assessmentVisible" :reportData="currentReport" />
    </div>
</template>
<script>
import Detail from './components/detail.vue'
export default {
    name: 'ExperimentResults',
    name: 'TechnicianJobEvaluation',
    components: {
        Detail
    },
    data() {
        return {
@@ -74,8 +68,6 @@
                pageNum: 1
            },
            total: 0,
            assessmentVisible: false,
            currentReport: {}
        }
    },
    methods: {
laboratory/src/views/deliveryAssessment/testingAndEvaluation/components/AssessmentDialog.vue
@@ -43,10 +43,17 @@
                </el-col>
            </el-row>
        </div>
        <!-- 化验师 审批人 -->
        <div class="assessed">
            <div>评定时间:2025-2-20 11:08:00</div>
            <div>评定人:张三</div>
        </div>
        <!-- 工艺工程师 -->
        <!-- <template #footer>
            <span class="select-member-footer">
                <el-button type="primary">提交评定结果</el-button>
            </span>
        </template> -->
    </el-dialog>
</template>
laboratory/src/views/deliveryAssessment/testingAndEvaluation/index.vue
@@ -70,6 +70,8 @@
                    </el-table-column>
                    <el-table-column prop="age" label="操作">
                        <template #default="{ row }">
                            <!-- 工艺工程师 -->
                            <!-- <el-button type="text" @click="assessmentVisible = true">评定</el-button> -->
                            <el-button type="text" @click="assessmentVisible = true">详情</el-button>
                        </template>
                    </el-table-column>
@@ -111,7 +113,7 @@
            if (this.expandRowKeys.includes(row.id)) {
                this.expandRowKeys = this.expandRowKeys.filter(key => key !== row.id);
            } else {
                this.expandRowKeys.push(row.id);
                this.expandRowKeys = [row.id];
            }
        },
        handleCurrentChange(page) {