pyt
2025-05-09 9a9f9dd67261e75d66c338f8573fd0b60f7cab9e
Merge branch 'main' of http://120.76.84.145:10101/gitblit/r/H5/leshan-laboratory
13个文件已修改
2个文件已添加
1691 ■■■■ 已修改文件
laboratory/src/components/AiEditor/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/components/approvalProcess/index.vue 167 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/layouts/components/HeaderNav.vue 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/router/index.js 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/utils/request.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/dataManagement/approvalPlan/addPlan.vue 231 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/dataManagement/approvalPlan/components/approvalDialog.vue 263 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/dataManagement/approvalPlan/list.vue 317 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/dataManagement/approvalPlan/service.js 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/login/index.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/projectList/addProject.vue 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/projectList/detailProject.vue 236 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/projectList/editProject.vue 288 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/projectList/index.vue 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/projectList/service.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/components/AiEditor/index.vue
@@ -60,10 +60,14 @@
      },
      immediate: true
    },
    readOnly(newVal) {
      if (this.editor) {
        this.editor.setReadOnly(newVal)
      }
    readOnly: {
      handler(newVal) {
        console.log('readOnly', newVal)
        if (this.editor) {
          this.editor.setReadOnly(newVal)
        }
      },
      immediate: true
    }
  },
  mounted() {
laboratory/src/components/approvalProcess/index.vue
@@ -6,32 +6,40 @@
        :key="index"
        :type="activity.type"
      >
        <div v-if="activity.mode && activity.mode == 'card'">
          <div class="member-list-card" v-for="item in 3" :key="item">
        <div v-if="activity.mode === 'card'">
          <div
            class="member-list-card"
            v-for="(group, groupIndex) in activity.groups"
            :key="groupIndex"
          >
            <div class="member-item">
              <div class="member-title">
                {{ ["工艺工程师", "实验员", "化验师"][item - 1] }}
                {{ group.title }}
              </div>
              <div class="flex-over">
                <div class="people-list" v-for="item in 10" :key="item">
                <div
                  class="people-list"
                  v-for="(member, memberIndex) in group.members"
                  :key="memberIndex"
                >
                  <div class="people-item">
                    <img src="" alt="" class="people-img" />
                    <div class="member-name">{{ item }}</div>
                    <img :src="member.avatar" alt="" class="people-img" />
                    <div class="member-name">{{ member.name }}</div>
                  </div>
                  <div class="member-status">
                    <div
                      class="member-status-text"
                      :class="
                        activity.type == 'success' ? 'success' : 'warning'
                        member.status === 'approved' ? 'success' : 'warning'
                      "
                    >
                      {{ activity.type == "success" ? "同意" : "待审批" }}
                      {{ member.status === "approved" ? "同意" : "待审批" }}
                    </div>
                    <div
                      v-if="activity.type == 'success'"
                      v-if="member.status === 'approved'"
                      class="member-status-time"
                    >
                      2025-04-08 12:30
                      {{ member.approveTime }}
                    </div>
                  </div>
                </div>
@@ -42,11 +50,15 @@
        <div
          v-else
          class="approval-process-item"
          :class="activity.type == 'primary' ? '' : 'approval-process-item1'"
          :class="activity.type === 'primary' ? '' : 'approval-process-item1'"
        >
          <div class="approval-process-item-name">提交人:李雷雷</div>
          <div class="approval-process-item-time">
            提交时间:2020-12-01 12:30
          <div
            v-for="(field, idx) in activity.fields"
            :key="idx"
            style="margin-bottom: 6px"
          >
            <span>{{ field.label }}</span>
            <span>{{ field.value }}</span>
          </div>
        </div>
      </el-timeline-item>
@@ -58,58 +70,77 @@
export default {
  name: "ApprovalProcess",
  props: {
    status: {
      type: String,
      default: "approved", // pending-等待审核, approved-已通过
      validator: (value) => ["pending", "approved"].includes(value),
    },
    submitTime: {
      type: String,
      default: "",
    },
    approver: {
      type: String,
      default: "",
    },
    approveTime: {
      type: String,
      default: "",
    },
    mode: {
      type: String,
      default: "",//card-就是卡片模式,其他就是列表模式
    },
  },
  computed: {
    processData() {
      const baseData = [
        {
          content: "提交审批",
          time: this.submitTime,
          type: "primary",
        },
      ];
      if (this.status === "approved") {
        console.log("111111111111111", this.status);
        baseData.push({
          content: `${this.approver}审批通过`,
          time: this.approveTime,
          type: "success",
          mode: this.mode,
        });
      } else {
        console.log("122222222222222221", this.status);
        baseData.push({
          content: "等待审核",
          time: this.submitTime,
          type: "warning",
          mode: this.mode,
        });
      }
      return baseData;
    processData: {
      type: Array,
      required: true,
      // 数据结构示例:
      // [
      //   {
      //     type: 'primary',
      //     content: '提交审批',
      //     time: '2020-12-01 12:30',
      //     mode: 'list'
      //   },
      //   {
      //     type: 'success',
      //     mode: 'card',
      //     groups: [
      //       {
      //         title: '工艺工程师',
      //         members: [
      //           {
      //             name: '张三',
      //             avatar: '',
      //             status: 'approved',
      //             approveTime: '2025-04-08 12:30'
      //           }
      //         ]
      //       }
      //     ]
      //   }
      // ]
      // 数据结构示例:
      //       [
      //   {
      //     type: 'success',
      //     mode: 'card',
      //     groups: [
      //       {
      //         title: '工艺工程师',
      //         members: [
      //           {
      //             fields: [
      //               { label: '姓名', value: '张三' },
      //               { label: '状态', value: '同意' },
      //               { label: '审批时间', value: '2025-04-08 12:30' }
      //             ],
      //             avatar: '' // 头像地址,可选
      //           },
      //           {
      //             fields: [
      //               { label: '姓名', value: '李四' },
      //               { label: '状态', value: '待审批' }
      //             ],
      //             avatar: ''
      //           }
      //         ]
      //       },
      //       {
      //         title: '实验员',
      //         members: [
      //           {
      //             fields: [
      //               { label: '姓名', value: '王五' },
      //               { label: '状态', value: '同意' },
      //               { label: '审批时间', value: '2025-04-08 13:00' }
      //             ],
      //             avatar: ''
      //           }
      //         ]
      //       }
      //     ]
      //   }
      // ]
    },
  },
};
@@ -230,7 +261,7 @@
        text-align: left;
        padding-left: 17px;
      }
      .flex-over{
      .flex-over {
        margin-top: 12px;
        flex: 1;
        overflow: auto;
@@ -242,7 +273,7 @@
        align-items: center;
        padding: 0 20px;
        margin-top: 12px;
        display: flex;
        .people-item {
          display: flex;
laboratory/src/layouts/components/HeaderNav.vue
@@ -73,7 +73,10 @@
    },
    // 跳转标签
    goTag(tag) {
      this.$router.push(tag.path)
        this.$router.push({
            path: tag.path,
            query: tag.query
        })
    },
    handleWheel(e) {
      if (this.scrollTimer) {
laboratory/src/router/index.js
@@ -120,6 +120,24 @@
                    keepAlive: true,
                },
                component: () => import("../views/projectList/addProject"),
            },
            {
                path: "editProject",
                name: "EditProject",
                meta: {
                    title: "编辑项目组",
                    hide: true,
                },
                component: () => import("../views/projectList/editProject"),
            },
            {
                path: "detailProject",
                name: "DetailProject",
                meta: {
                    title: "项目组详情",
                    hide: true,
                },
                component: () => import("../views/projectList/detailProject"),
            }
        ]
    },
@@ -701,7 +719,8 @@
            const tagInfo = {
                path: to.path,
                name: to.name,
                meta: to.meta
                meta: to.meta,
                query: to.query,
            }
            tagList.push(tagInfo)
            sessionStorage.setItem('tagList', JSON.stringify(tagList))
laboratory/src/utils/request.js
@@ -57,10 +57,10 @@
    }
    if (res.data.code == 200) {
      console.log('res',res)
      if (!res.data) {
        return Promise.resolve({})
      }
      console.log('res', res.data.data || res.data)
      return Promise.resolve(res.data.data || res.data)
    } else {
      if (res.data.code == 103 || res.data.code == 401) {
laboratory/src/views/dataManagement/approvalPlan/addPlan.vue
@@ -15,14 +15,18 @@
        label-position="top"
        style="margin-top: 38px"
      >
        <el-form-item prop="name" label="项目课题方案名称">
          <el-input v-model="form.name" placeholder="请输入" />
        <el-form-item prop="projectName" label="项目课题方案名称">
          <el-input v-model="form.projectName" placeholder="请输入" />
        </el-form-item>
        <el-form-item prop="description" label="项目阶段">
          <el-input v-model="form.description" placeholder="请输入" />
        <el-form-item prop="projectStage" label="项目阶段">
          <el-select v-model="form.projectStage" placeholder="请选择">
            <el-option label="实验室开发阶段" :value="1" />
            <el-option label="中式试验阶段" :value="2" />
            <el-option label="生产验证试验阶段" :value="3" />
          </el-select>
        </el-form-item>
        <el-form-item prop="description" label="项目课题方案编号">
          <el-input v-model="form.description" placeholder="请输入" />
        <el-form-item prop="projectCode" label="项目课题方案编号">
          <el-input v-model="form.projectCode" placeholder="请输入" disabled />
        </el-form-item>
      </el-form>
      <div class="header-title">
@@ -33,7 +37,7 @@
      </div>
      <AiEditor
        ref="purposeEditor"
        v-model="editorContents.purpose"
        :value="editorContents.experimentObjective"
        height="200px"
        placeholder="请输入实验目的..."
      />
@@ -48,7 +52,7 @@
      </div>
      <AiEditor
        ref="materialEditor"
        v-model="editorContents.material"
        :value="editorContents.experimentMaterial"
        height="200px"
        placeholder="请输入实验材料..."
      />
@@ -57,7 +61,7 @@
      </div>
      <AiEditor
        ref="equipmentEditor"
        v-model="editorContents.equipment"
        :value="editorContents.experimentDevice"
        height="200px"
        placeholder="请输入实验设备..."
      />
@@ -69,7 +73,7 @@
      </div>
      <AiEditor
        ref="methodEditor"
        v-model="editorContents.method"
        :value="editorContents.experimentTestMethod"
        height="200px"
        placeholder="请输入检测方法及开发..."
      />
@@ -81,7 +85,7 @@
      </div>
      <AiEditor
        ref="stepsEditor"
        v-model="editorContents.steps"
        :value="editorContents.experimentProcedure"
        height="200px"
        placeholder="请输入实验步骤..."
      />
@@ -93,7 +97,7 @@
      </div>
      <AiEditor
        ref="dataAnalysisEditor"
        v-model="editorContents.dataAnalysis"
        :value="editorContents.dataAcquisition"
        height="200px"
        placeholder="请输入数据采集及分析..."
      />
@@ -105,7 +109,7 @@
      </div>
      <AiEditor
        ref="evaluationEditor"
        v-model="editorContents.evaluation"
        :value="editorContents.resultEvaluation"
        height="200px"
        placeholder="请输入结果评估..."
      />
@@ -117,7 +121,7 @@
      </div>
      <AiEditor
        ref="notesEditor"
        v-model="editorContents.notes"
        :value="editorContents.precautions"
        height="200px"
        placeholder="请输入注意事项..."
      />
@@ -132,7 +136,7 @@
<script>
import AiEditor from '@/components/AiEditor'
// import { savePlan, saveDraft } from '@/api/approvalPlan'
import { addProposal, updateProposal, getDetailById } from './service'
export default {
  name: "AddProject",
@@ -141,59 +145,62 @@
  },
  data() {
    return {
      id: '', // 用于编辑时存储id
      form: {
        name: '',
        description: '',
        purpose: '',
        material: '',
        equipment: '',
        method: '',
        steps: '',
        dataAnalysis: '',
        evaluation: '',
        notes: ''
        projectName: '',
        projectStage: '',
        projectCode: '',
        experimentObjective: '',
        experimentMaterial: '',
        experimentDevice: '',
        experimentTestMethod: '',
        experimentProcedure: '',
        dataAcquisition: '',
        resultEvaluation: '',
        precautions: '',
        auditStatus: 1, // 默认为审批中
      },
      editorContents: {
        purpose: '',
        material: '',
        equipment: '',
        method: '',
        steps: '',
        dataAnalysis: '',
        evaluation: '',
        notes: ''
        experimentObjective: '',
        experimentMaterial: '',
        experimentDevice: '',
        experimentTestMethod: '',
        experimentProcedure: '',
        dataAcquisition: '',
        resultEvaluation: '',
        precautions: ''
      },
      rules: {
        name: [
          { required: true, message: "请输入项目组名称", trigger: "blur" },
        projectName: [
          { required: true, message: "请输入项目课题方案名称", trigger: "blur" },
        ],
        description: [
          { required: true, message: "请输入项目组描述", trigger: "blur" },
        projectStage: [
          { required: true, message: "请选择项目阶段", trigger: "change" },
        ],
        purpose: [
        experimentObjective: [
          { required: true, message: "请输入实验目的", trigger: "blur" },
        ],
        material: [
        experimentMaterial: [
          { required: true, message: "请输入实验材料", trigger: "blur" },
        ],
        equipment: [
        experimentDevice: [
          { required: true, message: "请输入实验设备", trigger: "blur" },
        ],
        method: [
        experimentTestMethod: [
          { required: true, message: "请输入检测方法及开发", trigger: "blur" },
        ],
        steps: [
        experimentProcedure: [
          { required: true, message: "请输入实验步骤", trigger: "blur" },
        ],
        dataAnalysis: [
        dataAcquisition: [
          { required: true, message: "请输入数据采集及分析", trigger: "blur" },
        ],
        evaluation: [
        resultEvaluation: [
          { required: true, message: "请输入结果评估", trigger: "blur" },
        ],
        notes: [
          { required: true, message: "请输入注意事项", trigger: "blur" },
        ],
        // precautions: [
        //   { required: true, message: "请输入注意事项", trigger: "blur" },
        // ],
      },
    };
  },
@@ -201,40 +208,130 @@
    // 获取所有编辑器的内容
    getAllEditorContent() {
      return {
        purpose: this.$refs.purposeEditor.getContent(),
        material: this.$refs.materialEditor.getContent(),
        equipment: this.$refs.equipmentEditor.getContent(),
        method: this.$refs.methodEditor.getContent(),
        steps: this.$refs.stepsEditor.getContent(),
        dataAnalysis: this.$refs.dataAnalysisEditor.getContent(),
        evaluation: this.$refs.evaluationEditor.getContent(),
        notes: this.$refs.notesEditor.getContent()
        experimentObjective: this.$refs.purposeEditor.getContent(),
        experimentMaterial: this.$refs.materialEditor.getContent(),
        experimentDevice: this.$refs.equipmentEditor.getContent(),
        experimentTestMethod: this.$refs.methodEditor.getContent(),
        experimentProcedure: this.$refs.stepsEditor.getContent(),
        dataAcquisition: this.$refs.dataAnalysisEditor.getContent(),
        resultEvaluation: this.$refs.evaluationEditor.getContent(),
        precautions: this.$refs.notesEditor.getContent()
      }
    },
    // 保存
    handleSave() {
    // 统一处理方法
    handleSubmit(type) {
      this.$refs.form.validate((valid) => {
        if (valid) {
          // 获取所有编辑器内容
          const editorContents = this.getAllEditorContent()
          // 检查编辑器内容是否为空
          const emptyFields = []
          console.log('editorContents', editorContents)
          // 判断内容是否为空(排除<p></p>)
          const isEmptyContent = (content) => {
            return !content || content === '<p></p>' || content.trim() === '<p></p>'
          }
          if (isEmptyContent(editorContents.experimentObjective)) emptyFields.push('实验目的')
          if (isEmptyContent(editorContents.experimentMaterial)) emptyFields.push('实验材料')
          if (isEmptyContent(editorContents.experimentDevice)) emptyFields.push('实验设备')
          if (isEmptyContent(editorContents.experimentTestMethod)) emptyFields.push('检测方法及开发')
          if (isEmptyContent(editorContents.experimentProcedure)) emptyFields.push('实验步骤')
          if (isEmptyContent(editorContents.dataAcquisition)) emptyFields.push('数据采集及分析')
          if (isEmptyContent(editorContents.resultEvaluation)) emptyFields.push('结果评估')
          // if (isEmptyContent(editorContents.precautions)) emptyFields.push('注意事项')
          if (emptyFields.length > 0) {
            this.$message.warning(`请填写${emptyFields.join('、')}`)
            return false
          }
          const formData = {
            ...this.form,
            ...this.getAllEditorContent()
            ...editorContents,
            auditStatus: type === 'draft' ? -1 : 1 // 草稿箱:-1, 审批中:1
          }
          console.log('保存数据:', formData)
          this.$message.success('保存成功')
          const request = this.id ? updateProposal : addProposal
          if (this.id) {
            formData.id = this.id
          }
          console.log('请求参数:', {
            formData
          })
          request(formData).then(res => {
            console.log('接口返回:', res)
            if (res.code === 200) {
              const successMsg = type === 'draft' ? '草稿保存成功' : (this.id ? '更新成功' : '保存成功')
              this.$message.success(successMsg)
              this.$router.back()
            } else {
              const errorMsg = type === 'draft' ? '草稿保存失败' : (this.id ? '更新失败' : '保存失败')
              this.$message.error(res.msg || errorMsg)
            }
          }).catch(err => {
            console.error('接口错误:', err)
            const errorMsg = type === 'draft' ? '草稿保存失败' : (this.id ? '更新失败' : '保存失败')
            this.$message.error(errorMsg)
          })
        } else {
          this.$message.warning('请填写必填项')
          return false
        }
      })
    },
    // 保存
    handleSave() {
      this.handleSubmit('save')
    },
    // 保存草稿
    handleSaveDraft() {
      const formData = {
        ...this.form,
        ...this.getAllEditorContent(),
        status: 'draft'
      }
      console.log('草稿数据:', formData)
      this.$message.success('草稿保存成功')
      this.handleSubmit('draft')
    },
    // 获取详情
    getDetail() {
      if (this.id) {
        console.log('获取详情参数:', { id: this.id })
        getDetailById({ id: this.id }).then(res => {
          console.log('详情接口返回:', res)
          if (res.code === 200 && res.data) {
            const data = res.data
            // 设置表单数据
            this.form = {
              projectName: data.projectName,
              projectStage: data.projectStage,
              projectCode: data.projectCode,
              auditStatus: data.auditStatus
            }
            // 设置编辑器内容
            this.editorContents = {
              experimentObjective: data.experimentObjective,
              experimentMaterial: data.experimentMaterial,
              experimentDevice: data.experimentDevice,
              experimentTestMethod: data.experimentTestMethod,
              experimentProcedure: data.experimentProcedure,
              dataAcquisition: data.dataAcquisition,
              resultEvaluation: data.resultEvaluation,
              precautions: data.precautions
            }
          }
        }).catch(err => {
          console.error('获取详情失败:', err)
        })
      }
    }
  },
  created() {
    // 从路由参数中获取id
    this.id = this.$route.query.id
    if (this.id) {
      this.getDetail()
    }
  }
};
</script>
laboratory/src/views/dataManagement/approvalPlan/components/approvalDialog.vue
@@ -25,14 +25,30 @@
              label-position="top"
              style="margin-top: 38px"
            >
              <el-form-item prop="name" label="项目课题方案名称">
                <el-input v-model="form.name" placeholder="请输入" />
              <el-form-item prop="projectName" label="项目课题方案名称">
                <el-input
                  v-model="form.projectName"
                  placeholder="请输入"
                  disabled
                />
              </el-form-item>
              <el-form-item prop="description" label="项目阶段">
                <el-input v-model="form.description" placeholder="请输入" />
              <el-form-item prop="projectStage" label="项目阶段">
                <el-select
                  v-model="form.projectStage"
                  placeholder="请选择"
                  disabled
                >
                  <el-option label="实验室开发阶段" :value="1" />
                  <el-option label="中式试验阶段" :value="2" />
                  <el-option label="生产验证试验阶段" :value="3" />
                </el-select>
              </el-form-item>
              <el-form-item prop="description" label="项目课题方案编号">
                <el-input v-model="form.description" placeholder="请输入" />
              <el-form-item prop="projectCode" label="项目课题方案编号">
                <el-input
                  v-model="form.projectCode"
                  placeholder="请输入"
                  disabled
                />
              </el-form-item>
            </el-form>
            <div class="header-title">
@@ -41,6 +57,12 @@
                <div>一 、实验目的</div>
              </div>
            </div>
            <AiEditor
              ref="purposeEditor"
              :value="form.experimentObjective"
              height="200px"
              :readOnly="true"
            />
            <div class="header-title">
              <div class="header-title-left">
                <img src="@/assets/public/headercard.png" />
@@ -50,57 +72,92 @@
            <div class="item-title">
              <span>1.实验材料</span>
            </div>
            <AiEditor
              ref="materialEditor"
              :value="form.experimentMaterial"
              height="200px"
              :readOnly="true"
            />
            <div class="item-title">
              <span>2.实验设备</span>
            </div>
            <AiEditor
              ref="equipmentEditor"
              :value="form.experimentDevice"
              height="200px"
              :readOnly="true"
            />
            <div class="header-title">
              <div class="header-title-left">
                <img src="@/assets/public/headercard.png" />
                <div>三 、检测方法及开发</div>
              </div>
            </div>
            <AiEditor
              ref="methodEditor"
              :value="form.experimentTestMethod"
              height="200px"
              :readOnly="true"
            />
            <div class="header-title">
              <div class="header-title-left">
                <img src="@/assets/public/headercard.png" />
                <div>四 、实验步骤</div>
              </div>
            </div>
            <AiEditor
              ref="stepsEditor"
              :value="form.experimentProcedure"
              height="200px"
              :readOnly="true"
            />
            <div class="header-title">
              <div class="header-title-left">
                <img src="@/assets/public/headercard.png" />
                <div>五 、数据采集及分析</div>
              </div>
            </div>
            <AiEditor
              ref="dataAnalysisEditor"
              :value="form.dataAcquisition"
              height="200px"
              :readOnly="true"
            />
            <div class="header-title">
              <div class="header-title-left">
                <img src="@/assets/public/headercard.png" />
                <div>六 、结果评估</div>
              </div>
            </div>
            <AiEditor
              ref="evaluationEditor"
              :value="form.resultEvaluation"
              height="200px"
              :readOnly="true"
            />
            <div class="header-title">
              <div class="header-title-left">
                <img src="@/assets/public/headercard.png" />
                <span>注意事项</span>
              </div>
            </div>
            <AiEditor
              ref="notesEditor"
              :value="form.precautions"
              height="200px"
              :readOnly="true"
            />
          </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"
          />
          <approval-process :processData="form.processData" />
        </div>
      </div>
    </div>
    <div class="approval-dialog-approve">
    <div class="approval-dialog-approve" v-if="type === 'approve'">
      <div class="status">
        <div class="status-title">审批结果</div>
        <div class="status-content">
@@ -122,12 +179,20 @@
      </div>
      <div class="remark">
        <div class="remark-title">审批意见</div>
        <el-input type="textarea" v-model="remark" placeholder="请输入审批意见"   />
        <el-input
          type="textarea"
          v-model="remark"
          placeholder="请输入审批意见"
        />
      </div>
    </div>
    <div slot="footer" class="dialog-footer">
      <el-button @click="handleClose">取 消</el-button>
      <el-button type="primary" @click="handleApprove" v-if="type === 'approve'"
      <el-button
        type="primary"
        style="margin-left: 20px"
        @click="handleApprove"
        v-if="type === 'approve'"
        >通过</el-button
      >
    </div>
@@ -135,12 +200,15 @@
</template>
<script>
import ApprovalProcess from '@/components/approvalProcess'
import ApprovalProcess from "@/components/approvalProcess";
import { getDetailById, audit } from "../service";
import AiEditor from "@/components/AiEditor";
export default {
  name: "ApprovalDialog",
  components: {
    ApprovalProcess
    ApprovalProcess,
    AiEditor,
  },
  props: {
    visible: {
@@ -159,17 +227,23 @@
  data() {
    return {
      form: {
        planName: "",
        planCode: "",
        stage: "",
        creator: "",
        projectName: "",
        projectCode: "",
        projectStage: "",
        experimentObjective: "",
        experimentMaterial: "",
        experimentDevice: "",
        experimentTestMethod: "",
        experimentProcedure: "",
        dataAcquisition: "",
        resultEvaluation: "",
        precautions: "",
        auditStatus: "",
        createTime: "",
        approvalComment: "",
        status: "pending",
        approver: "",
        approveTime: ""
        approveTime: "",
        processData: [],
      },
      radio1: 1,
      rules: {},
      status: "1",
      remark: "",
@@ -181,39 +255,112 @@
    },
  },
  watch: {
    data: {
    visible: {
      handler(val) {
        if (val) {
          this.form = { ...val };
        console.log("visible", val, "22", this.data);
        if (val && this.data && this.data.id) {
          this.getDetail(this.data.id);
        }
      },
      immediate: true,
    },
  },
  methods: {
    // 获取详情
    getDetail(id) {
      console.log("获取详情参数:", { id });
      getDetailById({ id })
        .then((res) => {
          console.log("获取详情:", res);
          if (res) {
            this.form = { ...res, processData: [] };
            // 组装流程数据
            let processData = [];
            // 提交节点
            processData.push({
              type: "primary",
              mode: "list",
              fields: [
                { label: "提交人:", value: res.updateBy || "" },
                { label: "提交时间:", value: res.createTime || "" },
              ],
            });
            if (res.auditStatus == 2 || res.auditStatus == 3) {
              processData.push({
                type:
                  res.auditStatus === 2
                    ? "primary"
                    : res.auditStatus === 3
                    ? "danger"
                    : "warning",
                mode: "list",
                fields: [
                  {
                    label: "审核结果:",
                    value:
                      res.auditStatus === 2
                        ? "通过"
                        : res.auditStatus === 3
                        ? "驳回"
                        : "待审批",
                  },
                  { label: "审批意见:", value: res.auditRemark || "" },
                  { label: "审核人:", value: res.auditPersonName || "" },
                  { label: "审核时间:", value: res.auditTime || "" },
                ],
              });
            }else{
              processData.push({
                type: "warning",
                mode: "list",
                fields: [
                  { label: "等待审核"},
                ],
              });
            }
            // 如有卡片模式,按前述结构 push
            this.$nextTick(() => {
              this.form.processData = processData;
            })
          }
        })
        .catch((err) => {
          console.error("获取详情失败:", err);
          this.$message.error("获取详情失败");
        });
    },
    handleClose() {
      this.$emit("update:visible", false);
      this.form.approvalComment = "";
      this.remark = "";
      this.status = "1";
    },
    handleApprove() {
      if (!this.form.approvalComment) {
      if (!this.remark) {
        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",
      });
      const params = {
        id: this.form.id,
        auditStatus: this.status === "1" ? 2 : 3, // 2-已通过,3-已驳回
        auditRemark: this.remark,
      };
      console.log('审批请求参数:', params);
      audit(params)
        .then((res) => {
          console.log('审批返回结果:', res);
          if (res.code === 200) {
            this.$message.success("审批成功");
            this.$emit("update:visible", false);
            this.$emit("success");
          } else {
            this.$message.error(res.msg || "审批失败");
          }
        })
        .catch((err) => {
          console.error("审批失败:", err);
          this.$message.error("审批失败");
        });
    },
  },
};
@@ -391,12 +538,20 @@
  }
}
.dialog-footer{
    align-items: center;
    display: flex;
    justify-content: center;
    button{
        width: 150px;
    }
.dialog-footer {
  align-items: center;
  display: flex;
  justify-content: center;
  button {
    width: 150px;
  }
}
</style>
.content-display {
  padding: 10px 20px;
  min-height: 100px;
  background: #f5f7fa;
  border-radius: 4px;
  margin: 10px 0;
}
</style>
laboratory/src/views/dataManagement/approvalPlan/list.vue
@@ -13,54 +13,42 @@
            <el-input v-model="form.createBy" 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-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.auditPersonName" 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-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
            class="drafts"
              :class="{active:currentType === 'draft'}"
              @click="handleTypeChange('draft')"
            >草稿箱</div>
            <div class="title" :class="{ active: currentType === 'list' }" @click="handleTypeChange('list')">
              项目课题方案列表
            </div>
            <div v-if="isProcessEngineer" class="drafts" :class="{ active: currentType === 'draft' }"
              @click="handleTypeChange('draft')">
              草稿箱
            </div>
          </div>
          <el-button @click="handleAddPlan" class="el-icon-plus" type="primary">
            新增项目课题方案</el-button
          >
          <el-button v-if="isProcessEngineer" @click="handleAddPlan" class="el-icon-plus" type="primary">
            新增项目课题方案</el-button>
        </div>
      </template>
      <template #table>
        <el-table-column
          prop="projectCode"
          label="项目课题方案编号"
        ></el-table-column>
        <el-table-column
          prop="projectName"
          label="项目课题方案名称"
        ></el-table-column>
        <el-table-column prop="stage" label="项目阶段"></el-table-column>
        <el-table-column prop="createBy" label="创建人"></el-table-column>
        <el-table-column prop="projectCode" label="项目课题方案编号"></el-table-column>
        <el-table-column prop="projectName" label="项目课题方案名称"></el-table-column>
        <el-table-column prop="projectStage" label="项目阶段">
          <template slot-scope="scope">
            {{ getProjectStageText(scope.row.projectStage) }}
          </template>
        </el-table-column>
        <el-table-column prop="updateBy" label="创建人"></el-table-column>
        <el-table-column prop="createTime" label="创建日期"></el-table-column>
        <el-table-column prop="auditStatus" label="审批状态">
          <template slot-scope="scope">
@@ -73,133 +61,130 @@
        <el-table-column prop="auditTime" label="审批时间"></el-table-column>
        <el-table-column label="操作" width="250">
          <template slot-scope="scope">
            <el-button
              v-if="scope.row.auditStatus === 1"
              type="text"
              @click="handleApprove(scope.row)"
              >审批</el-button
            >
            <el-button
              v-if="scope.row.auditStatus === 2"
              type="text"
              @click="handleRevokeApprove(scope.row)"
              >撤销审批</el-button
            >
            <el-button
              v-if="scope.row.auditStatus === 3"
              type="text"
              @click="handleEdit(scope.row)"
              >编辑</el-button
            >
            <el-button
              v-if="scope.row.auditStatus === 3"
              type="text"
              @click="handleDelete(scope.row)"
              >删除</el-button
            >
            <el-button type="text" @click="handleDetail(scope.row)"
              >详情</el-button
            >
            <!-- 工艺工程师的按钮 -->
            <template v-if="isProcessEngineer">
              <el-button v-if="scope.row.auditStatus === 1" type="text"
                @click="handleRevokeApprove(scope.row)">撤销审批</el-button>
              <el-button v-if="scope.row.auditStatus === 3" type="text" @click="handleEdit(scope.row)">编辑</el-button>
              <el-button v-if="scope.row.auditStatus === 3" type="text" @click="handleDelete(scope.row)">删除</el-button>
              <el-button v-if="scope.row.auditStatus !== 1" type="text" @click="handleDetail(scope.row)">详情</el-button>
            </template>
            <!-- 审批人和超级管理员的按钮 -->
            <template v-else>
              <el-button v-if="scope.row.auditStatus === 1" type="text" @click="handleApprove(scope.row)">审批</el-button>
              <el-button v-if="scope.row.auditStatus !== 1" type="text" @click="handleDetail(scope.row)">详情</el-button>
            </template>
          </template>
        </el-table-column>
      </template>
    </TableCustom>
    <!-- 审批弹窗 -->
    <approval-dialog
      :visible.sync="approvalDialogVisible"
      :type="approvalDialogType"
      :data="currentApprovalData"
      @approve="handleApproveSubmit"
      @reject="handleRejectSubmit"
    />
    <approval-dialog :visible.sync="approvalDialogVisible" :type="approvalDialogType" :data="currentApprovalData"
      @approve="handleApproveSubmit" @reject="handleRejectSubmit" />
    <ShowDelConfirm :title="changeStatusTitle" :tip="changeStatusTip" :show="changeStatus"
      @close="changeStatus = false" @confirm="handleChangeStatusConfirm" />
  </div>
</template>
<script>
import ApprovalDialog from './components/approvalDialog.vue'
import { getProposalList } from './service'
import ShowDelConfirm from "@/components/showDelConfirm/index.vue";
import ApprovalDialog from "./components/approvalDialog.vue";
import { getProposalList, upAndDown, deleteById } from "./service";
export default {
  name: "ProjectList",
  components: {
    ApprovalDialog
    ApprovalDialog,
    ShowDelConfirm
  },
  data() {
    return {
      currentType: 'list', // 当前显示类型:list-列表,draft-草稿箱
      currentType: "list", // 当前显示类型:list-列表,draft-草稿箱
      form: {
        projectName: "",
        projectCode: "",
        createBy: "",
        createTime: [],
        auditPersonName: "",
        auditStatus: 1,
        auditStatus: '',
        pageNum: 1,
        pageSize: 10,
        startTime: "",
        endTime: ""
        endTime: "",
      },
      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-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-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'
        }
          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-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: ''
        }
          planCode: "DRAFT-2024-002",
          planName: "实验室改造方案(草稿)",
          stage: "准备阶段",
          creator: "李四",
          createTime: "2024-03-15",
          status: "draft",
          approver: "",
          approveTime: "",
        },
      ],
      approvalDialogVisible: false,
      approvalDialogType: 'approve',
      approvalDialogType: "approve",
      currentApprovalData: null,
      // 确认弹窗相关数据
      changeStatus: false,
      changeStatusTitle: "",
      changeStatusTip: "",
      currentOperationType: "", // 当前操作类型:revoke-撤销审批,delete-删除
      currentOperationRow: null, // 当前操作的行数据
    };
  },
  computed: {
    isProcessEngineer() {
      const userInfo = JSON.parse(sessionStorage.getItem('userInfo') || '{}');
      return userInfo.roleType === 3; // 3是工艺工程师
    }
  },
  created() {
    this.getTableData();
@@ -212,12 +197,13 @@
        createBy: "",
        createTime: [],
        auditPersonName: "",
        auditStatus: this.currentType === 'draft' ? -1 : 1,
        auditStatus: this.currentType === "draft" ? -1 : '',
        pageNum: 1,
        pageSize: 10,
        startTime: "",
        endTime: ""
        endTime: "",
      };
      this.getTableData();
    },
    handleSearch() {
      if (this.form.createTime && this.form.createTime.length === 2) {
@@ -231,12 +217,12 @@
    },
    getStatusType(status) {
      const statusMap = {
        1: "warning",   // 审批中
        2: "success",   // 已通过
        3: "danger",    // 已驳回
        4: "info",      // 已撤销
        5: "info",      // 已封存
        "-1": "info",   // 草稿
        1: "warning", // 审批中
        2: "success", // 已通过
        3: "danger", // 已驳回
        4: "info", // 已撤销
        5: "info", // 已封存
        "-1": "info", // 草稿
      };
      return statusMap[status] || "info";
    },
@@ -251,6 +237,14 @@
      };
      return statusMap[status] || "未知";
    },
    getProjectStageText(stage) {
      const stageMap = {
        1: "实验室开发阶段",
        2: "中式试验阶段",
        3: "生产验证试验阶段"
      };
      return stageMap[stage] || "未知阶段";
    },
    handleAddPlan() {
      this.$router.push({
        path: "/dataManagement/addPlan",
@@ -258,43 +252,53 @@
    },
    handleApprove(row) {
      this.currentApprovalData = row;
      this.approvalDialogType = 'approve';
      this.approvalDialogType = "approve";
      this.approvalDialogVisible = true;
    },
    handleApproveSubmit(data) {
      // 处理审批通过
      console.log('审批通过:', data);
      console.log("审批通过:", data);
      this.approvalDialogVisible = false;
      this.$message.success('审批通过成功');
      this.$message.success("审批通过成功");
      this.getTableData();
    },
    handleRejectSubmit(data) {
      // 处理审批驳回
      console.log('审批驳回:', data);
      console.log("审批驳回:", data);
      this.approvalDialogVisible = false;
      this.$message.success('审批驳回成功');
      this.$message.success("审批驳回成功");
      this.getTableData();
    },
    handleRevokeApprove(row) {
      // 实现撤销审批逻辑
      console.log("撤销审批数据:", row);
      this.currentOperationType = "revoke";
      this.currentOperationRow = row;
      this.changeStatusTitle = "确认要撤销这条审批吗?";
      this.changeStatusTip = "撤销后,审批人将无法收到此条审批信息"; // 这里可以根据实际需求修改提示文案
      this.changeStatus = true;
    },
    handleEdit(row) {
      // 实现编辑逻辑
      console.log("编辑数据:", row);
      this.$router.push({
        path: "/dataManagement/addPlan",
        query: {
          id: row.id
        }
      });
    },
    handleDelete(row) {
      // 实现删除逻辑
      console.log("删除数据:", row);
      this.currentOperationType = "delete";
      this.currentOperationRow = row;
      this.changeStatusTitle = "确认要删除这条信息吗?";
      this.changeStatusTip = "删除后信息无法找回"; // 这里可以根据实际需求修改提示文案
      this.changeStatus = true;
    },
    handleDetail(row) {
      this.currentApprovalData = row;
      this.approvalDialogType = 'view';
      this.approvalDialogType = "view";
      this.approvalDialogVisible = true;
    },
    handleTypeChange(type) {
      this.currentType = type;
      this.form.auditStatus = type === 'draft' ? -1 : '';
      this.form.auditStatus = type === "draft" ? -1 : "";
      this.getTableData();
    },
    getTableData() {
@@ -305,15 +309,41 @@
        this.form.startTime = "";
        this.form.endTime = "";
      }
      // 创建新的请求参数对象,排除createTime
      const { createTime, ...requestParams } = this.form;
      console.log(requestParams,'requestParams')
      getProposalList(requestParams).then(res => {
      console.log(requestParams, "requestParams");
      getProposalList(requestParams).then((res) => {
        console.log(res, "请求回来的数据");
        this.tableData = res.data.records;
        this.total = res.data.total;
      });
    },
    handleChangeStatusConfirm() {
      if (this.currentOperationType === "revoke") {
        // 处理撤销审批逻辑
        upAndDown({
          id: this.currentOperationRow.id,
          status: 4 // 已撤销状态
        }).then(() => {
          this.$message.success("撤销审批成功");
          this.changeStatus = false;
          this.getTableData();
        }).catch(error => {
          this.$message.error("撤销审批失败");
        });
      } else if (this.currentOperationType === "delete") {
        // 处理删除逻辑
        deleteById({
          id: this.currentOperationRow.id
        }).then(() => {
          this.$message.success("删除成功");
          this.changeStatus = false;
          this.getTableData();
        }).catch(error => {
          this.$message.error("删除失败");
        });
      }
    },
  },
};
@@ -323,15 +353,18 @@
.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;
@@ -343,6 +376,7 @@
    width: unset;
    cursor: pointer;
  }
  .drafts {
    padding: 16px 65px;
    background: #fafafc;
@@ -354,7 +388,8 @@
    margin-left: 16px;
    cursor: pointer;
  }
  .active{
  .active {
    color: #049c9a;
    background: #ffffff;
    border-radius: 8px 8px 0px 0px;
laboratory/src/views/dataManagement/approvalPlan/service.js
@@ -23,14 +23,14 @@
//删除
export function deleteById(data) {
  return axios.delete('/open/t-project-proposal/deleteById', { ...data })
  return axios.delete('/open/t-project-proposal/deleteById', { params:data })
}
//批量删除
export function deleteByIds(data) {
  return axios.delete('/open/t-project-proposal/deleteByIds', { ...data })
  return axios.delete('/open/t-project-proposal/deleteByIds', { params:data })
}
//根据id查询方案详情
export function getDetailById(data) {
  return axios.get('/open/t-project-proposal/getDetailById', { ...data })
  return axios.get('/open/t-project-proposal/getDetailById', {params:data })
}
laboratory/src/views/login/index.vue
@@ -68,11 +68,20 @@
    // 添加窗口大小变化监听器
    window.addEventListener('resize', this.handleResize)
  },
  mounted() {
    document.addEventListener("keydown", this.handleKeyDown);
  },
  destroyed() {
    // 组件销毁时移除监听器
    window.removeEventListener('resize', this.handleResize)
    document.removeEventListener("keydown", this.handleKeyDown);
  },
  methods: {
    handleKeyDown(event) {
      if (event.key === 'Enter') {
        this.login()
      }
    },
    // 添加处理窗口大小变化的方法
    handleResize() {
      this.viewWidth = window.innerWidth
@@ -87,7 +96,7 @@
        return
      }
      loginReq(this.loginForm).then(res => {
        console.log('111111',res)
        console.log('111111', res)
        sessionStorage.setItem('token', res.token)
        sessionStorage.setItem('userInfo', JSON.stringify(res.userInfo.user))
        this.$router.push('/system')
laboratory/src/views/projectList/addProject.vue
@@ -31,7 +31,7 @@
                </div>
            </div>
            <div class="add-project-footer">
                <el-button type="primary">保存</el-button>
                <el-button @click="submitForm" type="primary">保存</el-button>
            </div>
        </template>
        <SelectMember ref="selectMember" @submit="selectUser" />
@@ -39,6 +39,7 @@
</template>
<script>
import { addProject } from './service'
export default {
    name: 'AddProject',
    data() {
@@ -60,7 +61,30 @@
        submitForm() {
            this.$refs.form.validate((valid) => {
                if (valid) {
                    console.log('submit!')
                    if (this.selectMemberData.length == 0) {
                        this.$message.error('请选择项目组成员')
                        return
                    }
                    const ROLE_NAME_TO_TYPE = {
                        '审批人': 2,
                        '工艺工程师': 3,
                        '实验员': 4,
                        '化验师': 5
                    };
                    const data = {
                        teamName: this.form.teamName,
                        personCharge: this.form.personCharge,
                        staffs: this.selectMemberData.map(member => ({
                            userId: member.userId,
                            roleType: ROLE_NAME_TO_TYPE[member.roleName]
                        }))
                    }
                    addProject(data).then(res => {
                        if (res.code == 200) {
                            this.$message.success('添加成功')
                            this.$router.push({ name: 'ProjectList' })
                        }
                    })
                }
            })
        },
laboratory/src/views/projectList/detailProject.vue
New file
@@ -0,0 +1,236 @@
<template>
    <Card>
        <template>
            <el-form disabled ref="form" :model="form" :rules="rules" inline label-position="top">
                <el-form-item prop="teamName" label="项目组名称">
                    <el-input v-model="form.teamName" placeholder="请输入" />
                </el-form-item>
                <el-form-item prop="personCharge" label="项目组负责人">
                    <el-input v-model="form.personCharge" placeholder="请输入" />
                </el-form-item>
            </el-form>
            <div class="header-title">
                <div class="header-title-left">
                    <img src="@/assets/public/headercard.png" />
                    <div>项目组成员</div>
                </div>
            </div>
            <div class="member-list">
                <div v-for="item in 4" :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' : 'member-name-box-2'">
                            <el-tooltip v-for="i in memberList(item)" :key="i.userId" class="member-name" effect="dark"
                                :content="i.nickName" placement="top">
                                <span>{{ i.nickName }}</span>
                            </el-tooltip>
                        </div>
                    </div>
                </div>
            </div>
        </template>
        <SelectMember ref="selectMember" @submit="selectUser" />
    </Card>
</template>
<script>
import { getProjectDetail } from './service'
export default {
    name: 'EddProject',
    data() {
        return {
            form: {},
            rules: {
                teamName: [{ required: true, message: '请输入项目组名称', trigger: 'blur' }],
                personCharge: [{ required: true, message: '请输入项目组描述', trigger: 'blur' }]
            },
            selectMemberData: [],
            ROLE_NAME_TO_TYPE: {
                '审批人': 2,
                '工艺工程师': 3,
                '实验员': 4,
                '化验师': 5
            }
        }
    },
    created() {
        getProjectDetail({ id: this.$route.query.id }).then(res => {
            this.form = {
                teamName: res.teamName,
                personCharge: res.personCharge
            }
            this.selectMemberData = res.staffs.map(item => ({
                userId: item.userId,
                roleName: ['审批人', '工艺工程师', '实验员', '化验师'][item.roleType - 2],
                nickName: item.nickName
            }))
        })
    },
    methods: {
        addMember() {
            this.$refs.selectMember.open()
        },
        memberList(i) {
            switch (i) {
                case 1:
                    return this.selectMemberData.filter(item => item.roleName == '审批人')
                case 2:
                    return this.selectMemberData.filter(item => item.roleName == '工艺工程师')
                case 3:
                    return this.selectMemberData.filter(item => item.roleName == '实验员')
                case 4:
                    return this.selectMemberData.filter(item => item.roleName == '化验师')
                default:
                    break;
            }
        },
    }
}
</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;
    .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;
            }
        }
    }
}
.member-list {
    margin-top: 18px;
    display: flex;
    flex-wrap: wrap;
    gap: 28px;
    .member-list-card {
        position: relative;
        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.20) 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;
            }
            .member-name-box {
                flex: 1;
                display: flex;
                align-items: center;
                justify-content: center;
            }
            .member-name-box-2 {
                padding: 0 20px;
                padding-top: 40px;
                display: grid;
                grid-template-columns: repeat(4, 1fr);
                align-items: flex-start;
                flex-wrap: wrap;
                gap: 20px;
                justify-content: center;
            }
            .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;
                overflow: hidden;
                text-overflow: ellipsis;
                white-space: nowrap;
                box-sizing: border-box;
            }
            .member-edit {
                cursor: pointer;
                position: absolute;
                bottom: 10px;
                left: 50%;
                transform: translateX(-50%);
                font-weight: 400;
                font-size: 12px;
                color: #FF4D4F;
                line-height: 22px;
                width: 40px;
                background: #FFF1F0;
                border-radius: 4px;
                border: 1px solid #FFCCC7;
                text-align: center;
            }
        }
    }
}
.add-project-footer {
    margin-top: 43px;
    button {
        width: 220px;
    }
}
</style>
laboratory/src/views/projectList/editProject.vue
New file
@@ -0,0 +1,288 @@
<template>
    <Card>
        <template>
            <el-form ref="form" :model="form" :rules="rules" inline label-position="top">
                <el-form-item prop="teamName" label="项目组名称">
                    <el-input v-model="form.teamName" placeholder="请输入" />
                </el-form-item>
                <el-form-item prop="personCharge" label="项目组负责人">
                    <el-input v-model="form.personCharge" placeholder="请输入" />
                </el-form-item>
            </el-form>
            <div class="header-title">
                <div class="header-title-left">
                    <img src="@/assets/public/headercard.png" />
                    <div>项目组成员</div>
                </div>
                <el-button class="el-icon-plus" type="primary" @click="addMember"> 添加项目组成员</el-button>
            </div>
            <div class="member-list">
                <div v-for="item in 4" :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' : 'member-name-box-2'">
                            <el-tooltip v-for="i in memberList(item)" :key="i.userId" class="member-name" effect="dark"
                                :content="i.nickName" placement="top">
                                <span>{{ i.nickName }}</span>
                            </el-tooltip>
                        </div>
                        <div class="member-edit" v-if="memberList(item).length != 0" @click="editUserList">修改</div>
                    </div>
                </div>
            </div>
            <div class="add-project-footer">
                <el-button @click="submitForm" type="primary">保存</el-button>
            </div>
        </template>
        <SelectMember ref="selectMember" @submit="selectUser" />
    </Card>
</template>
<script>
import { getProjectDetail, editProject } from './service'
export default {
    name: 'EddProject',
    data() {
        return {
            form: {},
            rules: {
                teamName: [{ required: true, message: '请输入项目组名称', trigger: 'blur' }],
                personCharge: [{ required: true, message: '请输入项目组描述', trigger: 'blur' }]
            },
            selectMemberData: [],
            // 角色配置常量
            ROLE_CONFIG: {
                1: { key: 'approver', limit: 1, label: '审批人' },
                2: { key: 'engineer', limit: 1, label: '工艺工程师' },
            },
            ROLE_NAME_TO_TYPE: {
                '审批人': 2,
                '工艺工程师': 3,
                '实验员': 4,
                '化验师': 5
            }
        }
    },
    created() {
        getProjectDetail({ id: this.$route.query.id }).then(res => {
            this.form = {
                teamName: res.teamName,
                personCharge: res.personCharge
            }
            this.selectMemberData = res.staffs.map(item => ({
                userId: item.userId,
                roleName: ['审批人', '工艺工程师', '实验员', '化验师'][item.roleType - 2],
                nickName: item.nickName
            }))
        })
    },
    methods: {
        submitForm() {
            this.$refs.form.validate((valid) => {
                if (valid) {
                    if (this.selectMemberData.length == 0) {
                        this.$message.error('请选择项目组成员')
                        return
                    }
                    const data = {
                        id: this.$route.query.id,
                        teamName: this.form.teamName,
                        personCharge: this.form.personCharge,
                        staffs: this.selectMemberData.map(member => ({
                            userId: member.userId,
                            roleType: this.ROLE_NAME_TO_TYPE[member.roleName]
                        }))
                    }
                    editProject(data).then(res => {
                        if (res.code == 200) {
                            this.$message.success('添加成功')
                            this.$router.push({ name: 'ProjectList' })
                        }
                    })
                }
            })
        },
        addMember() {
            this.$refs.selectMember.open()
        },
        memberList(i) {
            switch (i) {
                case 1:
                    return this.selectMemberData.filter(item => item.roleName == '审批人')
                case 2:
                    return this.selectMemberData.filter(item => item.roleName == '工艺工程师')
                case 3:
                    return this.selectMemberData.filter(item => item.roleName == '实验员')
                case 4:
                    return this.selectMemberData.filter(item => item.roleName == '化验师')
                default:
                    break;
            }
        },
        selectUser(data) {
            for (const [roleId, config] of Object.entries(this.ROLE_CONFIG)) {
                const members = data.filter(item => item.roleName === config.label);
                if (members.length > config.limit) {
                    this.$message.error(`${config.label}最多只能选择${config.limit}个`);
                    return
                }
            }
            this.selectMemberData = data;
            this.$refs.selectMember.close();
        },
        editUserList() {
            this.$refs.selectMember.open();
            this.$nextTick(() => {
                this.$refs.selectMember.setSelection(this.selectMemberData);
            });
        }
    }
}
</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;
    .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;
            }
        }
    }
}
.member-list {
    margin-top: 18px;
    display: flex;
    flex-wrap: wrap;
    gap: 28px;
    .member-list-card {
        position: relative;
        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.20) 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;
            }
            .member-name-box {
                flex: 1;
                display: flex;
                align-items: center;
                justify-content: center;
            }
            .member-name-box-2 {
                padding: 0 20px;
                padding-top: 40px;
                display: grid;
                grid-template-columns: repeat(4, 1fr);
                align-items: flex-start;
                flex-wrap: wrap;
                gap: 20px;
                justify-content: center;
            }
            .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;
                overflow: hidden;
                text-overflow: ellipsis;
                white-space: nowrap;
                box-sizing: border-box;
            }
            .member-edit {
                cursor: pointer;
                position: absolute;
                bottom: 10px;
                left: 50%;
                transform: translateX(-50%);
                font-weight: 400;
                font-size: 12px;
                color: #FF4D4F;
                line-height: 22px;
                width: 40px;
                background: #FFF1F0;
                border-radius: 4px;
                border: 1px solid #FFCCC7;
                text-align: center;
            }
        }
    }
}
.add-project-footer {
    margin-top: 43px;
    button {
        width: 220px;
    }
}
</style>
laboratory/src/views/projectList/index.vue
@@ -22,26 +22,26 @@
                </el-form>
            </template>
            <template #setting>
                <el-button @click="handleAddProject" class="el-icon-plus" type="primary">
                <el-button @click="handleProject('add')" class="el-icon-plus" type="primary">
                    新增项目组</el-button>
            </template>
            <template #table>
                <el-table-column prop="teamName" label="项目组名称" />
                <el-table-column prop="personCharge" label="项目负责人" />
                <el-table-column prop="staffName" label="项目组成员" />
                <el-table-column prop="age" label="项目创建时间" />
                <el-table-column prop="age" label="状态">
                <el-table-column prop="createTime" label="项目创建时间" />
                <el-table-column prop="status" label="状态">
                    <template #default="{ row }">
                        <el-tag v-if="row.status == 1" type="success">正常运作</el-tag>
                        <el-tag v-else type="danger">已封存</el-tag>
                    </template>
                </el-table-column>
                <el-table-column prop="age" label="操作">
                <el-table-column label="操作">
                    <template #default="{ row }">
                        <el-button type="text" @click="handleChangeStatus(row, 1)">封存</el-button>
                        <el-button type="text" @click="handleChangeStatus(row, 0)">解封</el-button>
                        <el-button type="text">编辑</el-button>
                        <el-button type="text">详情</el-button>
                        <el-button v-if="row.status == 1" type="text" @click="handleChangeStatus(row, 2)">封存</el-button>
                        <el-button v-if="row.status == 2" type="text" @click="handleChangeStatus(row, 1)">解封</el-button>
                        <el-button type="text" @click="handleProject('edit', row.id)">编辑</el-button>
                        <el-button type="text" @click="handleProject('detail', row.id)">详情</el-button>
                        <el-button type="text" @click="handleDel(row)">删除</el-button>
                    </template>
                </el-table-column>
@@ -54,7 +54,7 @@
</template>
<script>
import { getProjectList } from './service'
import { getProjectList, changeStatus, deleteProject } from './service'
import moment from 'moment'
export default {
    name: 'ProjectList',
@@ -62,6 +62,7 @@
        return {
            showDelConfirm: false,
            rowId: '',
            status: null,
            changeStatus: false,
            changeStatusTitle: '',
            changeStatusTip: '',
@@ -77,34 +78,60 @@
        this.getList()
    },
    methods: {
        handleAddProject() {
            this.$router.push({
                path: '/projectList/addProject'
            })
        handleProject(type, id) {
            if (type == 'add') {
                this.$router.push({
                    path: '/projectList/addProject'
                })
                return
            }
            if (type == 'edit') {
                this.$router.push({
                    path: '/projectList/editProject',
                    query: {
                        id
                    }
                })
                return
            }
            if (type == 'detail') {
                this.$router.push({
                    path: '/projectList/detailProject',
                    query: {
                        id
                    }
                })
                return
            }
        },
        handleDel(row) {
            this.rowId = row.id
            this.showDelConfirm = true
        },
        handleDelConfirm() {
            this.showDelConfirm = false
            this.msgsuccess('删除成功')
            this.rowId = ''
            this.getList()
            deleteProject({ id: this.rowId }).then(res => {
                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.status = status
            this.changeStatusTitle = status == 2 ? '确认要封存这个项目组吗?' : '确认要解封该项目组吗?'
            this.changeStatusTip = status == 2 ? '封存后项目组内人员看不到数据,审批人仍然可见数据。' : '解封后项目组内人员数据恢复。'
            this.changeStatus = true
        },
        handleChangeStatusConfirm() {
            this.changeStatus = false
            this.msgsuccess('操作成功')
            this.rowId = ''
            this.changeStatusTitle = ''
            this.changeStatusTip = ''
            this.getList()
            changeStatus({ id: this.rowId, status: this.status }).then(res => {
                this.changeStatus = false
                this.msgsuccess('操作成功')
                this.rowId = ''
                this.changeStatusTitle = ''
                this.changeStatusTip = ''
                this.getList()
            })
        },
        handleCurrentChange(page) {
            this.queryForm.pageNum = page
laboratory/src/views/projectList/service.js
@@ -3,4 +3,29 @@
// 列表
export const getProjectList = (data) => {
    return axios.post('/api/t-project-team/pageList', { ...data })
}
}
// 新增
export const addProject = (data) => {
    return axios.post('/api/t-project-team/add', { ...data })
}
// 编辑
export const editProject = (data) => {
    return axios.post('/api/t-project-team/update', { ...data })
}
// 详情
export const getProjectDetail = (data) => {
    return axios.get(`/open/t-project-team/getDetailById?id=${data.id}`)
}
// 修改项目组状态
export const changeStatus = (data) => {
    return axios.post('/api/t-project-team/upAndDown', { ...data })
}
// 删除项目组
export const deleteProject = (data) => {
    return axios.delete(`/open/t-project-team/deleteById?id=${data.id}`)
}