huminmin
2026-06-04 50e637f1ab3c6eb6ec29b4968de70a0b6d4f4224
Merge branch 'dev_天津_阳光印刷' of http://114.132.189.42:9002/r/product-inventory-management into dev_天津_阳光印刷
已修改10个文件
7974 ■■■■ 文件已修改
src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue 844 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue 632 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue 622 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/summary/index.vue 624 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/workOrder/index.vue 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/components/formDia.vue 872 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/index.vue 1293 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricBinding/index.vue 1133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricMaintenance/StandardFormDialog.vue 215 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricMaintenance/index.vue 1680 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue
@@ -1,18 +1,17 @@
<template>
  <div>
    <!-- 申请类型选择 -->
    <el-card class="type-card">
      <div class="type-selector">
        <div
            v-for="type in applicationTypes"
            :key="type.value"
            class="type-item"
            :class="{ active: currentType === type.value }"
            @click="changeType(type.value)"
        >
        <div v-for="type in applicationTypes"
             :key="type.value"
             class="type-item"
             :class="{ active: currentType === type.value }"
             @click="changeType(type.value)">
          <div class="type-icon">
            <el-icon :size="24"><component :is="type.icon"/></el-icon>
            <el-icon :size="24">
              <component :is="type.icon" />
            </el-icon>
          </div>
          <div class="type-info">
            <div class="type-name">{{ type.name }}</div>
@@ -21,491 +20,512 @@
        </div>
      </div>
    </el-card>
    <!-- 会议申请表单 -->
    <el-card>
      <div class="form-header">
        <h3>{{ getCurrentTypeName() }}申请</h3>
      </div>
      <el-form
          ref="meetingFormRef"
          :model="meetingForm"
          :rules="rules"
          label-width="100px"
      >
      <el-form ref="meetingFormRef"
               :model="meetingForm"
               :rules="rules"
               label-width="100px">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="会议主题" prop="title">
              <el-input v-model="meetingForm.title" placeholder="请输入会议主题"/>
            <el-form-item label="会议主题"
                          prop="title">
              <el-input v-model="meetingForm.title"
                        placeholder="请输入会议主题" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="会议室" prop="roomId">
              <el-select v-model="meetingForm.roomId" placeholder="请选择会议室" style="width: 100%">
                <el-option
                    v-for="room in meetingRooms"
                    :key="room.id"
                    :label="`${room.name} (${room.location})`"
                    :value="room.id"
                />
            <el-form-item label="会议室"
                          prop="roomId">
              <el-select v-model="meetingForm.roomId"
                         placeholder="请选择会议室"
                         style="width: 100%">
                <el-option v-for="room in meetingRooms"
                           :key="room.id"
                           :label="`${room.name} (${room.location})`"
                           :value="room.id" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="主持人" prop="host">
              <el-input v-model="meetingForm.host" placeholder="请输入主持人姓名"/>
            <el-form-item label="主持人"
                          prop="host">
              <el-input v-model="meetingForm.host"
                        placeholder="请输入主持人姓名" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="会议日期" prop="meetingDate">
              <el-date-picker
                  v-model="meetingForm.meetingDate"
                  type="date"
                  placeholder="请选择会议日期"
                  value-format="YYYY-MM-DD"
                  format="YYYY-MM-DD"
                  :disabled-date="disabledDate"
                  style="width: 100%"
              />
            <el-form-item label="会议日期"
                          prop="meetingDate">
              <el-date-picker v-model="meetingForm.meetingDate"
                              type="date"
                              placeholder="请选择会议日期"
                              value-format="YYYY-MM-DD"
                              format="YYYY-MM-DD"
                              :disabled-date="disabledDate"
                              style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <!-- 空列,保持布局 -->
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="开始时间" prop="startTime">
              <el-select
                  v-model="meetingForm.startTime"
                  placeholder="请选择开始时间"
                  style="width: 100%"
              >
                <el-option
                    v-for="time in startTimeOptions"
                    :key="time.value"
                    :label="time.label"
                    :value="time.value"
                />
            <el-form-item label="开始时间"
                          prop="startTime">
              <el-select v-model="meetingForm.startTime"
                         placeholder="请选择开始时间"
                         style="width: 100%">
                <el-option v-for="time in startTimeOptions"
                           :key="time.value"
                           :label="time.label"
                           :value="time.value" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="结束时间" prop="endTime">
              <el-select
                  v-model="meetingForm.endTime"
                  placeholder="请选择结束时间"
                  style="width: 100%"
              >
                <el-option
                    v-for="time in endTimeOptions"
                    :key="time.value"
                    :label="time.label"
                    :value="time.value"
                />
            <el-form-item label="结束时间"
                          prop="endTime">
              <el-select v-model="meetingForm.endTime"
                         placeholder="请选择结束时间"
                         style="width: 100%">
                <el-option v-for="time in endTimeOptions"
                           :key="time.value"
                           :label="time.label"
                           :value="time.value" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item label="参会人员" prop="participants">
          <el-select
              v-model="meetingForm.participants"
              multiple
              filterable
              placeholder="请选择参会人员"
              style="width: 100%"
          >
            <el-option
                v-for="person in employees"
                :key="person.id"
                :label="`${person.staffName}${person.postName ? ` (${person.postName})` : ''}`"
                :value="person.id"
            />
        <el-form-item label="参会人员"
                      prop="participants">
          <el-select v-model="meetingForm.participants"
                     multiple
                     filterable
                     placeholder="请选择参会人员"
                     style="width: 100%">
            <el-option v-for="user in users"
                       :key="user.userId"
                       :label="user.deptNames ? `${user.nickName} (${user.deptNames})` : user.nickName"
                       :value="user.userId" />
          </el-select>
        </el-form-item>
        <el-form-item label="会议说明" prop="description">
          <el-input
              v-model="meetingForm.description"
              type="textarea"
              :rows="4"
              placeholder="请输入会议说明"
          />
        <el-form-item label="会议说明"
                      prop="description">
          <el-input v-model="meetingForm.description"
                    type="textarea"
                    :rows="4"
                    placeholder="请输入会议说明" />
        </el-form-item>
      </el-form>
      <div class="form-footer">
        <el-button @click="resetForm">重置</el-button>
        <el-button type="primary" @click="submitForm">提交</el-button>
        <el-button type="primary"
                   @click="submitForm">提交</el-button>
      </div>
    </el-card>
  </div>
</template>
<script setup>
import {ref, reactive, onMounted, computed, watch} from 'vue'
import {ElMessage} from 'element-plus'
import {Plus, Document, Promotion, Bell} from '@element-plus/icons-vue'
import {getRoomEnum, saveMeetingApplication} from '@/api/collaborativeApproval/meeting.js'
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
  import { ref, reactive, onMounted, computed, watch } from "vue";
  import { ElMessage } from "element-plus";
  import { Plus, Document, Promotion, Bell } from "@element-plus/icons-vue";
  import {
    getRoomEnum,
    saveMeetingApplication,
  } from "@/api/collaborativeApproval/meeting.js";
  import { userListNoPageByTenantId } from "@/api/system/user.js";
// 当前申请类型
const currentType = ref('department') // approval: 审批流程, department: 部门级, notification: 通知发布
  // 当前申请类型
  const currentType = ref("department"); // approval: 审批流程, department: 部门级, notification: 通知发布
// 申请类型选项
const applicationTypes = ref([
  {
    value: 'approval',
    name: '审批流程会议',
    desc: '需要经过多级审批的会议申请',
    icon: Document
  },
  {
    value: 'department',
    name: '部门级会议',
    desc: '部门内部会议申请流程',
    icon: Promotion
  },
  {
    value: 'notification',
    name: '会议通知',
    desc: '无需审批直接发布的会议通知',
    icon: Bell
  }
])
  // 申请类型选项
  const applicationTypes = ref([
    {
      value: "approval",
      name: "审批流程会议",
      desc: "需要经过多级审批的会议申请",
      icon: Document,
    },
    {
      value: "department",
      name: "部门级会议",
      desc: "部门内部会议申请流程",
      icon: Promotion,
    },
    {
      value: "notification",
      name: "会议通知",
      desc: "无需审批直接发布的会议通知",
      icon: Bell,
    },
  ]);
// 表单数据
const meetingForm = reactive({
  title: '',
  type: '',
  roomId: '',
  host: '',
  meetingDate: '',
  startTime: '',
  endTime: '',
  participants: [],
  description: ''
})
  // 表单数据
  const meetingForm = reactive({
    title: "",
    type: "",
    roomId: "",
    host: "",
    meetingDate: "",
    startTime: "",
    endTime: "",
    participants: [],
    description: "",
  });
// 表单引用
const meetingFormRef = ref(null)
  // 表单引用
  const meetingFormRef = ref(null);
// 会议室列表
const meetingRooms = ref([])
  // 会议室列表
  const meetingRooms = ref([]);
// 员工列表
const employees = ref([])
  // 用户列表(系统管理-用户管理)
  const users = ref([]);
// 时间选项(以半小时为间隔)
const timeOptions = ref([])
  // 时间选项(以半小时为间隔)
  const timeOptions = ref([]);
const getTimeInMinutes = (time) => {
  if (!time) return -1
  const [hour, minute] = time.split(':').map(Number)
  return hour * 60 + minute
}
  const getTimeInMinutes = time => {
    if (!time) return -1;
    const [hour, minute] = time.split(":").map(Number);
    return hour * 60 + minute;
  };
const isToday = (dateText) => {
  if (!dateText) return false
  const [year, month, day] = dateText.split('-').map(Number)
  const now = new Date()
  return year === now.getFullYear() && month === now.getMonth() + 1 && day === now.getDate()
}
  const isToday = dateText => {
    if (!dateText) return false;
    const [year, month, day] = dateText.split("-").map(Number);
    const now = new Date();
    return (
      year === now.getFullYear() &&
      month === now.getMonth() + 1 &&
      day === now.getDate()
    );
  };
const validateStartTime = (_rule, value, callback) => {
  if (!value) {
    callback()
    return
  }
  if (isToday(meetingForm.meetingDate)) {
    const now = new Date()
    const currentMinutes = now.getHours() * 60 + now.getMinutes()
    if (getTimeInMinutes(value) > currentMinutes) {
      callback(new Error('当天开始时间不能晚于当前时间'))
      return
  const validateStartTime = (_rule, value, callback) => {
    if (!value) {
      callback();
      return;
    }
  }
  callback()
}
const validateEndTime = (_rule, value, callback) => {
  if (!value || !meetingForm.startTime) {
    callback()
    return
  }
  if (getTimeInMinutes(value) <= getTimeInMinutes(meetingForm.startTime)) {
    callback(new Error('结束时间必须大于开始时间'))
    return
  }
  callback()
}
// 表单校验规则
const rules = {
  title: [{required: true, message: '请输入会议主题', trigger: 'blur'}],
  roomId: [{required: true, message: '请选择会议室', trigger: 'change'}],
  host: [{required: true, message: '请输入主持人', trigger: 'blur'}],
  meetingDate: [{required: true, message: '请选择会议日期', trigger: 'change'}],
  startTime: [
    {required: true, message: '请选择开始时间', trigger: 'change'},
    {validator: validateStartTime, trigger: 'change'}
  ],
  endTime: [
    {required: true, message: '请选择结束时间', trigger: 'change'},
    {validator: validateEndTime, trigger: 'change'}
  ],
  participants: [{required: true, message: '请选择参会人员', trigger: 'change'}]
}
const startTimeOptions = computed(() => {
  if (!isToday(meetingForm.meetingDate)) {
    return timeOptions.value
  }
  const now = new Date()
  const currentMinutes = now.getHours() * 60 + now.getMinutes()
  return timeOptions.value.filter(item => getTimeInMinutes(item.value) <= currentMinutes)
})
const endTimeOptions = computed(() => {
  if (!meetingForm.startTime) {
    return timeOptions.value
  }
  const startMinutes = getTimeInMinutes(meetingForm.startTime)
  return timeOptions.value.filter(item => getTimeInMinutes(item.value) > startMinutes)
})
// 初始化时间选项
const initTimeOptions = () => {
  const options = []
  const now = new Date()
  const currentHour = now.getHours()
  const currentMinute = now.getMinutes()
  // meetingDate 是 "yyyy-MM-dd"
  const meetingDate = new Date(meetingForm.meetingDate)
  const isSameDay =
    now.getFullYear() === meetingDate.getFullYear() &&
    now.getMonth() === meetingDate.getMonth() &&
    now.getDate() === meetingDate.getDate()
  console.log('是否同一天:', isSameDay)
  for (let hour = 8; hour <= 18; hour++) {
    // 开始时间必须晚于当前时间
    if (hour < currentHour && isSameDay) {
      continue
    if (isToday(meetingForm.meetingDate)) {
      const now = new Date();
      const currentMinutes = now.getHours() * 60 + now.getMinutes();
      if (getTimeInMinutes(value) > currentMinutes) {
        callback(new Error("当天开始时间不能晚于当前时间"));
        return;
      }
    }
    if (hour === currentHour && currentMinute > 30 && isSameDay) {
      continue
    }
    // 每个小时添加两个选项:整点和半点
    options.push({
      value: `${hour.toString().padStart(2, '0')}:00`,
      label: `${hour.toString().padStart(2, '0')}:00`
    })
    if (hour < 18) { // 18:00之后没有半点选项
    callback();
  };
  const validateEndTime = (_rule, value, callback) => {
    if (!value || !meetingForm.startTime) {
      callback();
      return;
    }
    if (getTimeInMinutes(value) <= getTimeInMinutes(meetingForm.startTime)) {
      callback(new Error("结束时间必须大于开始时间"));
      return;
    }
    callback();
  };
  // 表单校验规则
  const rules = {
    title: [{ required: true, message: "请输入会议主题", trigger: "blur" }],
    roomId: [{ required: true, message: "请选择会议室", trigger: "change" }],
    host: [{ required: true, message: "请输入主持人", trigger: "blur" }],
    meetingDate: [
      { required: true, message: "请选择会议日期", trigger: "change" },
    ],
    startTime: [
      { required: true, message: "请选择开始时间", trigger: "change" },
      { validator: validateStartTime, trigger: "change" },
    ],
    endTime: [
      { required: true, message: "请选择结束时间", trigger: "change" },
      { validator: validateEndTime, trigger: "change" },
    ],
    participants: [
      { required: true, message: "请选择参会人员", trigger: "change" },
    ],
  };
  const startTimeOptions = computed(() => {
    if (!isToday(meetingForm.meetingDate)) {
      return timeOptions.value;
    }
    const now = new Date();
    const currentMinutes = now.getHours() * 60 + now.getMinutes();
    return timeOptions.value.filter(
      item => getTimeInMinutes(item.value) <= currentMinutes
    );
  });
  const endTimeOptions = computed(() => {
    if (!meetingForm.startTime) {
      return timeOptions.value;
    }
    const startMinutes = getTimeInMinutes(meetingForm.startTime);
    return timeOptions.value.filter(
      item => getTimeInMinutes(item.value) > startMinutes
    );
  });
  // 初始化时间选项
  const initTimeOptions = () => {
    const options = [];
    const now = new Date();
    const currentHour = now.getHours();
    const currentMinute = now.getMinutes();
    // meetingDate 是 "yyyy-MM-dd"
    const meetingDate = new Date(meetingForm.meetingDate);
    const isSameDay =
      now.getFullYear() === meetingDate.getFullYear() &&
      now.getMonth() === meetingDate.getMonth() &&
      now.getDate() === meetingDate.getDate();
    console.log("是否同一天:", isSameDay);
    for (let hour = 8; hour <= 18; hour++) {
      // 开始时间必须晚于当前时间
      if (hour < currentHour && isSameDay) {
        continue;
      }
      if (hour === currentHour && currentMinute > 30 && isSameDay) {
        continue;
      }
      // 每个小时添加两个选项:整点和半点
      options.push({
        value: `${hour.toString().padStart(2, '0')}:30`,
        label: `${hour.toString().padStart(2, '0')}:30`
      })
        value: `${hour.toString().padStart(2, "0")}:00`,
        label: `${hour.toString().padStart(2, "0")}:00`,
      });
      if (hour < 18) {
        // 18:00之后没有半点选项
        options.push({
          value: `${hour.toString().padStart(2, "0")}:30`,
          label: `${hour.toString().padStart(2, "0")}:30`,
        });
      }
    }
  }
  timeOptions.value = options
}
    timeOptions.value = options;
  };
watch(() => meetingForm.meetingDate, () => {
  if (meetingForm.startTime && !startTimeOptions.value.some(item => item.value === meetingForm.startTime)) {
    meetingForm.startTime = ''
  }
  if (meetingForm.endTime && !endTimeOptions.value.some(item => item.value === meetingForm.endTime)) {
    meetingForm.endTime = ''
  }
  if (meetingForm.startTime) {
    meetingFormRef.value?.validateField('startTime')
  }
  if (meetingForm.endTime) {
    meetingFormRef.value?.validateField('endTime')
  }
  initTimeOptions()
})
watch(() => meetingForm.startTime, () => {
  if (meetingForm.endTime && getTimeInMinutes(meetingForm.endTime) <= getTimeInMinutes(meetingForm.startTime)) {
    meetingForm.endTime = ''
  }
  if (meetingForm.endTime) {
    meetingFormRef.value?.validateField('endTime')
  }
})
// 禁用日期(禁用今天之前的日期)
const disabledDate = (time) => {
  // 禁用今天之前的日期
  return time.getTime() < Date.now() - 86400000
}
// 切换申请类型
const changeType = (type) => {
  currentType.value = type
}
// 获取当前类型名称
const getCurrentTypeName = () => {
  const type = applicationTypes.value.find(t => t.value === currentType.value)
  return type ? type.name : ''
}
// 重置表单
const resetForm = () => {
  meetingFormRef.value?.resetFields()
}
// 提交表单
const submitForm = () => {
  meetingFormRef.value?.validate((valid) => {
    if (valid) {
      let formData = {...meetingForm}
      formData.applicationType = currentType.value
      formData.startTime = `${meetingForm.meetingDate} ${meetingForm.startTime}:00`
      formData.endTime = `${meetingForm.meetingDate} ${meetingForm.endTime}:00`
      formData.participants = JSON.stringify(formData.participants)
      console.log(formData)
      saveMeetingApplication(formData).then(() => {
        // 模拟提交操作
        ElMessage.success(`${getCurrentTypeName()}提交成功`)
        // 根据不同类型执行不同操作
        switch (currentType.value) {
          case 'approval':
            ElMessage.info('会议已提交审批流程')
            break
          case 'department':
            ElMessage.info('部门级会议申请已提交')
            break
          case 'notification':
            ElMessage.info('会议通知已发布')
            break
        }
        resetForm()
      })
  watch(
    () => meetingForm.meetingDate,
    () => {
      if (
        meetingForm.startTime &&
        !startTimeOptions.value.some(item => item.value === meetingForm.startTime)
      ) {
        meetingForm.startTime = "";
      }
      if (
        meetingForm.endTime &&
        !endTimeOptions.value.some(item => item.value === meetingForm.endTime)
      ) {
        meetingForm.endTime = "";
      }
      if (meetingForm.startTime) {
        meetingFormRef.value?.validateField("startTime");
      }
      if (meetingForm.endTime) {
        meetingFormRef.value?.validateField("endTime");
      }
      initTimeOptions();
    }
  })
}
  );
// 页面加载时初始化
onMounted(() => {
  initTimeOptions()
  getRoomEnum().then(res => {
    meetingRooms.value = res.data
  })
  staffOnJobListPage({
    current: -1,
    size: -1,
    staffState: 1
  }).then(res => {
    employees.value = res.data.records.sort((a, b) => (a.postName || '').localeCompare(b.postName || ''))
  })
})
  watch(
    () => meetingForm.startTime,
    () => {
      if (
        meetingForm.endTime &&
        getTimeInMinutes(meetingForm.endTime) <=
          getTimeInMinutes(meetingForm.startTime)
      ) {
        meetingForm.endTime = "";
      }
      if (meetingForm.endTime) {
        meetingFormRef.value?.validateField("endTime");
      }
    }
  );
  // 禁用日期(禁用今天之前的日期)
  const disabledDate = time => {
    // 禁用今天之前的日期
    return time.getTime() < Date.now() - 86400000;
  };
  // 切换申请类型
  const changeType = type => {
    currentType.value = type;
  };
  // 获取当前类型名称
  const getCurrentTypeName = () => {
    const type = applicationTypes.value.find(t => t.value === currentType.value);
    return type ? type.name : "";
  };
  // 重置表单
  const resetForm = () => {
    meetingFormRef.value?.resetFields();
  };
  // 提交表单
  const submitForm = () => {
    meetingFormRef.value?.validate(valid => {
      if (valid) {
        let formData = { ...meetingForm };
        formData.applicationType = currentType.value;
        formData.startTime = `${meetingForm.meetingDate} ${meetingForm.startTime}:00`;
        formData.endTime = `${meetingForm.meetingDate} ${meetingForm.endTime}:00`;
        formData.participants = JSON.stringify(formData.participants);
        console.log(formData);
        saveMeetingApplication(formData).then(() => {
          // 模拟提交操作
          ElMessage.success(`${getCurrentTypeName()}提交成功`);
          // 根据不同类型执行不同操作
          switch (currentType.value) {
            case "approval":
              ElMessage.info("会议已提交审批流程");
              break;
            case "department":
              ElMessage.info("部门级会议申请已提交");
              break;
            case "notification":
              ElMessage.info("会议通知已发布");
              break;
          }
          resetForm();
        });
      }
    });
  };
  // 页面加载时初始化
  onMounted(() => {
    initTimeOptions();
    getRoomEnum().then(res => {
      meetingRooms.value = res.data;
    });
    userListNoPageByTenantId().then(res => {
      const list = Array.isArray(res?.data) ? res.data : [];
      users.value = list
        .map(item => ({
          userId: item?.userId,
          nickName:
            item?.nickName || item?.userName || String(item?.userId ?? ""),
          deptNames: item?.deptNames || "",
        }))
        .filter(
          item =>
            item.userId !== null && item.userId !== undefined && item.nickName
        )
        .sort((a, b) => String(a.nickName).localeCompare(String(b.nickName)));
    });
  });
</script>
<style scoped>
.app-container {
  padding: 20px;
}
  .app-container {
    padding: 20px;
  }
.page-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}
  .page-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
  }
.page-header h2 {
  margin: 0;
  color: #303133;
}
  .page-header h2 {
    margin: 0;
    color: #303133;
  }
.type-card {
  margin-bottom: 20px;
}
  .type-card {
    margin-bottom: 20px;
  }
.type-selector {
  display: flex;
  gap: 20px;
}
  .type-selector {
    display: flex;
    gap: 20px;
  }
.type-item {
  flex: 1;
  display: flex;
  align-items: center;
  padding: 20px;
  border: 1px solid #ebeef5;
  border-radius: 8px;
  cursor: pointer;
  transition: all 0.3s;
}
  .type-item {
    flex: 1;
    display: flex;
    align-items: center;
    padding: 20px;
    border: 1px solid #ebeef5;
    border-radius: 8px;
    cursor: pointer;
    transition: all 0.3s;
  }
.type-item:hover {
  border-color: #409eff;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
  .type-item:hover {
    border-color: #409eff;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  }
.type-item.active {
  border-color: #409eff;
  background-color: #ecf5ff;
}
  .type-item.active {
    border-color: #409eff;
    background-color: #ecf5ff;
  }
.type-icon {
  margin-right: 15px;
  color: #409eff;
}
  .type-icon {
    margin-right: 15px;
    color: #409eff;
  }
.type-name {
  font-size: 16px;
  font-weight: 500;
  color: #303133;
  margin-bottom: 5px;
}
  .type-name {
    font-size: 16px;
    font-weight: 500;
    color: #303133;
    margin-bottom: 5px;
  }
.type-desc {
  font-size: 14px;
  color: #909399;
}
  .type-desc {
    font-size: 14px;
    color: #909399;
  }
.form-header {
  margin-bottom: 20px;
  padding-bottom: 15px;
  border-bottom: 1px solid #ebeef5;
}
  .form-header {
    margin-bottom: 20px;
    padding-bottom: 15px;
    border-bottom: 1px solid #ebeef5;
  }
.form-header h3 {
  margin: 0;
  color: #303133;
}
  .form-header h3 {
    margin: 0;
    color: #303133;
  }
.form-footer {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 30px;
  padding-top: 20px;
  border-top: 1px solid #ebeef5;
}
  .form-footer {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    margin-top: 30px;
    padding-top: 20px;
    border-top: 1px solid #ebeef5;
  }
</style>
src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue
@@ -1,182 +1,219 @@
<template>
  <div>
    <el-form :model="searchForm" inline>
        <el-form-item label="会议主题">
          <el-input v-model="searchForm.title" placeholder="请输入会议主题" clearable/>
        </el-form-item>
        <el-form-item label="申请人">
          <el-input v-model="searchForm.applicant" placeholder="请输入申请人" clearable/>
        </el-form-item>
        <el-form-item label="审批状态">
          <el-select style="width: 100px" v-model="searchForm.status" placeholder="请选择审批状态" clearable>
            <el-option label="待审批" value="0"/>
            <el-option label="已通过" value="1"/>
            <el-option label="未审批" value="2"/>
            <el-option label="已取消" value="3"/>
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="resetSearch">重置</el-button>
        </el-form-item>
      </el-form>
    <el-form :model="searchForm"
             inline>
      <el-form-item label="会议主题">
        <el-input v-model="searchForm.title"
                  placeholder="请输入会议主题"
                  clearable />
      </el-form-item>
      <el-form-item label="申请人">
        <el-input v-model="searchForm.applicant"
                  placeholder="请输入申请人"
                  clearable />
      </el-form-item>
      <el-form-item label="审批状态">
        <el-select style="width: 100px"
                   v-model="searchForm.status"
                   placeholder="请选择审批状态"
                   clearable>
          <el-option label="待审批"
                     value="0" />
          <el-option label="已通过"
                     value="1" />
          <el-option label="未审批"
                     value="2" />
          <el-option label="已取消"
                     value="3" />
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary"
                   @click="handleSearch">搜索</el-button>
        <el-button @click="resetSearch">重置</el-button>
      </el-form-item>
    </el-form>
    <!-- 会议审批列表 -->
    <el-card>
      <el-table v-loading="loading" :data="approvalList" border :height="tableHeight">
        <el-table-column prop="title" label="会议主题" align="center" min-width="200" show-overflow-tooltip/>
        <el-table-column prop="applicant" label="申请人" align="center" width="120"/>
        <el-table-column prop="host" label="主理人" align="center" width="120"/>
        <el-table-column prop="meetingTime" label="会议时间" align="center" width="180">
      <el-table v-loading="loading"
                :data="approvalList"
                border
                :height="tableHeight">
        <el-table-column prop="title"
                         label="会议主题"
                         align="center"
                         min-width="200"
                         show-overflow-tooltip />
        <el-table-column prop="applicant"
                         label="申请人"
                         align="center"
                         width="120" />
        <el-table-column prop="host"
                         label="主理人"
                         align="center"
                         width="120" />
        <el-table-column prop="meetingTime"
                         label="会议时间"
                         align="center"
                         width="180">
          <template #default="scope">
            {{ formatDateTime(scope.row.meetingTime) }}
          </template>
        </el-table-column>
        <el-table-column prop="location" label="会议地点" align="center" width="150"/>
        <el-table-column prop="participants" label="参会人数" align="center" width="100">
        <el-table-column prop="location"
                         label="会议地点"
                         align="center"
                         width="150" />
        <el-table-column prop="participants"
                         label="参会人数"
                         align="center"
                         width="100">
          <template #default="scope">
            {{ scope.row.participants.length }}人
          </template>
        </el-table-column>
        <el-table-column prop="status" label="审批状态" align="center" width="120">
        <el-table-column prop="status"
                         label="审批状态"
                         align="center"
                         width="120">
          <template #default="scope">
            <el-tag :type="getStatusType(scope.row.status)">
              {{ getStatusText(scope.row.status) }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center" width="200" fixed="right">
        <el-table-column label="操作"
                         align="center"
                         width="200"
                         fixed="right">
          <template #default="scope">
            <el-button type="primary" link @click="viewDetail(scope.row)">查看</el-button>
            <el-button
                v-if="scope.row.status == '0'"
                type="primary"
                link
                @click="handleApproval(scope.row)"
            >
            <el-button type="primary"
                       link
                       @click="viewDetail(scope.row)">查看</el-button>
            <el-button v-if="scope.row.status == '0'"
                       type="primary"
                       link
                       @click="handleApproval(scope.row)">
              审批
            </el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- 分页 -->
      <pagination
          v-show="total > 0"
          :total="total"
          v-model:page="queryParams.current"
          v-model:limit="queryParams.size"
          @pagination="getList"
      />
      <pagination v-show="total > 0"
                  :total="total"
                  v-model:page="queryParams.current"
                  v-model:limit="queryParams.size"
                  @pagination="getList" />
    </el-card>
    <!-- 会议详情对话框 -->
    <el-dialog
        title="会议详情"
        v-model="detailDialogVisible"
        width="800px"
    >
    <el-dialog title="会议详情"
               v-model="detailDialogVisible"
               width="800px">
      <div v-if="currentMeeting">
         <el-descriptions label-width="100px" class="meeting-desc" :column="2" border>
          <el-descriptions-item label="会议主题" label-class-name="nowrap-label">{{
        <el-descriptions label-width="100px"
                         class="meeting-desc"
                         :column="2"
                         border>
          <el-descriptions-item label="会议主题"
                                label-class-name="nowrap-label">{{
              currentMeeting.title
            }}</el-descriptions-item>
          <el-descriptions-item label="申请人" label-class-name="nowrap-label">{{
          <el-descriptions-item label="申请人"
                                label-class-name="nowrap-label">{{
              currentMeeting.applicant
            }}</el-descriptions-item>
          <el-descriptions-item label="主理人" label-class-name="nowrap-label">{{
          <el-descriptions-item label="主理人"
                                label-class-name="nowrap-label">{{
              currentMeeting.host
            }}</el-descriptions-item>
          <el-descriptions-item label="会议时间" :span="2" label-class-name="nowrap-label">
          <el-descriptions-item label="会议时间"
                                :span="2"
                                label-class-name="nowrap-label">
            {{ formatDateTime(currentMeeting.meetingTime) }}
          </el-descriptions-item>
          <el-descriptions-item label="会议地点" label-class-name="nowrap-label">{{
          <el-descriptions-item label="会议地点"
                                label-class-name="nowrap-label">{{
              currentMeeting.location
            }}</el-descriptions-item>
          <el-descriptions-item label="参会人数" label-class-name="nowrap-label">{{
          <el-descriptions-item label="参会人数"
                                label-class-name="nowrap-label">{{
              currentMeeting.participants.length
            }}人</el-descriptions-item>
          <el-descriptions-item label="审批状态" label-class-name="nowrap-label">
          <el-descriptions-item label="审批状态"
                                label-class-name="nowrap-label">
            <el-tag :type="getStatusType(currentMeeting.status)">
              {{ getStatusText(currentMeeting.status) }}
            </el-tag>
          </el-descriptions-item>
          <el-descriptions-item label="申请时间" label-class-name="nowrap-label">{{
          <el-descriptions-item label="申请时间"
                                label-class-name="nowrap-label">{{
              currentMeeting.createTime
            }}</el-descriptions-item>
          <el-descriptions-item style="max-height: 400px" label="会议说明" :span="2"
          <el-descriptions-item style="max-height: 400px"
                                label="会议说明"
                                :span="2"
                                label-class-name="nowrap-label">{{ currentMeeting.description }}</el-descriptions-item>
        </el-descriptions>
        <div class="content-section mt-20">
          <h4>参会人员</h4>
          <div class="participants-list">
            <el-tag
                v-for="participant in currentMeeting.participants"
                :key="participant.id"
                style="margin-right: 10px; margin-bottom: 10px;"
            >
            <el-tag v-for="participant in currentMeeting.participants"
                    :key="participant.id"
                    style="margin-right: 10px; margin-bottom: 10px;">
              {{ participant.name }}
            </el-tag>
          </div>
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="detailDialogVisible = false">关 闭</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- 会议审批对话框 -->
    <el-dialog
        title="会议审批"
        v-model="approvalDialogVisible"
    >
    <el-dialog title="会议审批"
               v-model="approvalDialogVisible">
      <div v-if="currentMeeting">
        <el-descriptions :column="2" border>
        <el-descriptions :column="2"
                         border>
          <el-descriptions-item label="会议主题">{{ currentMeeting.title }}</el-descriptions-item>
          <el-descriptions-item label="申请人">{{ currentMeeting.applicant }}</el-descriptions-item>
          <el-descriptions-item label="主理人">{{ currentMeeting.host }}</el-descriptions-item>
          <el-descriptions-item label="会议时间" :span="2">
          <el-descriptions-item label="会议时间"
                                :span="2">
            {{ formatDateTime(currentMeeting.meetingTime) }}
          </el-descriptions-item>
          <el-descriptions-item label="会议地点">{{ currentMeeting.location }}</el-descriptions-item>
          <el-descriptions-item label="参会人数">{{ currentMeeting.participants.length }}人</el-descriptions-item>
        </el-descriptions>
        <div class="content-section mt-20">
          <h4>参会人员</h4>
          <div class="participants-list">
            <el-tag
                v-for="participant in currentMeeting.participants"
                :key="participant.id"
                style="margin-right: 10px; margin-bottom: 10px;"
            >
            <el-tag v-for="participant in currentMeeting.participants"
                    :key="participant.id"
                    style="margin-right: 10px; margin-bottom: 10px;">
              {{ participant.name }}
            </el-tag>
          </div>
        </div>
        <div v-show="false" class="approval-opinion mt-20">
        <div v-show="false"
             class="approval-opinion mt-20">
          <h4>审批意见</h4>
          <el-input
              v-model="approvalOpinion"
              type="textarea"
              placeholder="请输入审批意见"
              :rows="4"
          />
          <el-input v-model="approvalOpinion"
                    type="textarea"
                    placeholder="请输入审批意见"
                    :rows="4" />
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="approvalDialogVisible = false">取 消</el-button>
          <el-button type="danger" @click="submitApproval('2')">不通过</el-button>
          <el-button type="primary" @click="submitApproval('1')">通 过</el-button>
          <el-button type="danger"
                     @click="submitApproval('2')">不通过</el-button>
          <el-button type="primary"
                     @click="submitApproval('1')">通 过</el-button>
        </div>
      </template>
    </el-dialog>
@@ -184,231 +221,260 @@
</template>
<script setup>
import {ref, reactive, onMounted} from 'vue'
import {ElMessage, ElMessageBox} from 'element-plus'
import Pagination from '@/components/Pagination/index.vue'
import {getRoomEnum, getExamineList,saveMeetingApplication} from '@/api/collaborativeApproval/meeting.js'
import dayjs from "dayjs";
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
  import { ref, reactive, onMounted } from "vue";
  import { ElMessage, ElMessageBox } from "element-plus";
  import Pagination from "@/components/Pagination/index.vue";
  import {
    getRoomEnum,
    getExamineList,
    saveMeetingApplication,
  } from "@/api/collaborativeApproval/meeting.js";
  import dayjs from "dayjs";
  import { userListNoPageByTenantId } from "@/api/system/user.js";
// 数据列表加载状态
const loading = ref(false)
  // 数据列表加载状态
  const loading = ref(false);
// 总条数
const total = ref(0)
  // 总条数
  const total = ref(0);
// 表格高度(根据窗口高度自适应)
const tableHeight = ref(window.innerHeight - 380)
const roomEnum = ref([])
const staffList = ref([])
// 审批列表数据
const approvalList = ref([])
  // 表格高度(根据窗口高度自适应)
  const tableHeight = ref(window.innerHeight - 380);
  const roomEnum = ref([]);
  const userList = ref([]);
  // 审批列表数据
  const approvalList = ref([]);
// 查询参数
const queryParams = reactive({
  current: 1,
  size: 10
})
  // 查询参数
  const queryParams = reactive({
    current: 1,
    size: 10,
  });
// 搜索表单
const searchForm = reactive({
  title: '',
  applicant: '',
  status: ''
})
  // 搜索表单
  const searchForm = reactive({
    title: "",
    applicant: "",
    status: "",
  });
// 是否显示对话框
const detailDialogVisible = ref(false)
const approvalDialogVisible = ref(false)
  // 是否显示对话框
  const detailDialogVisible = ref(false);
  const approvalDialogVisible = ref(false);
// 当前查看的会议
const currentMeeting = ref(null)
  // 当前查看的会议
  const currentMeeting = ref(null);
// 审批意见
const approvalOpinion = ref('')
  // 审批意见
  const approvalOpinion = ref("");
// 查询数据
const getList = async () => {
  loading.value = true
  let resp = await getExamineList({...searchForm, ...queryParams})
  approvalList.value = resp.data.records.map(it => {
    let room = roomEnum.value.find(room => it.roomId === room.id)
    it.location = `${room.name}(${room.location})`
    let staffs = JSON.parse(it.participants)
    it.staffCount = staffs.size
    it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format('HH:mm:ss')} ~ ${dayjs(it.endTime).format('HH:mm:ss')}`
    it.participants = staffList.value.filter(staff => staffs.some(id=>id === staff.id)).map(staff => {
      return {
        id: staff.id,
        name:  `${staff.staffName}${staff.postName ? ` (${staff.postName})` : ''}`
  // 查询数据
  const getList = async () => {
    loading.value = true;
    let resp = await getExamineList({ ...searchForm, ...queryParams });
    const userMap = new Map(userList.value.map(u => [String(u.userId), u]));
    approvalList.value = resp.data.records.map(it => {
      let room = roomEnum.value.find(room => it.roomId === room.id);
      it.location = `${room.name}(${room.location})`;
      let participantIds = [];
      try {
        participantIds = Array.isArray(it.participants)
          ? it.participants
          : JSON.parse(it.participants);
      } catch (_e) {
        participantIds = [];
      }
    })
      if (!Array.isArray(participantIds)) participantIds = [];
      it.staffCount = participantIds.length;
      it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format(
        "HH:mm:ss"
      )} ~ ${dayjs(it.endTime).format("HH:mm:ss")}`;
      it.participants = participantIds.map(id => {
        const user = userMap.get(String(id));
        const nickName = user?.nickName || user?.userName || String(id ?? "");
        const deptNames = user?.deptNames || "";
        return {
          id,
          name: deptNames ? `${nickName} (${deptNames})` : nickName,
        };
      });
      return it;
    });
    total.value = resp.data.total;
    loading.value = false;
  };
    return it
  })
  total.value = resp.data.total
  loading.value = false
}
  // 搜索按钮操作
  const handleSearch = () => {
    queryParams.pageNum = 1;
    getList();
  };
// 搜索按钮操作
const handleSearch = () => {
  queryParams.pageNum = 1
  getList()
}
  // 重置搜索表单
  const resetSearch = () => {
    Object.assign(searchForm, {
      title: "",
      applicant: "",
      status: "",
    });
    handleSearch();
  };
// 重置搜索表单
const resetSearch = () => {
  Object.assign(searchForm, {
    title: '',
    applicant: '',
    status: ''
  })
  handleSearch()
}
  // 查看详情
  const viewDetail = row => {
    currentMeeting.value = row;
    detailDialogVisible.value = true;
  };
// 查看详情
const viewDetail = (row) => {
  currentMeeting.value = row
  detailDialogVisible.value = true
}
  // 处理审批
  const handleApproval = row => {
    currentMeeting.value = row;
    approvalOpinion.value = "";
    approvalDialogVisible.value = true;
  };
// 处理审批
const handleApproval = (row) => {
  currentMeeting.value = row
  approvalOpinion.value = ''
  approvalDialogVisible.value = true
}
  // 获取状态类型
  const getStatusType = status => {
    const statusMap = {
      0: "info", // 待审批
      1: "success", // 已通过
      2: "warning", // 未通过
      3: "danger", // 取消
    };
    return statusMap[status] || "info";
  };
// 获取状态类型
const getStatusType = (status) => {
  const statusMap = {
    '0': 'info',     // 待审批
    '1': 'success',  // 已通过
    '2': 'warning',  // 未通过
    '3': 'danger'   // 取消
  }
  return statusMap[status] || 'info'
}
  // 获取状态文本
  const getStatusText = status => {
    const statusMap = {
      0: "待审批",
      1: "已通过",
      2: "未通过",
      3: "已取消",
    };
    return statusMap[status] || "未知";
  };
// 获取状态文本
const getStatusText = (status) => {
  const statusMap = {
    '0': '待审批',
    '1': '已通过',
    '2': '未通过',
    '3': '已取消'
  }
  return statusMap[status] || '未知'
}
  // 格式化日期时间
  const formatDateTime = dateTime => {
    if (!dateTime) return "";
    return dateTime.replace(" ", "\n");
  };
// 格式化日期时间
const formatDateTime = (dateTime) => {
  if (!dateTime) return ''
  return dateTime.replace(' ', '\n')
}
  // 提交审批
  const submitApproval = status => {
    // if (status === 'approved' && !approvalOpinion.value.trim()) {
    //   ElMessage.warning('请填写审批意见')
    //   return
    // }
// 提交审批
const submitApproval = (status) => {
  // if (status === 'approved' && !approvalOpinion.value.trim()) {
  //   ElMessage.warning('请填写审批意见')
  //   return
  // }
  ElMessageBox.confirm(
      `确认${status === '1' ? '通过' : '不通过'}该会议申请?`,
      '审批确认',
    ElMessageBox.confirm(
      `确认${status === "1" ? "通过" : "不通过"}该会议申请?`,
      "审批确认",
      {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }
  ).then(() => {
    saveMeetingApplication({
      id: currentMeeting.value.id,
      status: status
    }).then(resp=>{
      // 更新会议状态
      currentMeeting.value.status = status
    )
      .then(() => {
        saveMeetingApplication({
          id: currentMeeting.value.id,
          status: status,
        }).then(resp => {
          // 更新会议状态
          currentMeeting.value.status = status;
      ElMessage.success('审批提交成功')
      approvalDialogVisible.value = false
      getList()
    })
          ElMessage.success("审批提交成功");
          approvalDialogVisible.value = false;
          getList();
        });
      })
      .catch(() => {});
  };
  }).catch(() => {
  })
}
  // 页面加载时获取数据
  onMounted(async () => {
    const [resp1, resp2] = await Promise.all([
      getRoomEnum(),
      userListNoPageByTenantId(),
    ]);
    roomEnum.value = resp1.data;
    const list = Array.isArray(resp2?.data) ? resp2.data : [];
    userList.value = list
      .map(item => ({
        userId: item?.userId,
        nickName: item?.nickName,
        userName: item?.userName,
        deptNames:
          item?.deptNames || (item?.dept?.deptName ? item.dept.deptName : ""),
      }))
      .filter(item => item.userId !== null && item.userId !== undefined);
// 页面加载时获取数据
onMounted(async () => {
  const [resp1, resp2]= await Promise.all([getRoomEnum(), staffOnJobListPage({current: -1, size: -1, staffState: 1})])
  roomEnum.value = resp1.data
  staffList.value = resp2.data.records
  await getList()
})
    await getList();
  });
</script>
<style scoped>
.app-container {
  padding: 20px;
}
  .app-container {
    padding: 20px;
  }
.page-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}
  .page-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
  }
.page-header h2 {
  margin: 0;
  color: #303133;
}
  .page-header h2 {
    margin: 0;
    color: #303133;
  }
.search-card {
  margin-bottom: 20px;
}
  .search-card {
    margin-bottom: 20px;
  }
.dialog-footer {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}
  .dialog-footer {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
  }
.content-section h4 {
  margin: 0 0 15px 0;
  color: #303133;
}
  .content-section h4 {
    margin: 0 0 15px 0;
    color: #303133;
  }
.mt-20 {
  margin-top: 20px;
}
  .mt-20 {
    margin-top: 20px;
  }
.participants-list {
  min-height: 40px;
  padding: 15px;
  border-radius: 4px;
  line-height: 1.6;
}
  .participants-list {
    min-height: 40px;
    padding: 15px;
    border-radius: 4px;
    line-height: 1.6;
  }
.approval-opinion h4 {
  margin: 0 0 15px 0;
  color: #303133;
}
  .approval-opinion h4 {
    margin: 0 0 15px 0;
    color: #303133;
  }
.nowrap-label {
  white-space: nowrap !important;
}
  .nowrap-label {
    white-space: nowrap !important;
  }
.description-content {
  white-space: pre-wrap;
  word-wrap: break-word;
  line-height: 1.6;
  padding: 10px;
  background-color: #f5f7fa;
  border-radius: 4px;
  min-height: 60px;
}
  .description-content {
    white-space: pre-wrap;
    word-wrap: break-word;
    line-height: 1.6;
    padding: 10px;
    background-color: #f5f7fa;
    border-radius: 4px;
    min-height: 60px;
  }
</style>
src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue
@@ -1,180 +1,213 @@
<template>
  <div>
    <el-form :model="searchForm" inline>
        <el-form-item label="会议主题">
          <el-input v-model="searchForm.title" placeholder="请输入会议主题" clearable/>
        </el-form-item>
        <el-form-item label="申请人">
          <el-input v-model="searchForm.applicant" placeholder="请输入申请人" clearable/>
        </el-form-item>
        <el-form-item label="发布状态">
          <el-select style="width: 100px" v-model="searchForm.status" placeholder="请选择发布状态" clearable>
            <el-option label="待发布" value="0"/>
            <el-option label="已发布" value="1"/>
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="resetSearch">重置</el-button>
        </el-form-item>
      </el-form>
    <el-form :model="searchForm"
             inline>
      <el-form-item label="会议主题">
        <el-input v-model="searchForm.title"
                  placeholder="请输入会议主题"
                  clearable />
      </el-form-item>
      <el-form-item label="申请人">
        <el-input v-model="searchForm.applicant"
                  placeholder="请输入申请人"
                  clearable />
      </el-form-item>
      <el-form-item label="发布状态">
        <el-select style="width: 100px"
                   v-model="searchForm.status"
                   placeholder="请选择发布状态"
                   clearable>
          <el-option label="待发布"
                     value="0" />
          <el-option label="已发布"
                     value="1" />
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary"
                   @click="handleSearch">搜索</el-button>
        <el-button @click="resetSearch">重置</el-button>
      </el-form-item>
    </el-form>
    <!-- 会议发布列表 -->
    <el-card>
      <el-table v-loading="loading" :data="approvalList" border :height="tableHeight">
        <el-table-column prop="title" label="会议主题" align="center" min-width="200" show-overflow-tooltip/>
        <el-table-column prop="applicant" label="申请人" align="center" width="120"/>
        <el-table-column prop="host" label="主理人" align="center" width="120"/>
        <el-table-column prop="meetingTime" label="会议时间" align="center" width="180">
      <el-table v-loading="loading"
                :data="approvalList"
                border
                :height="tableHeight">
        <el-table-column prop="title"
                         label="会议主题"
                         align="center"
                         min-width="200"
                         show-overflow-tooltip />
        <el-table-column prop="applicant"
                         label="申请人"
                         align="center"
                         width="120" />
        <el-table-column prop="host"
                         label="主理人"
                         align="center"
                         width="120" />
        <el-table-column prop="meetingTime"
                         label="会议时间"
                         align="center"
                         width="180">
          <template #default="scope">
            {{ formatDateTime(scope.row.meetingTime) }}
          </template>
        </el-table-column>
        <el-table-column prop="location" label="会议地点" align="center" width="150"/>
        <el-table-column prop="participants" label="参会人数" align="center" width="100">
        <el-table-column prop="location"
                         label="会议地点"
                         align="center"
                         width="150" />
        <el-table-column prop="participants"
                         label="参会人数"
                         align="center"
                         width="100">
          <template #default="scope">
            {{ scope.row.participants.length }}人
          </template>
        </el-table-column>
        <el-table-column prop="status" label="发布状态" align="center" width="120">
        <el-table-column prop="status"
                         label="发布状态"
                         align="center"
                         width="120">
          <template #default="scope">
            <el-tag :type="getStatusType(scope.row.status)">
              {{ getStatusText(scope.row.status) }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center" width="200" fixed="right">
        <el-table-column label="操作"
                         align="center"
                         width="200"
                         fixed="right">
          <template #default="scope">
            <el-button type="primary" link @click="viewDetail(scope.row)">查看</el-button>
            <el-button
                v-if="scope.row.status == '0'"
                type="primary"
                link
                @click="handleApproval(scope.row)"
            >
            <el-button type="primary"
                       link
                       @click="viewDetail(scope.row)">查看</el-button>
            <el-button v-if="scope.row.status == '0'"
                       type="primary"
                       link
                       @click="handleApproval(scope.row)">
              发布
            </el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- 分页 -->
      <pagination
          v-show="total > 0"
          :total="total"
          v-model:page="queryParams.current"
          v-model:limit="queryParams.size"
          @pagination="getList"
      />
      <pagination v-show="total > 0"
                  :total="total"
                  v-model:page="queryParams.current"
                  v-model:limit="queryParams.size"
                  @pagination="getList" />
    </el-card>
    <!-- 会议详情对话框 -->
    <el-dialog
        title="会议详情"
        v-model="detailDialogVisible"
        width="800px"
    >
    <el-dialog title="会议详情"
               v-model="detailDialogVisible"
               width="800px">
      <div v-if="currentMeeting">
         <el-descriptions label-width="100px" class="meeting-desc" :column="2" border>
          <el-descriptions-item label="会议主题" label-class-name="nowrap-label">{{
        <el-descriptions label-width="100px"
                         class="meeting-desc"
                         :column="2"
                         border>
          <el-descriptions-item label="会议主题"
                                label-class-name="nowrap-label">{{
              currentMeeting.title
            }}</el-descriptions-item>
          <el-descriptions-item label="申请人" label-class-name="nowrap-label">{{
          <el-descriptions-item label="申请人"
                                label-class-name="nowrap-label">{{
              currentMeeting.applicant
            }}</el-descriptions-item>
          <el-descriptions-item label="主理人" label-class-name="nowrap-label">{{
          <el-descriptions-item label="主理人"
                                label-class-name="nowrap-label">{{
              currentMeeting.host
            }}</el-descriptions-item>
          <el-descriptions-item label="会议时间" :span="2" label-class-name="nowrap-label">
          <el-descriptions-item label="会议时间"
                                :span="2"
                                label-class-name="nowrap-label">
            {{ formatDateTime(currentMeeting.meetingTime) }}
          </el-descriptions-item>
          <el-descriptions-item label="会议地点" label-class-name="nowrap-label">{{
          <el-descriptions-item label="会议地点"
                                label-class-name="nowrap-label">{{
              currentMeeting.location
            }}</el-descriptions-item>
          <el-descriptions-item label="参会人数" label-class-name="nowrap-label">{{
          <el-descriptions-item label="参会人数"
                                label-class-name="nowrap-label">{{
              currentMeeting.participants.length
            }}人</el-descriptions-item>
          <el-descriptions-item label="发布状态" label-class-name="nowrap-label">
          <el-descriptions-item label="发布状态"
                                label-class-name="nowrap-label">
            <el-tag :type="getStatusType(currentMeeting.status)">
              {{ getStatusText(currentMeeting.status) }}
            </el-tag>
          </el-descriptions-item>
          <el-descriptions-item label="申请时间" label-class-name="nowrap-label">{{
          <el-descriptions-item label="申请时间"
                                label-class-name="nowrap-label">{{
              currentMeeting.createTime
            }}</el-descriptions-item>
          <el-descriptions-item style="max-height: 400px" label="会议说明" :span="2"
          <el-descriptions-item style="max-height: 400px"
                                label="会议说明"
                                :span="2"
                                label-class-name="nowrap-label">{{ currentMeeting.description }}</el-descriptions-item>
        </el-descriptions>
        <div class="content-section mt-20">
          <h4>参会人员</h4>
          <div class="participants-list">
            <el-tag
                v-for="participant in currentMeeting.participants"
                :key="participant.id"
                style="margin-right: 10px; margin-bottom: 10px;"
            >
            <el-tag v-for="participant in currentMeeting.participants"
                    :key="participant.id"
                    style="margin-right: 10px; margin-bottom: 10px;">
              {{ participant.name }}
            </el-tag>
          </div>
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="detailDialogVisible = false">关 闭</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- 会议发布对话框 -->
    <el-dialog
        title="会议发布"
        v-model="approvalDialogVisible"
    >
    <el-dialog title="会议发布"
               v-model="approvalDialogVisible">
      <div v-if="currentMeeting">
        <el-descriptions :column="2" border>
        <el-descriptions :column="2"
                         border>
          <el-descriptions-item label="会议主题">{{ currentMeeting.title }}</el-descriptions-item>
          <el-descriptions-item label="申请人">{{ currentMeeting.applicant }}</el-descriptions-item>
          <el-descriptions-item label="主理人">{{ currentMeeting.host }}</el-descriptions-item>
          <el-descriptions-item label="会议时间" :span="2">
          <el-descriptions-item label="会议时间"
                                :span="2">
            {{ formatDateTime(currentMeeting.meetingTime) }}
          </el-descriptions-item>
          <el-descriptions-item label="会议地点">{{ currentMeeting.location }}</el-descriptions-item>
          <el-descriptions-item label="参会人数">{{ currentMeeting.participants.length }}人</el-descriptions-item>
        </el-descriptions>
        <div class="content-section mt-20">
          <h4>参会人员</h4>
          <div class="participants-list">
            <el-tag
                v-for="participant in currentMeeting.participants"
                :key="participant.id"
                style="margin-right: 10px; margin-bottom: 10px;"
            >
            <el-tag v-for="participant in currentMeeting.participants"
                    :key="participant.id"
                    style="margin-right: 10px; margin-bottom: 10px;">
              {{ participant.name }}
            </el-tag>
          </div>
        </div>
        <div class="approval-opinion mt-20">
          <h4>发布意见</h4>
          <el-input
              v-model="publishComment"
              type="textarea"
              placeholder="请输入发布意见"
              :rows="4"
          />
          <el-input v-model="publishComment"
                    type="textarea"
                    placeholder="请输入发布意见"
                    :rows="4" />
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="approvalDialogVisible = false">取 消</el-button>
<!--          <el-button type="danger" @click="submitApproval('2')">不通过</el-button>-->
          <el-button type="primary" @click="submitApproval('1')">发 布</el-button>
          <!--          <el-button type="danger" @click="submitApproval('2')">不通过</el-button>-->
          <el-button type="primary"
                     @click="submitApproval('1')">发 布</el-button>
        </div>
      </template>
    </el-dialog>
@@ -182,231 +215,260 @@
</template>
<script setup>
import {ref, reactive, onMounted} from 'vue'
import {ElMessage, ElMessageBox} from 'element-plus'
import Pagination from '@/components/Pagination/index.vue'
import {getRoomEnum, getMeetingPublish,saveMeetingApplication} from '@/api/collaborativeApproval/meeting.js'
import dayjs from "dayjs";
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
  import { ref, reactive, onMounted } from "vue";
  import { ElMessage, ElMessageBox } from "element-plus";
  import Pagination from "@/components/Pagination/index.vue";
  import {
    getRoomEnum,
    getMeetingPublish,
    saveMeetingApplication,
  } from "@/api/collaborativeApproval/meeting.js";
  import dayjs from "dayjs";
  import { userListNoPageByTenantId } from "@/api/system/user.js";
// 数据列表加载状态
const loading = ref(false)
  // 数据列表加载状态
  const loading = ref(false);
// 总条数
const total = ref(0)
  // 总条数
  const total = ref(0);
// 表格高度(根据窗口高度自适应)
const tableHeight = ref(window.innerHeight - 380)
const roomEnum = ref([])
const staffList = ref([])
// 发布列表数据
const approvalList = ref([])
  // 表格高度(根据窗口高度自适应)
  const tableHeight = ref(window.innerHeight - 380);
  const roomEnum = ref([]);
  const userList = ref([]);
  // 发布列表数据
  const approvalList = ref([]);
// 查询参数
const queryParams = reactive({
  current: 1,
  size: 10
})
  // 查询参数
  const queryParams = reactive({
    current: 1,
    size: 10,
  });
// 搜索表单
const searchForm = reactive({
  title: '',
  applicant: '',
  status: ''
})
  // 搜索表单
  const searchForm = reactive({
    title: "",
    applicant: "",
    status: "",
  });
// 是否显示对话框
const detailDialogVisible = ref(false)
const approvalDialogVisible = ref(false)
  // 是否显示对话框
  const detailDialogVisible = ref(false);
  const approvalDialogVisible = ref(false);
// 当前查看的会议
const currentMeeting = ref(null)
  // 当前查看的会议
  const currentMeeting = ref(null);
// 发布意见
const publishComment = ref('')
  // 发布意见
  const publishComment = ref("");
// 查询数据
const getList = async () => {
  loading.value = true
  let resp = await getMeetingPublish({...searchForm, ...queryParams})
  approvalList.value = resp.data.records.map(it => {
    let room = roomEnum.value.find(room => it.roomId === room.id)
    it.location = `${room.name}(${room.location})`
    let staffs = JSON.parse(it.participants)
    it.staffCount = staffs.size
    it.status = it.publishStatus
    it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format('HH:mm:ss')} ~ ${dayjs(it.endTime).format('HH:mm:ss')}`
    it.participants = staffList.value.filter(staff => staffs.some(id=>id === staff.id)).map(staff => {
      return {
        id: staff.id,
        name: `${staff.staffName}${staff.postName ? ` (${staff.postName})` : ''}`
  // 查询数据
  const getList = async () => {
    loading.value = true;
    let resp = await getMeetingPublish({ ...searchForm, ...queryParams });
    const userMap = new Map(userList.value.map(u => [String(u.userId), u]));
    approvalList.value = resp.data.records.map(it => {
      let room = roomEnum.value.find(room => it.roomId === room.id);
      it.location = `${room.name}(${room.location})`;
      let participantIds = [];
      try {
        participantIds = Array.isArray(it.participants)
          ? it.participants
          : JSON.parse(it.participants);
      } catch (_e) {
        participantIds = [];
      }
    })
      if (!Array.isArray(participantIds)) participantIds = [];
      it.staffCount = participantIds.length;
      it.status = it.publishStatus;
      it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format(
        "HH:mm:ss"
      )} ~ ${dayjs(it.endTime).format("HH:mm:ss")}`;
      it.participants = participantIds.map(id => {
        const user = userMap.get(String(id));
        const nickName = user?.nickName || user?.userName || String(id ?? "");
        const deptNames = user?.deptNames || "";
        return {
          id,
          name: deptNames ? `${nickName} (${deptNames})` : nickName,
        };
      });
      return it;
    });
    total.value = resp.data.total;
    loading.value = false;
  };
    return it
  })
  total.value = resp.data.total
  loading.value = false
}
  // 搜索按钮操作
  const handleSearch = () => {
    queryParams.pageNum = 1;
    getList();
  };
// 搜索按钮操作
const handleSearch = () => {
  queryParams.pageNum = 1
  getList()
}
  // 重置搜索表单
  const resetSearch = () => {
    Object.assign(searchForm, {
      title: "",
      applicant: "",
      status: "",
    });
    handleSearch();
  };
// 重置搜索表单
const resetSearch = () => {
  Object.assign(searchForm, {
    title: '',
    applicant: '',
    status: ''
  })
  handleSearch()
}
  // 查看详情
  const viewDetail = row => {
    currentMeeting.value = row;
    detailDialogVisible.value = true;
  };
// 查看详情
const viewDetail = (row) => {
  currentMeeting.value = row
  detailDialogVisible.value = true
}
  // 处理发布
  const handleApproval = row => {
    currentMeeting.value = row;
    publishComment.value = "";
    approvalDialogVisible.value = true;
  };
// 处理发布
const handleApproval = (row) => {
  currentMeeting.value = row
  publishComment.value = ''
  approvalDialogVisible.value = true
}
  // 获取状态类型
  const getStatusType = status => {
    const statusMap = {
      0: "info", // 待发布
      1: "success", // 已通过
      2: "danger", // 未通过
    };
    return statusMap[status] || "info";
  };
// 获取状态类型
const getStatusType = (status) => {
  const statusMap = {
    '0': 'info',     // 待发布
    '1': 'success',  // 已通过
    '2': 'danger',  // 未通过
  }
  return statusMap[status] || 'info'
}
  // 获取状态文本
  const getStatusText = status => {
    const statusMap = {
      0: "待发布",
      1: "已发布",
      2: "已取消",
    };
    return statusMap[status] || "未知";
  };
// 获取状态文本
const getStatusText = (status) => {
  const statusMap = {
    '0': '待发布',
    '1': '已发布',
    '2': '已取消',
  }
  return statusMap[status] || '未知'
}
  // 格式化日期时间
  const formatDateTime = dateTime => {
    if (!dateTime) return "";
    return dateTime.replace(" ", "\n");
  };
// 格式化日期时间
const formatDateTime = (dateTime) => {
  if (!dateTime) return ''
  return dateTime.replace(' ', '\n')
}
  // 提交发布
  const submitApproval = status => {
    // if (status === 'approved' && !publishComment.value.trim()) {
    //   ElMessage.warning('请填写发布意见')
    //   return
    // }
// 提交发布
const submitApproval = (status) => {
  // if (status === 'approved' && !publishComment.value.trim()) {
  //   ElMessage.warning('请填写发布意见')
  //   return
  // }
  ElMessageBox.confirm(
      `确认${status === '1' ? '发布' : '取消'}该会议?`,
      '发布确认',
    ElMessageBox.confirm(
      `确认${status === "1" ? "发布" : "取消"}该会议?`,
      "发布确认",
      {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }
  ).then(() => {
    saveMeetingApplication({
      id: currentMeeting.value.id,
      publishStatus: status,
      publishComment: publishComment.value
    }).then(resp=>{
      // 更新会议状态
      currentMeeting.value.status = status
    )
      .then(() => {
        saveMeetingApplication({
          id: currentMeeting.value.id,
          publishStatus: status,
          publishComment: publishComment.value,
        }).then(resp => {
          // 更新会议状态
          currentMeeting.value.status = status;
      ElMessage.success('发布提交成功')
      approvalDialogVisible.value = false
      getList()
    })
          ElMessage.success("发布提交成功");
          approvalDialogVisible.value = false;
          getList();
        });
      })
      .catch(() => {});
  };
  }).catch(() => {
  })
}
  // 页面加载时获取数据
  onMounted(async () => {
    const [resp1, resp2] = await Promise.all([
      getRoomEnum(),
      userListNoPageByTenantId(),
    ]);
    roomEnum.value = resp1.data;
    const list = Array.isArray(resp2?.data) ? resp2.data : [];
    userList.value = list
      .map(item => ({
        userId: item?.userId,
        nickName: item?.nickName,
        userName: item?.userName,
        deptNames:
          item?.deptNames || (item?.dept?.deptName ? item.dept.deptName : ""),
      }))
      .filter(item => item.userId !== null && item.userId !== undefined);
// 页面加载时获取数据
onMounted(async () => {
  const [resp1, resp2]= await Promise.all([getRoomEnum(), staffOnJobListPage({current: -1, size: -1, staffState: 1})])
  roomEnum.value = resp1.data
  staffList.value = resp2.data.records
  await getList()
})
    await getList();
  });
</script>
<style scoped>
.app-container {
  padding: 20px;
}
  .app-container {
    padding: 20px;
  }
.page-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}
  .page-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
  }
.page-header h2 {
  margin: 0;
  color: #303133;
}
  .page-header h2 {
    margin: 0;
    color: #303133;
  }
.search-card {
  margin-bottom: 20px;
}
  .search-card {
    margin-bottom: 20px;
  }
.dialog-footer {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}
  .dialog-footer {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
  }
.content-section h4 {
  margin: 0 0 15px 0;
  color: #303133;
}
  .content-section h4 {
    margin: 0 0 15px 0;
    color: #303133;
  }
.mt-20 {
  margin-top: 20px;
}
  .mt-20 {
    margin-top: 20px;
  }
.participants-list {
  min-height: 40px;
  padding: 15px;
  border-radius: 4px;
  line-height: 1.6;
}
  .participants-list {
    min-height: 40px;
    padding: 15px;
    border-radius: 4px;
    line-height: 1.6;
  }
.approval-opinion h4 {
  margin: 0 0 15px 0;
  color: #303133;
}
  .approval-opinion h4 {
    margin: 0 0 15px 0;
    color: #303133;
  }
.nowrap-label {
  white-space: nowrap !important;
}
  .nowrap-label {
    white-space: nowrap !important;
  }
.description-content {
  white-space: pre-wrap;
  word-wrap: break-word;
  line-height: 1.6;
  padding: 10px;
  background-color: #f5f7fa;
  border-radius: 4px;
  min-height: 60px;
}
  .description-content {
    white-space: pre-wrap;
    word-wrap: break-word;
    line-height: 1.6;
    padding: 10px;
    background-color: #f5f7fa;
    border-radius: 4px;
    min-height: 60px;
  }
</style>
src/views/collaborativeApproval/notificationManagement/summary/index.vue
@@ -1,153 +1,182 @@
<template>
  <div>
    <el-form :model="searchForm" inline>
        <el-form-item label="会议主题">
          <el-input v-model="searchForm.title" placeholder="请输入会议主题" clearable />
        </el-form-item>
        <el-form-item label="申请人">
          <el-input v-model="searchForm.applicant" placeholder="请输入申请人" clearable />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="resetSearch">重置</el-button>
        </el-form-item>
      </el-form>
    <el-form :model="searchForm"
             inline>
      <el-form-item label="会议主题">
        <el-input v-model="searchForm.title"
                  placeholder="请输入会议主题"
                  clearable />
      </el-form-item>
      <el-form-item label="申请人">
        <el-input v-model="searchForm.applicant"
                  placeholder="请输入申请人"
                  clearable />
      </el-form-item>
      <el-form-item>
        <el-button type="primary"
                   @click="handleSearch">搜索</el-button>
        <el-button @click="resetSearch">重置</el-button>
      </el-form-item>
    </el-form>
    <!-- 会议列表 -->
    <el-card>
      <el-table v-loading="loading" :data="meetingList" border :height="tableHeight">
        <el-table-column prop="title" label="会议主题" align="center" min-width="200" show-overflow-tooltip />
        <el-table-column prop="applicant" label="申请人" align="center" width="120" />
        <el-table-column prop="host" label="主持人" align="center" width="120" />
        <el-table-column prop="meetingTime" label="会议时间" align="center" width="180">
      <el-table v-loading="loading"
                :data="meetingList"
                border
                :height="tableHeight">
        <el-table-column prop="title"
                         label="会议主题"
                         align="center"
                         min-width="200"
                         show-overflow-tooltip />
        <el-table-column prop="applicant"
                         label="申请人"
                         align="center"
                         width="120" />
        <el-table-column prop="host"
                         label="主持人"
                         align="center"
                         width="120" />
        <el-table-column prop="meetingTime"
                         label="会议时间"
                         align="center"
                         width="180">
          <template #default="scope">
            {{ formatDateTime(scope.row.meetingTime) }}
          </template>
        </el-table-column>
        <el-table-column prop="location" label="会议地点" align="center" width="150" />
        <el-table-column prop="participants" label="参会人数" align="center" width="100">
        <el-table-column prop="location"
                         label="会议地点"
                         align="center"
                         width="150" />
        <el-table-column prop="participants"
                         label="参会人数"
                         align="center"
                         width="100">
          <template #default="scope">
            {{ scope.row.participants.length }}人
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center" width="200" fixed="right">
        <el-table-column label="操作"
                         align="center"
                         width="200"
                         fixed="right">
          <template #default="scope">
            <el-button type="primary" link @click="viewDetail(scope.row)">查看</el-button>
            <el-button
              type="primary"
              link
              @click="addMinutes(scope.row)"
            >
            <el-button type="primary"
                       link
                       @click="viewDetail(scope.row)">查看</el-button>
            <el-button type="primary"
                       link
                       @click="addMinutes(scope.row)">
              添加纪要
            </el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- 分页 -->
      <pagination
        v-show="total > 0"
        :total="total"
        v-model:page="queryParams.current"
        v-model:limit="queryParams.size"
        @pagination="getList"
      />
      <pagination v-show="total > 0"
                  :total="total"
                  v-model:page="queryParams.current"
                  v-model:limit="queryParams.size"
                  @pagination="getList" />
    </el-card>
    <!-- 会议详情对话框 -->
    <el-dialog
      title="会议详情"
      v-model="detailDialogVisible"
      width="800px"
    >
    <el-dialog title="会议详情"
               v-model="detailDialogVisible"
               width="800px">
      <div v-if="currentMeeting">
        <el-descriptions label-width="100px" class="meeting-desc" :column="2" border>
          <el-descriptions-item label="会议主题" label-class-name="nowrap-label">{{
        <el-descriptions label-width="100px"
                         class="meeting-desc"
                         :column="2"
                         border>
          <el-descriptions-item label="会议主题"
                                label-class-name="nowrap-label">{{
            currentMeeting.title
          }}</el-descriptions-item>
          <el-descriptions-item label="申请人" label-class-name="nowrap-label">{{
          <el-descriptions-item label="申请人"
                                label-class-name="nowrap-label">{{
            currentMeeting.applicant
          }}</el-descriptions-item>
          <el-descriptions-item label="主持人" label-class-name="nowrap-label">{{
          <el-descriptions-item label="主持人"
                                label-class-name="nowrap-label">{{
            currentMeeting.host
          }}</el-descriptions-item>
          <el-descriptions-item label="会议时间" :span="2" label-class-name="nowrap-label">
          <el-descriptions-item label="会议时间"
                                :span="2"
                                label-class-name="nowrap-label">
            {{ formatDateTime(currentMeeting.meetingTime) }}
          </el-descriptions-item>
          <el-descriptions-item label="会议地点" label-class-name="nowrap-label">{{
          <el-descriptions-item label="会议地点"
                                label-class-name="nowrap-label">{{
            currentMeeting.location
          }}</el-descriptions-item>
          <el-descriptions-item label="参会人数" label-class-name="nowrap-label">{{
          <el-descriptions-item label="参会人数"
                                label-class-name="nowrap-label">{{
            currentMeeting.participants.length
          }}人</el-descriptions-item>
          <el-descriptions-item label="审批状态" label-class-name="nowrap-label">
          <el-descriptions-item label="审批状态"
                                label-class-name="nowrap-label">
            <el-tag :type="getStatusType(currentMeeting.status)">
              {{ getStatusText(currentMeeting.status) }}
            </el-tag>
          </el-descriptions-item>
          <el-descriptions-item label="申请时间" label-class-name="nowrap-label">{{
          <el-descriptions-item label="申请时间"
                                label-class-name="nowrap-label">{{
            currentMeeting.createTime
          }}</el-descriptions-item>
          <el-descriptions-item style="max-height: 400px" label="会议说明" :span="2"
            label-class-name="nowrap-label">{{ currentMeeting.description }}</el-descriptions-item>
          <el-descriptions-item style="max-height: 400px"
                                label="会议说明"
                                :span="2"
                                label-class-name="nowrap-label">{{ currentMeeting.description }}</el-descriptions-item>
        </el-descriptions>
        <div class="content-section mt-20">
          <h4>参会人员</h4>
          <div class="participants-list">
            <el-tag
              v-for="participant in currentMeeting.participants"
              :key="participant.id"
              style="margin-right: 10px; margin-bottom: 10px;"
            >
            <el-tag v-for="participant in currentMeeting.participants"
                    :key="participant.id"
                    style="margin-right: 10px; margin-bottom: 10px;">
              {{ participant.name }}
            </el-tag>
          </div>
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="detailDialogVisible = false">关 闭</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- 添加会议纪要对话框 -->
    <el-dialog
      title="添加会议纪要"
      v-model="minutesDialogVisible"
      width="80%"
      @close="handleCloseMinutesDialog"
    >
    <el-dialog title="添加会议纪要"
               v-model="minutesDialogVisible"
               width="80%"
               @close="handleCloseMinutesDialog">
      <div v-if="currentMeeting">
        <el-descriptions :column="2" border>
        <el-descriptions :column="2"
                         border>
          <el-descriptions-item label="会议主题">{{ currentMeeting.title }}</el-descriptions-item>
          <el-descriptions-item label="申请人">{{ currentMeeting.applicant }}</el-descriptions-item>
          <el-descriptions-item label="主持人">{{ currentMeeting.host }}</el-descriptions-item>
          <el-descriptions-item label="会议时间" :span="2">
          <el-descriptions-item label="会议时间"
                                :span="2">
            {{ formatDateTime(currentMeeting.meetingTime) }}
          </el-descriptions-item>
          <el-descriptions-item label="会议地点">{{ currentMeeting.location }}</el-descriptions-item>
          <el-descriptions-item label="参会人数">{{ currentMeeting.participants.length }}人</el-descriptions-item>
        </el-descriptions>
        <div class="content-section mt-20">
          <h4>会议纪要内容</h4>
          <div class="editor-container">
            <Editor
              v-model="minutesContent"
              :min-height="400"
            />
            <Editor v-model="minutesContent"
                    :min-height="400" />
          </div>
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="minutesDialogVisible = false">取 消</el-button>
          <el-button type="primary" @click="submitMinutes">保 存</el-button>
          <el-button type="primary"
                     @click="submitMinutes">保 存</el-button>
        </div>
      </template>
    </el-dialog>
@@ -155,245 +184,278 @@
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import Pagination from '@/components/Pagination/index.vue'
import Editor from '@/components/Editor/index.vue'
import { getRoomEnum, getMeetingPublish ,getMeetingMinutesByMeetingId,saveMeetingMinutes} from '@/api/collaborativeApproval/meeting.js'
import dayjs from "dayjs"
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
  import { ref, reactive, onMounted } from "vue";
  import { ElMessage } from "element-plus";
  import Pagination from "@/components/Pagination/index.vue";
  import Editor from "@/components/Editor/index.vue";
  import {
    getRoomEnum,
    getMeetingPublish,
    getMeetingMinutesByMeetingId,
    saveMeetingMinutes,
  } from "@/api/collaborativeApproval/meeting.js";
  import dayjs from "dayjs";
  import { userListNoPageByTenantId } from "@/api/system/user.js";
// 数据列表加载状态
const loading = ref(false)
  // 数据列表加载状态
  const loading = ref(false);
// 总条数
const total = ref(0)
  // 总条数
  const total = ref(0);
// 表格高度(根据窗口高度自适应)
const tableHeight = ref(window.innerHeight - 380)
const roomEnum = ref([])
const staffList = ref([])
  // 表格高度(根据窗口高度自适应)
  const tableHeight = ref(window.innerHeight - 380);
  const roomEnum = ref([]);
  const userList = ref([]);
// 会议列表数据
const meetingList = ref([])
  // 会议列表数据
  const meetingList = ref([]);
// 查询参数
const queryParams = reactive({
  current: 1,
  size: 10
})
  // 查询参数
  const queryParams = reactive({
    current: 1,
    size: 10,
  });
// 搜索表单
const searchForm = reactive({
  title: '',
  applicant: '',
  // status: '1' // 默认只显示已通过审批的会议
})
  // 搜索表单
  const searchForm = reactive({
    title: "",
    applicant: "",
    // status: '1' // 默认只显示已通过审批的会议
  });
// 是否显示对话框
const detailDialogVisible = ref(false)
const minutesDialogVisible = ref(false)
  // 是否显示对话框
  const detailDialogVisible = ref(false);
  const minutesDialogVisible = ref(false);
// 当前查看的会议
const currentMeeting = ref(null)
  // 当前查看的会议
  const currentMeeting = ref(null);
// 会议纪要内容
const minutesContent = ref('')
const minutesContentId = ref('')
  // 会议纪要内容
  const minutesContent = ref("");
  const minutesContentId = ref("");
// 查询数据
const getList = async () => {
  loading.value = true
  let resp = await getMeetingPublish({ ...searchForm, ...queryParams })
  meetingList.value = resp.data.records.map(it => {
    let room = roomEnum.value.find(room => it.roomId === room.id)
    it.location = `${room.name}(${room.location})`
    let staffs = JSON.parse(it.participants)
    it.staffCount = staffs.size
    it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format('HH:mm:ss')} ~ ${dayjs(it.endTime).format('HH:mm:ss')}`
    it.participants = staffList.value.filter(staff => staffs.some(id => id === staff.id)).map(staff => {
      return {
        id: staff.id,
        name:  `${staff.staffName}${staff.postName ? ` (${staff.postName})` : ''}`
      }
    })
  const parseParticipants = raw => {
    if (!raw) return [];
    if (Array.isArray(raw)) return raw;
    try {
      const parsed = JSON.parse(raw);
      return Array.isArray(parsed) ? parsed : [];
    } catch (_e) {
      return [];
    }
  };
    return it
  })
  total.value = resp.data.total
  loading.value = false
}
  // 查询数据
  const getList = async () => {
    loading.value = true;
    let resp = await getMeetingPublish({ ...searchForm, ...queryParams });
    const userMap = new Map(userList.value.map(u => [String(u.userId), u]));
    meetingList.value = resp.data.records.map(it => {
      let room = roomEnum.value.find(room => it.roomId === room.id);
      it.location = `${room.name}(${room.location})`;
      const participantIds = parseParticipants(it.participants);
      it.staffCount = participantIds.length;
      it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format(
        "HH:mm:ss"
      )} ~ ${dayjs(it.endTime).format("HH:mm:ss")}`;
      it.participants = participantIds.map(id => {
        const user = userMap.get(String(id));
        const nickName = user?.nickName || user?.userName || String(id ?? "");
        const deptNames = user?.deptNames || "";
        return {
          id,
          name: deptNames ? `${nickName} (${deptNames})` : nickName,
        };
      });
// 搜索按钮操作
const handleSearch = () => {
  queryParams.current = 1
  getList()
}
      return it;
    });
    total.value = resp.data.total;
    loading.value = false;
  };
// 重置搜索表单
const resetSearch = () => {
  Object.assign(searchForm, {
    title: '',
    applicant: '',
    // status: '1'
  })
  handleSearch()
}
  // 搜索按钮操作
  const handleSearch = () => {
    queryParams.current = 1;
    getList();
  };
// 查看详情
const viewDetail = (row) => {
  currentMeeting.value = row
  detailDialogVisible.value = true
}
  // 重置搜索表单
  const resetSearch = () => {
    Object.assign(searchForm, {
      title: "",
      applicant: "",
      // status: '1'
    });
    handleSearch();
  };
// 添加会议纪要
const addMinutes = async (row) => {
  let resp = await getMeetingMinutesByMeetingId(row.id)
  currentMeeting.value = row
  if (resp.data){
    minutesContent.value = resp.data.content
    minutesContentId.value = resp.data.id
  }else {
    minutesContent.value = `<h2>${row.title}会议纪要</h2>
<p><strong>会议时间:</strong>${row.meetingTime}</p>
<p><strong>会议地点:</strong>${row.location}</p>
<p><strong>主持人:</strong>${row.host}</p>
<p><strong>参会人员:</strong></p>
<ol>
  ${row.participants.map(p => `<li>${p.name}</li>`).join('')}
</ol>
<p><strong>会议内容:</strong></p>
<ol>
  <li>议题一:
    <ul>
      <li>讨论内容:</li>
      <li>决议事项:</li>
    </ul>
  </li>
  <li>议题二:
    <ul>
      <li>讨论内容:</li>
      <li>决议事项:</li>
    </ul>
  </li>
</ol>
<p><strong>备注:</strong></p>`
  }
  // 查看详情
  const viewDetail = row => {
    currentMeeting.value = row;
    detailDialogVisible.value = true;
  };
  minutesDialogVisible.value = true
}
  // 添加会议纪要
  const addMinutes = async row => {
    let resp = await getMeetingMinutesByMeetingId(row.id);
    currentMeeting.value = row;
    if (resp.data) {
      minutesContent.value = resp.data.content;
      minutesContentId.value = resp.data.id;
    } else {
      minutesContent.value = `<h2>${row.title}会议纪要</h2>
  <p><strong>会议时间:</strong>${row.meetingTime}</p>
  <p><strong>会议地点:</strong>${row.location}</p>
  <p><strong>主持人:</strong>${row.host}</p>
  <p><strong>参会人员:</strong></p>
  <ol>
    ${row.participants.map(p => `<li>${p.name}</li>`).join("")}
  </ol>
  <p><strong>会议内容:</strong></p>
  <ol>
    <li>议题一:
      <ul>
        <li>讨论内容:</li>
        <li>决议事项:</li>
      </ul>
    </li>
    <li>议题二:
      <ul>
        <li>讨论内容:</li>
        <li>决议事项:</li>
      </ul>
    </li>
  </ol>
  <p><strong>备注:</strong></p>`;
    }
// 提交会议纪要
const submitMinutes = () => {
  if (!minutesContent.value) {
    ElMessage.warning('请输入会议纪要内容')
    return
  }
  saveMeetingMinutes({
    id: minutesContentId.value,
    content: minutesContent.value,
    meetingId: currentMeeting.value.id,
    title: currentMeeting.value.title
  }).then(resp=>{
    console.log('会议纪要内容:', minutesContent.value)
    ElMessage.success('会议纪要保存成功')
    minutesDialogVisible.value = false
  })
    minutesDialogVisible.value = true;
  };
}
  // 提交会议纪要
  const submitMinutes = () => {
    if (!minutesContent.value) {
      ElMessage.warning("请输入会议纪要内容");
      return;
    }
    saveMeetingMinutes({
      id: minutesContentId.value,
      content: minutesContent.value,
      meetingId: currentMeeting.value.id,
      title: currentMeeting.value.title,
    }).then(resp => {
      console.log("会议纪要内容:", minutesContent.value);
      ElMessage.success("会议纪要保存成功");
      minutesDialogVisible.value = false;
    });
  };
// 关闭会议纪要对话框
const handleCloseMinutesDialog = () => {
  minutesContent.value = ''
}
  // 关闭会议纪要对话框
  const handleCloseMinutesDialog = () => {
    minutesContent.value = "";
  };
// 获取状态类型
const getStatusType = (status) => {
  const statusMap = {
    '0': 'info',     // 待审批
    '1': 'success',  // 已通过
    '2': 'warning',  // 未通过
    '3': 'danger'   // 取消
  }
  return statusMap[status] || 'info'
}
  // 获取状态类型
  const getStatusType = status => {
    const statusMap = {
      0: "info", // 待审批
      1: "success", // 已通过
      2: "warning", // 未通过
      3: "danger", // 取消
    };
    return statusMap[status] || "info";
  };
// 获取状态文本
const getStatusText = (status) => {
  const statusMap = {
    '0': '待审批',
    '1': '已通过',
    '2': '未通过',
    '3': '已取消'
  }
  return statusMap[status] || '未知'
}
  // 获取状态文本
  const getStatusText = status => {
    const statusMap = {
      0: "待审批",
      1: "已通过",
      2: "未通过",
      3: "已取消",
    };
    return statusMap[status] || "未知";
  };
// 格式化日期时间
const formatDateTime = (dateTime) => {
  if (!dateTime) return ''
  return dateTime.replace(' ', '\n')
}
  // 格式化日期时间
  const formatDateTime = dateTime => {
    if (!dateTime) return "";
    return dateTime.replace(" ", "\n");
  };
// 页面加载时获取数据
onMounted(async () => {
  const [resp1, resp2] = await Promise.all([getRoomEnum(), staffOnJobListPage({current: -1, size: -1, staffState: 1})])
  roomEnum.value = resp1.data
  staffList.value = resp2.data.records
  // 页面加载时获取数据
  onMounted(async () => {
    const [resp1, resp2] = await Promise.all([
      getRoomEnum(),
      userListNoPageByTenantId(),
    ]);
    roomEnum.value = resp1.data;
    const list = Array.isArray(resp2?.data) ? resp2.data : [];
    userList.value = list
      .map(item => ({
        userId: item?.userId,
        nickName: item?.nickName,
        userName: item?.userName,
        deptNames:
          item?.deptNames || (item?.dept?.deptName ? item.dept.deptName : ""),
      }))
      .filter(item => item.userId !== null && item.userId !== undefined);
  await getList()
})
    await getList();
  });
</script>
<style scoped>
.app-container {
  padding: 20px;
}
  .app-container {
    padding: 20px;
  }
.page-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}
  .page-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
  }
.page-header h2 {
  margin: 0;
  color: #303133;
}
  .page-header h2 {
    margin: 0;
    color: #303133;
  }
.search-card {
  margin-bottom: 20px;
}
  .search-card {
    margin-bottom: 20px;
  }
.dialog-footer {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}
  .dialog-footer {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
  }
.content-section h4 {
  margin: 0 0 15px 0;
  color: #303133;
}
  .content-section h4 {
    margin: 0 0 15px 0;
    color: #303133;
  }
.mt-20 {
  margin-top: 20px;
}
  .mt-20 {
    margin-top: 20px;
  }
.participants-list {
  min-height: 40px;
  padding: 15px;
  border-radius: 4px;
  line-height: 1.6;
}
  .participants-list {
    min-height: 40px;
    padding: 15px;
    border-radius: 4px;
    line-height: 1.6;
  }
.nowrap-label {
  white-space: nowrap !important;
}
  .nowrap-label {
    white-space: nowrap !important;
  }
.editor-container {
  border: 1px solid #dcdfe6;
  border-radius: 4px;
}
  .editor-container {
    border: 1px solid #dcdfe6;
    border-radius: 4px;
  }
</style>
src/views/productionManagement/workOrder/index.vue
@@ -233,6 +233,7 @@
            <el-form-item label="班组信息"
                          prop="teamList">
              <el-select v-model="reportForm.teamList"
                         ref="teamSelectRef"
                         multiple
                         filterable
                         allow-create
@@ -242,8 +243,8 @@
                         value-key="userName"
                         placeholder="请选择或输入班组成员"
                         @change="handleTeamListChange">
                <el-option v-for="user in reportForm.userIdsList"
                           :key="user.userId"
                <el-option v-for="user in teamSelectOptions"
                           :key="user.userId || `custom-${user.nickName}`"
                           :label="user.nickName"
                           :value="{ userId: user.userId, userName: user.nickName }" />
              </el-select>
@@ -505,6 +506,7 @@
  const currentUserId = ref("");
  const deviceOptions = ref([]);
  const currentUserName = ref("");
  const teamSelectRef = ref(null);
  const ensureCurrentUser = async () => {
    if (currentUserId.value) return;
@@ -1216,6 +1218,29 @@
    teamList: [],
    deviceId: null,
  });
  const teamSelectOptions = computed(() => {
    const base =
      Array.isArray(reportForm.userIdsList) && reportForm.userIdsList.length > 0
        ? reportForm.userIdsList.map(u => ({
            userId: String(u.userId ?? ""),
            nickName: String(u.nickName ?? "").trim(),
          }))
        : [];
    const baseNameSet = new Set(base.map(u => u.nickName));
    const selected = Array.isArray(reportForm.teamList)
      ? reportForm.teamList
      : [];
    const extraNames = selected
      .map(item => {
        if (typeof item === "string") return String(item).trim();
        const name = item?.userName ?? item?.nickName ?? "";
        return String(name).trim();
      })
      .filter(Boolean)
      .filter(name => !baseNameSet.has(name));
    const extras = extraNames.map(name => ({ userId: "", nickName: name }));
    return [...base, ...extras];
  });
  function removeLastFour(str) {
    if (!str) return ""; // 空值保护
    return str.toString().slice(0, -4); // 核心:截取 0 到 倒数第4位
@@ -1675,13 +1700,39 @@
  const handleTeamListChange = val => {
    if (!Array.isArray(val)) return;
    if (!val.some(item => typeof item === "string")) return;
    reportForm.teamList = val.map(item => {
    let hasString = false;
    const newList = val.map(item => {
      if (typeof item === "string") {
        hasString = true;
        return { userName: item };
      }
      return item;
    });
    if (hasString) {
      reportForm.teamList = newList;
    }
    // 解决 allow-create 在 multiple 模式下输入框内容不自动清空的问题
    setTimeout(() => {
      if (teamSelectRef.value) {
        // 1. 清除内部 query 状态
        teamSelectRef.value.query = "";
        if (teamSelectRef.value.states) {
          teamSelectRef.value.states.query = "";
          teamSelectRef.value.states.inputValue = "";
        }
        // 2. 强制清除 DOM 输入框的值
        const input = teamSelectRef.value.$el?.querySelector("input");
        if (input) {
          input.value = "";
        }
        // 3. 针对某些版本,重置选中标签的内部偏移(防止输入框被挤占)
        if (typeof teamSelectRef.value.resetInputState === "function") {
          teamSelectRef.value.resetInputState();
        }
      }
    }, 50);
  };
  // 审核人
  const handleReviewerIdChange = userId => {
src/views/qualityManagement/finalInspection/components/formDia.vue
@@ -1,120 +1,167 @@
<template>
  <div>
    <el-dialog
        v-model="dialogFormVisible"
        :title="operationType === 'add' ? '新增出厂检验' : '编辑出厂检验'"
        width="70%"
        @close="closeDia"
    >
      <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
    <el-dialog v-model="dialogFormVisible"
               :title="operationType === 'add' ? '新增出厂检验' : '编辑出厂检验'"
               width="70%"
               @close="closeDia">
      <el-form :model="form"
               label-width="140px"
               label-position="top"
               :rules="rules"
               ref="formRef">
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="产品名称:" prop="productId">
              <el-tree-select
                  v-model="form.productId"
                  placeholder="请选择"
                  clearable
                  check-strictly
                  @change="getModels"
                  :data="productOptions"
                  :render-after-expand="false"
                  :disabled="operationType === 'edit'"
                  style="width: 100%"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="规格型号:" prop="productModelId">
              <el-select v-model="form.productModelId" placeholder="请选择" clearable :disabled="operationType === 'edit'"
                         filterable readonly @change="handleChangeModel">
                <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" />
            <el-form-item label="工序:"
                          prop="process">
              <el-select v-model="form.process"
                         placeholder="请选择工序"
                         clearable
                         :disabled="processQuantityDisabled"
                         @change="handleProcessChange"
                         style="width: 100%">
                <el-option v-for="item in processList"
                           :key="item.name"
                           :label="item.name"
                           :value="item.name" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="指标选择:" prop="testStandardId">
              <el-select
                v-model="form.testStandardId"
                placeholder="请选择指标"
                clearable
                @change="handleTestStandardChange"
                style="width: 100%"
              >
                <el-option
                  v-for="item in testStandardOptions"
                  :key="item.id"
                  :label="item.standardName || item.standardNo"
                  :value="item.id"
                />
            <el-form-item label="产品名称:"
                          prop="productId">
              <el-tree-select v-model="form.productId"
                              placeholder="请选择"
                              clearable
                              check-strictly
                              @change="getModels"
                              :data="productOptions"
                              :render-after-expand="false"
                              :disabled="operationType === 'edit'"
                              style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="规格型号:"
                          prop="productModelId">
              <el-select v-model="form.productModelId"
                         placeholder="请选择"
                         clearable
                         :disabled="operationType === 'edit'"
                         filterable
                         readonly
                         @change="handleChangeModel">
                <el-option v-for="item in modelOptions"
                           :key="item.id"
                           :label="item.model"
                           :value="item.id" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="指标选择:"
                          prop="testStandardId">
              <el-select v-model="form.testStandardId"
                         placeholder="请选择指标"
                         clearable
                         @change="handleTestStandardChange"
                         style="width: 100%">
                <el-option v-for="item in testStandardOptions"
                           :key="item.id"
                           :label="item.standardName || item.standardNo"
                           :value="item.id" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="单位:" prop="unit">
              <el-input v-model="form.unit" placeholder="请输入" disabled/>
            <el-form-item label="单位:"
                          prop="unit">
              <el-input v-model="form.unit"
                        placeholder="请输入"
                        disabled />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="数量:" prop="quantity">
              <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请输入" clearable :precision="2" :disabled="quantityDisabled"/>
            <el-form-item label="数量:"
                          prop="quantity">
              <el-input-number :step="0.01"
                               :min="0"
                               style="width: 100%"
                               v-model="form.quantity"
                               placeholder="请输入"
                               clearable
                               :precision="2"
                               :disabled="quantityDisabled" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="检测单位:" prop="checkCompany">
              <el-input v-model="form.checkCompany" placeholder="请输入" clearable/>
            <el-form-item label="检测单位:"
                          prop="checkCompany">
              <el-input v-model="form.checkCompany"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="检测结果:" prop="checkResult">
            <el-form-item label="检测结果:"
                          prop="checkResult">
              <el-select v-model="form.checkResult">
                <el-option label="合格" value="合格" />
                <el-option label="不合格" value="不合格" />
                <el-option label="合格"
                           value="合格" />
                <el-option label="不合格"
                           value="不合格" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="检验员:" prop="checkName">
                            <el-select v-model="form.checkName" placeholder="请选择" clearable filterable>
                                <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
                                                     :value="item.nickName"/>
                            </el-select>
            <el-form-item label="检验员:"
                          prop="checkName">
              <el-select v-model="form.checkName"
                         placeholder="请选择"
                         clearable
                         filterable>
                <el-option v-for="item in userList"
                           :key="item.nickName"
                           :label="item.nickName"
                           :value="item.nickName" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="检测日期:" prop="checkTime">
              <el-date-picker
                  v-model="form.checkTime"
                  type="date"
                  placeholder="请选择日期"
                  value-format="YYYY-MM-DD"
                  format="YYYY-MM-DD"
                  clearable
                  style="width: 100%"
              />
            <el-form-item label="检测日期:"
                          prop="checkTime">
              <el-date-picker v-model="form.checkTime"
                              type="date"
                              placeholder="请选择日期"
                              value-format="YYYY-MM-DD"
                              format="YYYY-MM-DD"
                              clearable
                              style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
            <PIMTable
                rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :tableLoading="tableLoading"
                height="400"
            >
                <template #slot="{ row }">
                    <el-input v-model="row.testValue" clearable/>
                </template>
            </PIMTable>
      <PIMTable rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :tableLoading="tableLoading"
                height="400">
        <template #slot="{ row }">
          <el-input v-model="row.testValue"
                    clearable />
        </template>
      </PIMTable>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button type="primary"
                     @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
@@ -123,315 +170,396 @@
</template>
<script setup>
import {ref, reactive, toRefs, computed, getCurrentInstance, nextTick} from "vue";
import {getOptions} from "@/api/procurementManagement/procurementLedger.js";
import {modelList, productTreeList} from "@/api/basicData/product.js";
import {qualityInspectAdd, qualityInspectUpdate} from "@/api/qualityManagement/rawMaterialInspection.js";
import {userListNoPage} from "@/api/system/user.js";
import {qualityInspectDetailByProductId, getQualityTestStandardParamByTestStandardId} from "@/api/qualityManagement/metricMaintenance.js";
import {qualityInspectParamInfo} from "@/api/qualityManagement/qualityInspectParam.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
  import {
    ref,
    reactive,
    toRefs,
    computed,
    getCurrentInstance,
    nextTick,
  } from "vue";
  import { getOptions } from "@/api/procurementManagement/procurementLedger.js";
  import { modelList, productTreeList } from "@/api/basicData/product.js";
  import {
    qualityInspectAdd,
    qualityInspectUpdate,
  } from "@/api/qualityManagement/rawMaterialInspection.js";
  import { userListNoPage } from "@/api/system/user.js";
  import {
    qualityInspectDetailByProductId,
    getQualityTestStandardParamByTestStandardId,
  } from "@/api/qualityManagement/metricMaintenance.js";
  import { qualityInspectParamInfo } from "@/api/qualityManagement/qualityInspectParam.js";
  import { list } from "@/api/productionManagement/productionProcess";
  const { proxy } = getCurrentInstance();
  const emit = defineEmits(["close"]);
const dialogFormVisible = ref(false);
const operationType = ref('')
const data = reactive({
  form: {
    checkTime: "",
    process: "",
    checkName: "",
    productName: "",
    productId: "",
    productModelId: "",
    model: "",
    testStandardId: "",
    unit: "",
    quantity: "",
    checkCompany: "",
    checkResult: "",
  },
  rules: {
    checkTime: [{ required: true, message: "请输入", trigger: "blur" },],
    process: [{ required: true, message: "请输入", trigger: "blur" }],
    checkName: [{ required: false, message: "请输入", trigger: "blur" }],
    productId: [{ required: true, message: "请输入", trigger: "blur" }],
    productModelId: [{ required: true, message: "请选择", trigger: "change" }],
    testStandardId: [{required: false, message: "请选择指标", trigger: "change"}],
    unit: [{ required: false, message: "请输入", trigger: "blur" }],
    quantity: [{ required: true, message: "请输入", trigger: "blur" }],
    checkCompany: [{ required: false, message: "请输入", trigger: "blur" }],
    checkResult: [{ required: true, message: "请输入", trigger: "change" }],
  },
});
const { form, rules } = toRefs(data);
// 编辑时:productMainId 或 purchaseLedgerId 任一有值则数量置灰
const quantityDisabled = computed(() => {
  const v = form.value || {};
  return !!(v.productMainId != null || v.purchaseLedgerId != null);
});
const supplierList = ref([]);
const productOptions = ref([]);
const tableColumn = ref([
    {
        label: "指标",
        prop: "parameterItem",
    },
    {
        label: "单位",
        prop: "unit",
    },
    {
        label: "标准值",
        prop: "standardValue",
    },
    {
        label: "内控值",
        prop: "controlValue",
    },
    {
        label: "检验值",
        prop: "testValue",
        dataType: 'slot',
        slot: 'slot',
    },
]);
const tableData = ref([]);
const tableLoading = ref(false);
const userList = ref([]);
const currentProductId = ref(0);
const testStandardOptions = ref([]); // 指标选择下拉框数据
const modelOptions = ref([]);
// 打开弹框
const openDialog = async (type, row, defaultCheckResult = "", defaultCheckName = "") => {
  operationType.value = type;
  dialogFormVisible.value = true;
  // 先清空表单验证状态,避免闪烁
  await nextTick();
  proxy.$refs.formRef?.clearValidate();
  // 并行加载基础数据
  const [userListsRes] = await Promise.all([
    userListNoPage(),
    getProductOptions(),
    getOptions().then((res) => {
      supplierList.value = res.data;
    })
  const dialogFormVisible = ref(false);
  const operationType = ref("");
  const data = reactive({
    form: {
      checkTime: "",
      process: "",
      checkName: "",
      productName: "",
      productId: "",
      productModelId: "",
      model: "",
      testStandardId: "",
      unit: "",
      quantity: "",
      checkCompany: "",
      checkResult: "",
    },
    rules: {
      checkTime: [{ required: true, message: "请输入", trigger: "blur" }],
      process: [{ required: true, message: "请选择工序", trigger: "change" }],
      checkName: [{ required: false, message: "请输入", trigger: "blur" }],
      productId: [{ required: true, message: "请输入", trigger: "blur" }],
      productModelId: [{ required: true, message: "请选择", trigger: "change" }],
      testStandardId: [
        { required: false, message: "请选择指标", trigger: "change" },
      ],
      unit: [{ required: false, message: "请输入", trigger: "blur" }],
      quantity: [{ required: true, message: "请输入", trigger: "blur" }],
      checkCompany: [{ required: false, message: "请输入", trigger: "blur" }],
      checkResult: [{ required: true, message: "请输入", trigger: "change" }],
    },
  });
  const { form, rules } = toRefs(data);
  // 编辑时:productMainId 或 purchaseLedgerId 任一有值则数量置灰
  const quantityDisabled = computed(() => {
    const v = form.value || {};
    return !!(v.productMainId != null || v.purchaseLedgerId != null);
  });
  const processQuantityDisabled = computed(() => {
    const v = form.value || {};
    return !!(v.productMainId != null || v.purchaseLedgerId != null);
  });
  const processList = ref([]); // 工序下拉列表(工序名称 name)
  const supplierList = ref([]);
  const productOptions = ref([]);
  const tableColumn = ref([
    {
      label: "指标",
      prop: "parameterItem",
    },
    {
      label: "单位",
      prop: "unit",
    },
    {
      label: "标准值",
      prop: "standardValue",
    },
    {
      label: "内控值",
      prop: "controlValue",
    },
    {
      label: "检验值",
      prop: "testValue",
      dataType: "slot",
      slot: "slot",
    },
  ]);
  // 筛选 roleIds 包含 106 的用户
  userList.value = (userListsRes.data || []).filter(user => {
    const roleIds = user.roleIds || [];
    return roleIds.includes(106) || roleIds.includes('106');
  });
  const tableData = ref([]);
  const tableLoading = ref(false);
  const userList = ref([]);
  const currentProductId = ref(0);
  const testStandardOptions = ref([]); // 指标选择下拉框数据
  const modelOptions = ref([]);
  form.value = {}
  testStandardOptions.value = [];
  tableData.value = [];
  // 打开弹框
  const openDialog = async (
    type,
    row,
    defaultCheckResult = "",
    defaultCheckName = ""
  ) => {
    operationType.value = type;
    dialogFormVisible.value = true;
    await nextTick();
    proxy.$refs.formRef?.clearValidate?.();
  if (operationType.value === 'edit') {
    // 先保存 testStandardId,避免被清空
    const savedTestStandardId = row.testStandardId;
    // 先设置表单数据,但暂时清空 testStandardId,等选项加载完成后再设置
    form.value = {...row, testStandardId: ''}
    // 如果传入了默认检测结果,覆盖row中的值
    if (defaultCheckResult) {
      form.value.checkResult = defaultCheckResult;
    }
    // 如果传入了默认检验员,覆盖row中的值(优先使用传入的检验员)
    console.log('formDia checkName debug:', { defaultCheckName, rowCheckName: row.checkName });
    form.value.checkName = defaultCheckName || row.checkName || "";
    currentProductId.value = row.productId || 0
    // 清空验证状态,避免数据加载过程中的校验闪烁
    nextTick(() => {
      proxy.$refs.formRef?.clearValidate();
    const [userListsRes] = await Promise.all([
      userListNoPage(),
      getProductOptions(),
      getOptions().then(res => {
        supplierList.value = res.data;
      }),
      list()
        .then(res => {
          processList.value = res.data || [];
        })
        .catch(() => {
          processList.value = [];
        }),
    ]);
    // 筛选 roleIds 包含 106 的用户
    userList.value = (userListsRes.data || []).filter(user => {
      const roleIds = user.roleIds || [];
      return roleIds.includes(106) || roleIds.includes("106");
    });
    // 编辑模式下,并行加载规格型号和指标选项
    if (currentProductId.value) {
      // 设置产品名称
      form.value.productName = findNodeById(productOptions.value, currentProductId.value);
      // 并行加载规格型号和指标选项
      const params = {
        productId: currentProductId.value,
        inspectType: 2
      };
      Promise.all([
        modelList({ id: currentProductId.value }),
        qualityInspectDetailByProductId(params)
      ]).then(([modelRes, testStandardRes]) => {
        // 设置规格型号选项
        modelOptions.value = modelRes || [];
        // 如果表单中已有 productModelId,设置对应的 model 和 unit
        if (form.value.productModelId && modelOptions.value.length > 0) {
          const selectedModel = modelOptions.value.find(item => item.id == form.value.productModelId);
          if (selectedModel) {
            form.value.model = selectedModel.model || '';
            form.value.unit = selectedModel.unit || '';
          }
        }
        // 设置指标选项
        testStandardOptions.value = testStandardRes.data || [];
        // 设置 testStandardId 并加载参数列表
        nextTick(() => {
          if (savedTestStandardId) {
            // 确保类型匹配(item.id 可能是数字或字符串)
            const matchedOption = testStandardOptions.value.find(item =>
              item.id == savedTestStandardId || String(item.id) === String(savedTestStandardId)
            );
            if (matchedOption) {
              // 确保使用匹配项的 id(保持类型一致)
              form.value.testStandardId = matchedOption.id;
            } else {
              // 如果找不到匹配项,尝试直接使用原值
              console.warn('未找到匹配的指标选项,testStandardId:', savedTestStandardId, '可用选项:', testStandardOptions.value);
              form.value.testStandardId = savedTestStandardId;
            }
          }
          // 编辑场景保留已有检验值,直接拉取原参数数据
          getQualityInspectParamList(row.id);
        });
      });
    } else {
      getQualityInspectParamList(row.id);
    }
  }
}
const getProductOptions = () => {
  return productTreeList().then((res) => {
    productOptions.value = convertIdToValue(res);
  });
};
const getModels = (value) => {
  form.value.productModelId = undefined;
  form.value.unit = undefined;
  modelOptions.value = [];
  currentProductId.value = value
  form.value.productName = findNodeById(productOptions.value, value);
  modelList({ id: value }).then((res) => {
    modelOptions.value = res;
  })
  if (currentProductId.value) {
    getList();
  }
};
const handleChangeModel = (value) => {
  form.value.model = modelOptions.value.find(item => item.id == value)?.model || '';
  form.value.unit = modelOptions.value.find(item => item.id == value)?.unit || '';
}
const findNodeById = (nodes, productId) => {
  for (let i = 0; i < nodes.length; i++) {
    if (nodes[i].value === productId) {
      return nodes[i].label; // 找到节点,返回该节点
    }
    if (nodes[i].children && nodes[i].children.length > 0) {
      const foundNode = findNodeById(nodes[i].children, productId);
      if (foundNode) {
        return foundNode; // 在子节点中找到,返回该节点
      }
    }
  }
  return null; // 没有找到节点,返回null
};
function convertIdToValue(data) {
  return data.map((item) => {
    const { id, children, ...rest } = item;
    const newItem = {
      ...rest,
      value: id, // 将 id 改为 value
    currentProductId.value = 0;
    modelOptions.value = [];
    form.value = {
      checkTime: "",
      process: "",
      checkName: defaultCheckName || "",
      productName: "",
      productId: "",
      productModelId: "",
      model: "",
      testStandardId: "",
      unit: "",
      quantity: "",
      checkCompany: "",
      checkResult: defaultCheckResult || "",
    };
    if (children && children.length > 0) {
      newItem.children = convertIdToValue(children);
    }
    return newItem;
  });
}
// 提交产品表单
const submitForm = () => {
  proxy.$refs.formRef.validate(valid => {
    if (valid) {
      form.value.inspectType = 2
            if (operationType.value === "add") {
                tableData.value.forEach((item) => {
                    delete item.id
                })
            }
            const data = {...form.value, qualityInspectParams: tableData.value}
      if (operationType.value === "add") {
        qualityInspectAdd(data).then(res => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
        })
      } else {
        qualityInspectUpdate(data).then(res => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
        })
      }
    }
  })
}
const getList = () => {
  if (!currentProductId.value) {
    testStandardOptions.value = [];
    tableData.value = [];
    return;
  }
  let params = {
    productId: currentProductId.value,
    inspectType: 2
  }
    qualityInspectDetailByProductId(params).then(res => {
        // 保存下拉框选项数据
        testStandardOptions.value = res.data || [];
        // 清空表格数据,等待用户选择指标
        tableData.value = [];
        // 清空指标选择
        form.value.testStandardId = '';
    })
}
// 指标选择变化处理
const handleTestStandardChange = (testStandardId) => {
  if (!testStandardId) {
    if (operationType.value === "edit") {
      // 先保存 testStandardId,避免被清空
      const savedTestStandardId = row.testStandardId;
      // 先设置表单数据,但暂时清空 testStandardId,等选项加载完成后再设置
      form.value = { ...row, testStandardId: "" };
      // 如果传入了默认检测结果,覆盖row中的值
      if (defaultCheckResult) {
        form.value.checkResult = defaultCheckResult;
      }
      // 如果传入了默认检验员,覆盖row中的值(优先使用传入的检验员)
      console.log("formDia checkName debug:", {
        defaultCheckName,
        rowCheckName: row.checkName,
      });
      form.value.checkName = defaultCheckName || row.checkName || "";
      currentProductId.value = row.productId || 0;
      // 清空验证状态,避免数据加载过程中的校验闪烁
      nextTick(() => {
        proxy.$refs.formRef?.clearValidate();
      });
      // 编辑模式下,并行加载规格型号和指标选项
      if (currentProductId.value) {
        // 设置产品名称
        form.value.productName = findNodeById(
          productOptions.value,
          currentProductId.value
        );
        // 并行加载规格型号和指标选项
        const params = {
          productId: currentProductId.value,
          inspectType: 2,
          process: form.value.process || "",
        };
        Promise.all([
          modelList({ id: currentProductId.value }),
          qualityInspectDetailByProductId(params),
        ]).then(([modelRes, testStandardRes]) => {
          // 设置规格型号选项
          modelOptions.value = modelRes || [];
          // 如果表单中已有 productModelId,设置对应的 model 和 unit
          if (form.value.productModelId && modelOptions.value.length > 0) {
            const selectedModel = modelOptions.value.find(
              item => item.id == form.value.productModelId
            );
            if (selectedModel) {
              form.value.model = selectedModel.model || "";
              form.value.unit = selectedModel.unit || "";
            }
          }
          // 设置指标选项
          testStandardOptions.value = testStandardRes.data || [];
          // 设置 testStandardId 并加载参数列表
          nextTick(() => {
            if (savedTestStandardId) {
              // 确保类型匹配(item.id 可能是数字或字符串)
              const matchedOption = testStandardOptions.value.find(
                item =>
                  item.id == savedTestStandardId ||
                  String(item.id) === String(savedTestStandardId)
              );
              if (matchedOption) {
                // 确保使用匹配项的 id(保持类型一致)
                form.value.testStandardId = matchedOption.id;
              } else {
                // 如果找不到匹配项,尝试直接使用原值
                console.warn(
                  "未找到匹配的指标选项,testStandardId:",
                  savedTestStandardId,
                  "可用选项:",
                  testStandardOptions.value
                );
                form.value.testStandardId = savedTestStandardId;
              }
            }
            // 编辑场景保留已有检验值,直接拉取原参数数据
            getQualityInspectParamList(row.id);
          });
        });
      } else {
        getQualityInspectParamList(row.id);
      }
    }
  };
  const getProductOptions = () => {
    return productTreeList().then(res => {
      productOptions.value = convertIdToValue(res);
    });
  };
  const getModels = value => {
    form.value.productModelId = undefined;
    form.value.unit = undefined;
    modelOptions.value = [];
    currentProductId.value = value;
    form.value.productName = findNodeById(productOptions.value, value);
    modelList({ id: value }).then(res => {
      modelOptions.value = res;
    });
    if (currentProductId.value) {
      getList();
    }
  };
  const handleProcessChange = () => {
    testStandardOptions.value = [];
    tableData.value = [];
    return;
    form.value.testStandardId = "";
    if (currentProductId.value) {
      getList();
    }
  };
  const handleChangeModel = value => {
    form.value.model =
      modelOptions.value.find(item => item.id == value)?.model || "";
    form.value.unit =
      modelOptions.value.find(item => item.id == value)?.unit || "";
  };
  const findNodeById = (nodes, productId) => {
    for (let i = 0; i < nodes.length; i++) {
      if (nodes[i].value === productId) {
        return nodes[i].label; // 找到节点,返回该节点
      }
      if (nodes[i].children && nodes[i].children.length > 0) {
        const foundNode = findNodeById(nodes[i].children, productId);
        if (foundNode) {
          return foundNode; // 在子节点中找到,返回该节点
        }
      }
    }
    return null; // 没有找到节点,返回null
  };
  function convertIdToValue(data) {
    return data.map(item => {
      const { id, children, ...rest } = item;
      const newItem = {
        ...rest,
        value: id, // 将 id 改为 value
      };
      if (children && children.length > 0) {
        newItem.children = convertIdToValue(children);
      }
      return newItem;
    });
  }
  tableLoading.value = true;
  getQualityTestStandardParamByTestStandardId(testStandardId).then(res => {
    tableData.value = res.data || [];
  }).catch(error => {
    console.error('获取标准参数失败:', error);
  // 提交产品表单
  const submitForm = () => {
    proxy.$refs.formRef.validate(valid => {
      if (valid) {
        form.value.inspectType = 2;
        if (operationType.value === "add") {
          tableData.value.forEach(item => {
            delete item.id;
          });
        }
        const processName = form.value.process || "";
        const data = {
          ...form.value,
          process: processName,
          qualityInspectParams: tableData.value,
        };
        if (operationType.value === "add") {
          qualityInspectAdd(data).then(res => {
            proxy.$modal.msgSuccess("提交成功");
            closeDia();
          });
        } else {
          qualityInspectUpdate(data).then(res => {
            proxy.$modal.msgSuccess("提交成功");
            closeDia();
          });
        }
      }
    });
  };
  const getList = () => {
    if (!currentProductId.value) {
      testStandardOptions.value = [];
      tableData.value = [];
      return;
    }
    let params = {
      productId: currentProductId.value,
      inspectType: 2,
      process: form.value.process || "",
    };
    qualityInspectDetailByProductId(params).then(res => {
      // 保存下拉框选项数据
      testStandardOptions.value = res.data || [];
      // 清空表格数据,等待用户选择指标
      tableData.value = [];
      // 清空指标选择
      form.value.testStandardId = "";
    });
  };
  // 指标选择变化处理
  const handleTestStandardChange = testStandardId => {
    if (!testStandardId) {
      tableData.value = [];
      return;
    }
    tableLoading.value = true;
    getQualityTestStandardParamByTestStandardId(testStandardId)
      .then(res => {
        tableData.value = res.data || [];
      })
      .catch(error => {
        console.error("获取标准参数失败:", error);
        tableData.value = [];
      })
      .finally(() => {
        tableLoading.value = false;
      });
  };
  const getQualityInspectParamList = id => {
    qualityInspectParamInfo(id).then(res => {
      tableData.value = res.data;
    });
  };
  // 关闭弹框
  const closeDia = () => {
    proxy.resetForm("formRef");
    tableData.value = [];
  }).finally(() => {
    tableLoading.value = false;
  })
}
const getQualityInspectParamList = (id) => {
    qualityInspectParamInfo(id).then(res => {
        tableData.value = res.data;
    })
}
// 关闭弹框
const closeDia = () => {
  proxy.resetForm("formRef");
  tableData.value = [];
  testStandardOptions.value = [];
  form.value.testStandardId = '';
  dialogFormVisible.value = false;
  emit('close')
};
defineExpose({
  openDialog,
});
    testStandardOptions.value = [];
    form.value.testStandardId = "";
    dialogFormVisible.value = false;
    emit("close");
  };
  defineExpose({
    openDialog,
  });
</script>
<style scoped>
</style>
</style>
src/views/qualityManagement/finalInspection/index.vue
@@ -2,50 +2,73 @@
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title">产品名称:</span>
        <el-input
            v-model="searchForm.productName"
            style="width: 240px"
            placeholder="请输入产品名称搜索"
            @change="handleQuery"
            clearable
            :prefix-icon="Search"
        />
        <span  style="margin-left: 10px" class="search_title">检测日期:</span>
        <el-date-picker  v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
                         placeholder="请选择" clearable @change="changeDaterange" />
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
        >搜索</el-button
        >
        <span class="search_title">工序:</span>
        <el-input v-model="searchForm.process"
                  style="width: 240px"
                  placeholder="请输入工序搜索"
                  @change="handleQuery"
                  clearable
                  :prefix-icon="Search" />
        <span style="margin-left: 10px"
              class="search_title">产品名称:</span>
        <el-input v-model="searchForm.productName"
                  style="width: 240px"
                  placeholder="请输入产品名称搜索"
                  @change="handleQuery"
                  clearable
                  :prefix-icon="Search" />
        <span style="margin-left: 10px"
              class="search_title">检测日期:</span>
        <el-date-picker v-model="searchForm.entryDate"
                        value-format="YYYY-MM-DD"
                        format="YYYY-MM-DD"
                        type="daterange"
                        placeholder="请选择"
                        clearable
                        @change="changeDaterange" />
        <el-button type="primary"
                   @click="handleQuery"
                   style="margin-left: 10px">搜索</el-button>
      </div>
      <div>
        <el-button type="primary" @click="openForm('add')">新增</el-button>
        <el-button type="primary"
                   @click="openForm('add')">新增</el-button>
        <el-button @click="handleOut">导出</el-button>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
        <el-button type="danger"
                   plain
                   @click="handleDelete">删除</el-button>
      </div>
    </div>
    <div class="table_list">
      <PIMTable
          rowKey="id"
          :column="tableColumn"
          :tableData="tableData"
          :page="page"
          :isSelection="true"
          @selection-change="handleSelectionChange"
          :tableLoading="tableLoading"
          @pagination="pagination"
          :total="page.total"
      ></PIMTable>
      <PIMTable rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :isSelection="true"
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
                :total="page.total"></PIMTable>
    </div>
    <InspectionFormDia ref="inspectionFormDia" @close="handleQuery"></InspectionFormDia>
    <FormDia ref="formDia" @close="handleQuery"></FormDia>
    <files-dia ref="filesDia" @close="handleQuery"></files-dia>
    <InspectionFormDia ref="inspectionFormDia"
                       @close="handleQuery"></InspectionFormDia>
    <FormDia ref="formDia"
             @close="handleQuery"></FormDia>
    <files-dia ref="filesDia"
               @close="handleQuery"></files-dia>
    <!-- 检验结果选择对话框 -->
    <el-dialog v-model="quickCheckVisible" title="检验" width="40%" @close="closeQuickCheck">
      <el-form :model="quickCheckForm" label-width="140px" label-position="top" ref="quickCheckRef">
    <el-dialog v-model="quickCheckVisible"
               title="检验"
               width="40%"
               @close="closeQuickCheck">
      <el-form :model="quickCheckForm"
               label-width="140px"
               label-position="top"
               ref="quickCheckRef">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="检测结果:" required>
            <el-form-item label="检测结果:"
                          required>
              <el-radio-group v-model="quickCheckForm.checkResult">
                <el-radio value="合格">合格</el-radio>
                <el-radio value="不合格">不合格</el-radio>
@@ -53,42 +76,44 @@
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="检验员:" required>
              <el-select v-model="quickCheckForm.checkName" placeholder="请选择" clearable style="width: 100%">
                <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName"/>
            <el-form-item label="检验员:"
                          required>
              <el-select v-model="quickCheckForm.checkName"
                         placeholder="请选择"
                         clearable
                         style="width: 100%">
                <el-option v-for="item in userList"
                           :key="item.nickName"
                           :label="item.nickName"
                           :value="item.nickName" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="检测日期:" required>
              <el-date-picker
                v-model="quickCheckForm.checkTime"
                type="date"
                placeholder="请选择日期"
                value-format="YYYY-MM-DD"
                format="YYYY-MM-DD"
                clearable
                style="width: 100%"
              />
            <el-form-item label="检测日期:"
                          required>
              <el-date-picker v-model="quickCheckForm.checkTime"
                              type="date"
                              placeholder="请选择日期"
                              value-format="YYYY-MM-DD"
                              format="YYYY-MM-DD"
                              clearable
                              style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="指标选择:">
              <el-select
                v-model="quickCheckForm.testStandardId"
                placeholder="请选择指标"
                clearable
                @change="handleTestStandardChange"
                style="width: 100%"
              >
                <el-option
                  v-for="item in testStandardOptions"
                  :key="item.id"
                  :label="item.standardName || item.standardNo"
                  :value="item.id"
                />
              <el-select v-model="quickCheckForm.testStandardId"
                         placeholder="请选择指标"
                         clearable
                         @change="handleTestStandardChange"
                         style="width: 100%">
                <el-option v-for="item in testStandardOptions"
                           :key="item.id"
                           :label="item.standardName || item.standardNo"
                           :value="item.id" />
              </el-select>
            </el-form-item>
          </el-col>
@@ -96,344 +121,380 @@
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="检测单位:">
              <el-input v-model="quickCheckForm.checkCompany" placeholder="请输入" clearable style="width: 100%"/>
              <el-input v-model="quickCheckForm.checkCompany"
                        placeholder="请输入"
                        clearable
                        style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <!-- 检验值表格 -->
        <el-form-item label="检验值:" v-if="quickCheckTableData.length > 0">
          <PIMTable
            rowKey="id"
            :column="quickCheckTableColumn"
            :tableData="quickCheckTableData"
            :tableLoading="quickCheckTableLoading"
            height="200"
          >
        <el-form-item label="检验值:"
                      v-if="quickCheckTableData.length > 0">
          <PIMTable rowKey="id"
                    :column="quickCheckTableColumn"
                    :tableData="quickCheckTableData"
                    :tableLoading="quickCheckTableLoading"
                    height="200">
            <template #slot="{ row }">
              <el-input v-model="row.testValue" clearable placeholder="请输入检验值"/>
              <el-input v-model="row.testValue"
                        clearable
                        placeholder="请输入检验值" />
            </template>
          </PIMTable>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleQuickCheckConfirm">确认</el-button>
          <el-button type="primary"
                     @click="handleQuickCheckConfirm">确认</el-button>
          <el-button @click="closeQuickCheck">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <el-dialog v-model="dialogFormVisible" title="编辑检验员" width="30%"
                             @close="closeDia">
                <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
                    <el-form-item label="检验员:" prop="checkName">
                        <el-select v-model="form.checkName" placeholder="请选择" clearable>
                            <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
                                                 :value="item.nickName"/>
                        </el-select>
                    </el-form-item>
                </el-form>
                <template #footer>
                    <div class="dialog-footer">
                        <el-button type="primary" @click="submitForm">确认</el-button>
                        <el-button @click="closeDia">取消</el-button>
                    </div>
                </template>
            </el-dialog>
    <el-dialog v-model="dialogFormVisible"
               title="编辑检验员"
               width="30%"
               @close="closeDia">
      <el-form :model="form"
               label-width="140px"
               label-position="top"
               :rules="rules"
               ref="formRef">
        <el-form-item label="检验员:"
                      prop="checkName">
          <el-select v-model="form.checkName"
                     placeholder="请选择"
                     clearable>
            <el-option v-for="item in userList"
                       :key="item.nickName"
                       :label="item.nickName"
                       :value="item.nickName" />
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { Search } from "@element-plus/icons-vue";
import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue";
import InspectionFormDia from "@/views/qualityManagement/finalInspection/components/inspectionFormDia.vue";
import FormDia from "@/views/qualityManagement/finalInspection/components/formDia.vue";
import {ElMessageBox} from "element-plus";
import {
    downloadQualityInspect,
    qualityInspectDel,
    qualityInspectDetail,
    qualityInspectListPage, qualityInspectUpdate,
    submitQualityInspect,
    qualityInspectExportNew
} from "@/api/qualityManagement/rawMaterialInspection.js";
import {qualityInspectDetailByProductId, getQualityTestStandardParamByTestStandardId} from "@/api/qualityManagement/metricMaintenance.js";
import FilesDia from "@/views/qualityManagement/finalInspection/components/filesDia.vue";
import dayjs from "dayjs";
import {userListNoPage} from "@/api/system/user.js";
import useUserStore from "@/store/modules/user";
  import { Search } from "@element-plus/icons-vue";
  import {
    onMounted,
    ref,
    reactive,
    toRefs,
    getCurrentInstance,
    nextTick,
  } from "vue";
  import InspectionFormDia from "@/views/qualityManagement/finalInspection/components/inspectionFormDia.vue";
  import FormDia from "@/views/qualityManagement/finalInspection/components/formDia.vue";
  import { ElMessageBox } from "element-plus";
  import {
    downloadQualityInspect,
    qualityInspectDel,
    qualityInspectDetail,
    qualityInspectListPage,
    qualityInspectUpdate,
    submitQualityInspect,
    qualityInspectExportNew,
  } from "@/api/qualityManagement/rawMaterialInspection.js";
  import {
    qualityInspectDetailByProductId,
    getQualityTestStandardParamByTestStandardId,
  } from "@/api/qualityManagement/metricMaintenance.js";
  import FilesDia from "@/views/qualityManagement/finalInspection/components/filesDia.vue";
  import dayjs from "dayjs";
  import { userListNoPage } from "@/api/system/user.js";
  import useUserStore from "@/store/modules/user";
const data = reactive({
  searchForm: {
    productName: "",
    entryDate: undefined, // 录入日期
    entryDateStart: undefined,
    entryDateEnd: undefined,
  },
    rules: {
        checkName: [{required: true, message: "请选择", trigger: "change"}],
    },
});
const { searchForm } = toRefs(data);
const tableColumn = ref([
  {
    label: "检测日期",
    prop: "checkTime",
    width: 120
  },
  {
    label: "生产工单号",
    prop: "workOrderNo",
    width: 120
  },
  {
    label: "检验员",
    prop: "checkName",
  },
  {
    label: "产品名称",
    prop: "productName",
  },
  {
    label: "规格型号",
    prop: "model",
  },
  {
    label: "单位",
    prop: "unit",
  },
  {
    label: "数量",
    prop: "quantity",
    width: 100
  },
  {
    label: "检测单位",
    prop: "checkCompany",
    width: 120
  },
  {
    label: "检测结果",
    prop: "checkResult",
    dataType: "tag",
    formatType: (params) => {
      if (params == '不合格') {
        return "danger";
      } else if (params == '合格') {
        return "success";
      } else {
        return null;
      }
  const data = reactive({
    searchForm: {
      process: "",
      productName: "",
      entryDate: undefined, // 录入日期
      entryDateStart: undefined,
      entryDateEnd: undefined,
    },
  },
    {
        label: "提交状态",
        prop: "inspectState",
        formatData: (params) => {
            if (params) {
                return "已提交";
            } else {
                return "未提交";
            }
        },
    },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 280,
    operation: [
      {
        name: "检验",
        type: "text",
        clickFun: (row) => {
          openQuickCheck(row);
        },
                disabled: (row) => {
                    // 已提交则禁用
                    if (row.inspectState == 1) return true;
                    // 如果检验员有值,只有当前登录用户能编辑
                    if (row.checkName) {
                        return row.checkName !== userStore.nickName;
                    }
                    return false;
                }
    rules: {
      checkName: [{ required: true, message: "请选择", trigger: "change" }],
    },
  });
  const { searchForm } = toRefs(data);
  const tableColumn = ref([
    {
      label: "检测日期",
      prop: "checkTime",
      width: 120,
    },
    {
      label: "生产工单号",
      prop: "workOrderNo",
      width: 120,
    },
    {
      label: "工序",
      prop: "process",
      width: 230,
    },
    {
      label: "检验员",
      prop: "checkName",
    },
    {
      label: "产品名称",
      prop: "productName",
    },
    {
      label: "规格型号",
      prop: "model",
    },
    {
      label: "单位",
      prop: "unit",
    },
    {
      label: "数量",
      prop: "quantity",
      width: 100,
    },
    {
      label: "检测单位",
      prop: "checkCompany",
      width: 120,
    },
    {
      label: "检测结果",
      prop: "checkResult",
      dataType: "tag",
      formatType: params => {
        if (params == "不合格") {
          return "danger";
        } else if (params == "合格") {
          return "success";
        } else {
          return null;
        }
      },
      {
        name: "附件",
        type: "text",
        clickFun: (row) => {
          openFilesFormDia(row);
        },
    },
    {
      label: "提交状态",
      prop: "inspectState",
      formatData: params => {
        if (params) {
          return "已提交";
        } else {
          return "未提交";
        }
      },
                {
                    name: "提交",
                    type: "text",
                    clickFun: (row) => {
                        submit(row.id);
                    },
                    disabled: (row) => {
                        // 已提交则禁用
                        if (row.inspectState == 1) return true;
                        // 如果检验员有值,只有当前登录用户能提交
                        if (row.checkName) {
                            return row.checkName !== userStore.nickName;
                        }
                        return false;
                    }
                },
                {
                    name: "分配检验员",
                    type: "text",
                    clickFun: (row) => {
                        if (!row.checkName) {
                            open(row)
                        } else {
                            proxy.$modal.msgError("检验员已存在");
                        }
                    },
                    disabled: (row) => {
                        return row.inspectState == 1 || row.checkName;
                    }
                },
                {
                    name: "下载",
                    type: "text",
                    clickFun: (row) => {
                        downLoadFile(row);
                    },
                },
    ],
  },
]);
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const currentRow = ref(null)
const page = reactive({
  current: 1,
  size: 100,
  total: 0
});
const formDia = ref()
const filesDia = ref()
const inspectionFormDia = ref()
const { proxy } = getCurrentInstance()
const userStore = useUserStore()
const userList = ref([]);
const form = ref({
    checkName: ""
});
const dialogFormVisible = ref(false);
const quickCheckVisible = ref(false);
const quickCheckForm = ref({
    checkResult: "合格",
    checkName: "",
    checkTime: "",
    testStandardId: "",
    checkCompany: "天津阳光彩印股份有限公司"
});
const quickCheckRef = ref(null);
const testStandardOptions = ref([]); // 指标选择下拉框数据
const quickCheckTableColumn = ref([
  {
    label: "指标",
    prop: "parameterItem",
  },
  {
    label: "单位",
    prop: "unit",
  },
  {
    label: "标准值",
    prop: "standardValue",
  },
  {
    label: "内控值",
    prop: "controlValue",
  },
  {
    label: "检验值",
    prop: "testValue",
    dataType: 'slot',
    slot: 'slot',
  },
]);
const quickCheckTableData = ref([]);
const quickCheckTableLoading = ref(false);
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
      width: 280,
      operation: [
        {
          name: "检验",
          type: "text",
          clickFun: row => {
            openQuickCheck(row);
          },
          disabled: row => {
            // 已提交则禁用
            if (row.inspectState == 1) return true;
            // 如果检验员有值,只有当前登录用户能编辑
            if (row.checkName) {
              return row.checkName !== userStore.nickName;
            }
            return false;
          },
        },
        {
          name: "附件",
          type: "text",
          clickFun: row => {
            openFilesFormDia(row);
          },
        },
        {
          name: "提交",
          type: "text",
          clickFun: row => {
            submit(row.id);
          },
          disabled: row => {
            // 已提交则禁用
            if (row.inspectState == 1) return true;
            // 如果检验员有值,只有当前登录用户能提交
            if (row.checkName) {
              return row.checkName !== userStore.nickName;
            }
            return false;
          },
        },
        {
          name: "分配检验员",
          type: "text",
          clickFun: row => {
            if (!row.checkName) {
              open(row);
            } else {
              proxy.$modal.msgError("检验员已存在");
            }
          },
          disabled: row => {
            return row.inspectState == 1 || row.checkName;
          },
        },
        {
          name: "下载",
          type: "text",
          clickFun: row => {
            downLoadFile(row);
          },
        },
      ],
    },
  ]);
  const tableData = ref([]);
  const selectedRows = ref([]);
  const tableLoading = ref(false);
  const currentRow = ref(null);
  const page = reactive({
    current: 1,
    size: 100,
    total: 0,
  });
  const formDia = ref();
  const filesDia = ref();
  const inspectionFormDia = ref();
  const { proxy } = getCurrentInstance();
  const userStore = useUserStore();
  const userList = ref([]);
  const form = ref({
    checkName: "",
  });
  const dialogFormVisible = ref(false);
  const quickCheckVisible = ref(false);
  const quickCheckForm = ref({
    checkResult: "合格",
    checkName: "",
    checkTime: "",
    testStandardId: "",
    checkCompany: "",
  });
  const quickCheckRef = ref(null);
  const testStandardOptions = ref([]); // 指标选择下拉框数据
  const quickCheckTableColumn = ref([
    {
      label: "指标",
      prop: "parameterItem",
    },
    {
      label: "单位",
      prop: "unit",
    },
    {
      label: "标准值",
      prop: "standardValue",
    },
    {
      label: "内控值",
      prop: "controlValue",
    },
    {
      label: "检验值",
      prop: "testValue",
      dataType: "slot",
      slot: "slot",
    },
  ]);
  const quickCheckTableData = ref([]);
  const quickCheckTableLoading = ref(false);
const changeDaterange = (value) => {
  searchForm.value.entryDateStart = undefined;
  searchForm.value.entryDateEnd = undefined;
  if (value) {
    searchForm.value.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
    searchForm.value.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
  }
  getList();
};
// 查询列表
/** 搜索按钮操作 */
const handleQuery = () => {
  page.current = 1;
  getList();
};
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  const params = { ...searchForm.value, ...page };
  params.entryDate = undefined
  qualityInspectListPage({...params, inspectType: 2}).then(res => {
    tableLoading.value = false;
    tableData.value = res.data.records
    page.total = res.data.total;
  }).catch(err => {
    tableLoading.value = false;
  })
};
// 表格选择数据
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
  const changeDaterange = value => {
    searchForm.value.entryDateStart = undefined;
    searchForm.value.entryDateEnd = undefined;
    if (value) {
      searchForm.value.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
      searchForm.value.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
    }
    getList();
  };
  // 查询列表
  /** 搜索按钮操作 */
  const handleQuery = () => {
    page.current = 1;
    getList();
  };
  const pagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
  };
  const getList = () => {
    tableLoading.value = true;
    const params = { ...searchForm.value, ...page };
    params.entryDate = undefined;
    qualityInspectListPage({ ...params, inspectType: 2 })
      .then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records;
        page.total = res.data.total;
      })
      .catch(err => {
        tableLoading.value = false;
      });
  };
  // 表格选择数据
  const handleSelectionChange = selection => {
    selectedRows.value = selection;
  };
// 打开弹框
const openForm = (type, row) => {
  nextTick(() => {
    formDia.value?.openDialog(type, row)
  })
};
// 打开新增检验弹框
const openInspectionForm = (type, row) => {
  nextTick(() => {
    inspectionFormDia.value?.openDialog(type, row)
  })
};
// 打开附件弹框
const openFilesFormDia = (type, row) => {
  nextTick(() => {
    filesDia.value?.openDialog(type, row)
  })
};
  // 打开弹框
  const openForm = (type, row) => {
    nextTick(() => {
      formDia.value?.openDialog(type, row);
    });
  };
  // 打开新增检验弹框
  const openInspectionForm = (type, row) => {
    nextTick(() => {
      inspectionFormDia.value?.openDialog(type, row);
    });
  };
  // 打开附件弹框
  const openFilesFormDia = (type, row) => {
    nextTick(() => {
      filesDia.value?.openDialog(type, row);
    });
  };
// 删除
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map((item) => item.id);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
  // 删除
  const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
      ids = selectedRows.value.map(item => item.id);
    } else {
      proxy.$modal.msgWarning("请选择数据");
      return;
    }
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        qualityInspectDel(ids).then((res) => {
        qualityInspectDel(ids).then(res => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        });
@@ -441,258 +502,276 @@
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
// 导出
const handleOut = () => {
    // 判断是否有选中数据
    const hasSelected = selectedRows.value.length > 0;
  };
  // 导出
  const handleOut = () => {
    // 判断是否有选中数据
    const hasSelected = selectedRows.value.length > 0;
    const confirmMsg = hasSelected
        ? "选中的内容将被导出,是否确认导出?"
        : "将导出全部数据,是否确认导出?";
    const confirmMsg = hasSelected
      ? "选中的内容将被导出,是否确认导出?"
      : "将导出全部数据,是否确认导出?";
    ElMessageBox.confirm(confirmMsg, "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            // 构建导出参数
            const exportData = {
                inspectType: 2,
                productName: searchForm.value.productName || "",
                entryDateStart: searchForm.value.entryDateStart || "",
                entryDateEnd: searchForm.value.entryDateEnd || "",
            };
    ElMessageBox.confirm(confirmMsg, "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        // 构建导出参数
        const exportData = {
          inspectType: 2,
          process: searchForm.value.process || "",
          productName: searchForm.value.productName || "",
          entryDateStart: searchForm.value.entryDateStart || "",
          entryDateEnd: searchForm.value.entryDateEnd || "",
        };
            // 如果有选中数据,添加ids
            if (hasSelected) {
                exportData.ids = selectedRows.value.map(item => item.id);
            }
        // 如果有选中数据,添加ids
        if (hasSelected) {
          exportData.ids = selectedRows.value.map(item => item.id);
        }
            // 调用新导出接口
            qualityInspectExportNew(exportData).then((blobData) => {
                const blob = new Blob([blobData], {
                    type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
                });
                const downloadUrl = window.URL.createObjectURL(blob);
                const link = document.createElement('a');
                link.href = downloadUrl;
                link.download = '出厂检验.xlsx';
                document.body.appendChild(link);
                link.click();
                document.body.removeChild(link);
                window.URL.revokeObjectURL(downloadUrl);
            }).catch((error) => {
                proxy.$modal.msgError("导出失败");
            });
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
        // 调用新导出接口
        qualityInspectExportNew(exportData)
          .then(blobData => {
            const blob = new Blob([blobData], {
              type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
            });
            const downloadUrl = window.URL.createObjectURL(blob);
            const link = document.createElement("a");
            link.href = downloadUrl;
            link.download = "出厂检验.xlsx";
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            window.URL.revokeObjectURL(downloadUrl);
          })
          .catch(error => {
            proxy.$modal.msgError("导出失败");
          });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  };
// 提价
const submit = async (id) => {
    const res = await submitQualityInspect({id: id})
    if (res.code === 200) {
        proxy.$modal.msgSuccess("提交成功");
        getList();
    }
}
  // 提价
  const submit = async id => {
    const res = await submitQualityInspect({ id: id });
    if (res.code === 200) {
      proxy.$modal.msgSuccess("提交成功");
      getList();
    }
  };
// 关闭弹框
const closeDia = () => {
    proxy.resetForm("formRef");
    dialogFormVisible.value = false;
};
  // 关闭弹框
  const closeDia = () => {
    proxy.resetForm("formRef");
    dialogFormVisible.value = false;
  };
const submitForm = () => {
    if (currentRow.value) {
        const data = {
            ...form.value,
            id: currentRow.value.id
        }
        qualityInspectUpdate(data).then(res => {
            proxy.$modal.msgSuccess("提交成功");
            closeDia();
            getList();
        })
    }
};
  const submitForm = () => {
    if (currentRow.value) {
      const data = {
        ...form.value,
        id: currentRow.value.id,
      };
      qualityInspectUpdate(data).then(res => {
        proxy.$modal.msgSuccess("提交成功");
        closeDia();
        getList();
      });
    }
  };
const open = async (row) => {
    let userLists = await userListNoPage();
    // 筛选 roleIds 包含 106 的用户
    userList.value = (userLists.data || []).filter(user => {
        const roleIds = user.roleIds || [];
        return roleIds.includes(106) || roleIds.includes('106');
    });
    currentRow.value = row
    dialogFormVisible.value = true
}
  const open = async row => {
    let userLists = await userListNoPage();
    // 筛选 roleIds 包含 106 的用户
    userList.value = (userLists.data || []).filter(user => {
      const roleIds = user.roleIds || [];
      return roleIds.includes(106) || roleIds.includes("106");
    });
    currentRow.value = row;
    dialogFormVisible.value = true;
  };
const downLoadFile = (row) => {
    downloadQualityInspect({ id: row.id }).then((blobData) => {
        const blob = new Blob([blobData], {
            type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        })
        const downloadUrl = window.URL.createObjectURL(blob)
  const downLoadFile = row => {
    downloadQualityInspect({ id: row.id }).then(blobData => {
      const blob = new Blob([blobData], {
        type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
      });
      const downloadUrl = window.URL.createObjectURL(blob);
        const link = document.createElement('a')
        link.href = downloadUrl
        link.download = '出厂检验报告.docx'
        document.body.appendChild(link)
        link.click()
      const link = document.createElement("a");
      link.href = downloadUrl;
      link.download = "出厂检验报告.docx";
      document.body.appendChild(link);
      link.click();
        document.body.removeChild(link)
        window.URL.revokeObjectURL(downloadUrl)
    })
};
      document.body.removeChild(link);
      window.URL.revokeObjectURL(downloadUrl);
    });
  };
// 打开检验结果选择对话框
const openQuickCheck = async (row) => {
    // 先调用详情接口获取完整数据(包含指标和检验值)
    let detailData = row;
    try {
        const detailRes = await qualityInspectDetail(row.id);
        if (detailRes.code === 200 && detailRes.data) {
            detailData = detailRes.data;
        }
    } catch (e) {
        console.error("获取检验详情失败", e);
    }
    currentRow.value = detailData;
  // 打开检验结果选择对话框
  const openQuickCheck = async row => {
    // 先调用详情接口获取完整数据(包含指标和检验值)
    let detailData = row;
    try {
      const detailRes = await qualityInspectDetail(row.id);
      if (detailRes.code === 200 && detailRes.data) {
        detailData = detailRes.data;
      }
    } catch (e) {
      console.error("获取检验详情失败", e);
    }
    currentRow.value = detailData;
    // 加载用户列表,筛选 roleIds 包含 106 的用户
    try {
        const userLists = await userListNoPage();
        userList.value = (userLists.data || []).filter(user => {
            const roleIds = user.roleIds || [];
            return roleIds.includes(106) || roleIds.includes('106');
        });
    } catch (e) {
        console.error("加载检验员列表失败", e);
        userList.value = [];
    }
    // 加载指标选项(根据产品ID)
    if (detailData.productId) {
        try {
            const params = {
                productId: detailData.productId,
                inspectType: 2
            };
            const res = await qualityInspectDetailByProductId(params);
            testStandardOptions.value = res.data || [];
        } catch (e) {
            console.error("加载指标选项失败", e);
            testStandardOptions.value = [];
        }
    } else {
        testStandardOptions.value = [];
    }
    // 设置默认值(优先使用详情数据,检验员默认为当前登录用户)
    quickCheckForm.value = {
        checkResult: detailData.checkResult || "合格",
        checkName: detailData.checkName || userStore.nickName || "",
        checkTime: detailData.checkTime || dayjs().format("YYYY-MM-DD"),
        testStandardId: detailData.testStandardId || "",
        checkCompany: detailData.checkCompany || "天津阳光彩印股份有限公司"
    };
    // 如果有检验参数数据,回显到表格
    if (detailData.qualityInspectParams && detailData.qualityInspectParams.length > 0) {
        quickCheckTableData.value = detailData.qualityInspectParams;
    } else {
        quickCheckTableData.value = [];
    }
    quickCheckVisible.value = true;
};
    // 加载用户列表,筛选 roleIds 包含 106 的用户
    try {
      const userLists = await userListNoPage();
      userList.value = (userLists.data || []).filter(user => {
        const roleIds = user.roleIds || [];
        return roleIds.includes(106) || roleIds.includes("106");
      });
    } catch (e) {
      console.error("加载检验员列表失败", e);
      userList.value = [];
    }
    // 加载指标选项(根据产品ID)
    if (detailData.productId) {
      try {
        const params = {
          productId: detailData.productId,
          inspectType: 2,
          process: detailData.process || "",
        };
        const res = await qualityInspectDetailByProductId(params);
        testStandardOptions.value = res.data || [];
      } catch (e) {
        console.error("加载指标选项失败", e);
        testStandardOptions.value = [];
      }
    } else {
      testStandardOptions.value = [];
    }
    // 设置默认值(优先使用详情数据,检验员默认为当前登录用户)
    quickCheckForm.value = {
      checkResult: detailData.checkResult || "合格",
      checkName: detailData.checkName || userStore.nickName || "",
      checkTime: detailData.checkTime || dayjs().format("YYYY-MM-DD"),
      testStandardId: detailData.testStandardId || "",
      checkCompany: detailData.checkCompany || "",
    };
    // 如果有检验参数数据,回显到表格
    if (
      detailData.qualityInspectParams &&
      detailData.qualityInspectParams.length > 0
    ) {
      quickCheckTableData.value = detailData.qualityInspectParams;
    } else {
      quickCheckTableData.value = [];
    }
    quickCheckVisible.value = true;
  };
// 指标选择变化处理
const handleTestStandardChange = (testStandardId) => {
  if (!testStandardId) {
  // 指标选择变化处理
  const handleTestStandardChange = testStandardId => {
    if (!testStandardId) {
      quickCheckTableData.value = [];
      return;
    }
    quickCheckTableLoading.value = true;
    getQualityTestStandardParamByTestStandardId(testStandardId)
      .then(res => {
        quickCheckTableData.value = res.data || [];
      })
      .catch(error => {
        console.error("获取标准参数失败:", error);
        quickCheckTableData.value = [];
      })
      .finally(() => {
        quickCheckTableLoading.value = false;
      });
  };
  // 关闭检验结果选择对话框
  const closeQuickCheck = () => {
    quickCheckVisible.value = false;
    quickCheckForm.value = {
      checkResult: "合格",
      checkName: "",
      checkTime: "",
      testStandardId: "",
      checkCompany: "",
    };
    quickCheckTableData.value = [];
    return;
  }
  quickCheckTableLoading.value = true;
  getQualityTestStandardParamByTestStandardId(testStandardId).then(res => {
    quickCheckTableData.value = res.data || [];
  }).catch(error => {
    console.error('获取标准参数失败:', error);
    quickCheckTableData.value = [];
  }).finally(() => {
    quickCheckTableLoading.value = false;
    testStandardOptions.value = [];
  };
  // 确认检验结果
  const handleQuickCheckConfirm = () => {
    if (!quickCheckForm.value.checkResult) {
      proxy.$modal.msgWarning("请选择检测结果");
      return;
    }
    if (!quickCheckForm.value.checkName) {
      proxy.$modal.msgWarning("请选择检验员");
      return;
    }
    if (!quickCheckForm.value.checkTime) {
      proxy.$modal.msgWarning("请选择检测日期");
      return;
    }
    const checkNameToPass = quickCheckForm.value.checkName;
    const checkResultToPass = quickCheckForm.value.checkResult;
    const checkTimeToPass = quickCheckForm.value.checkTime;
    const testStandardIdToPass = quickCheckForm.value.testStandardId;
    const checkCompanyToPass = quickCheckForm.value.checkCompany;
    const qualityInspectParamsToPass = [...quickCheckTableData.value];
    if (quickCheckForm.value.checkResult === "合格") {
      // 合格:直接提交
      const data = {
        id: currentRow.value.id,
        checkResult: "合格",
        checkName: checkNameToPass,
        checkTime: checkTimeToPass,
        testStandardId: testStandardIdToPass,
        checkCompany: checkCompanyToPass,
        inspectType: 2,
        qualityInspectParams: qualityInspectParamsToPass,
      };
      qualityInspectUpdate(data).then(res => {
        proxy.$modal.msgSuccess("检验成功");
        closeQuickCheck();
        getList();
      });
    } else {
      // 不合格:打开详细填写页面
      closeQuickCheck();
      nextTick(() => {
        currentRow.value.checkTime = checkTimeToPass;
        currentRow.value.checkCompany = checkCompanyToPass;
        currentRow.value.testStandardId = testStandardIdToPass;
        formDia.value?.openDialog(
          "edit",
          currentRow.value,
          checkResultToPass,
          checkNameToPass,
          testStandardIdToPass,
          checkCompanyToPass,
          qualityInspectParamsToPass
        );
      });
    }
  };
  onMounted(() => {
    getList();
  });
};
// 关闭检验结果选择对话框
const closeQuickCheck = () => {
    quickCheckVisible.value = false;
    quickCheckForm.value = {
        checkResult: "合格",
        checkName: "",
        checkTime: "",
        testStandardId: "",
        checkCompany: "天津阳光彩印股份有限公司"
    };
    quickCheckTableData.value = [];
    testStandardOptions.value = [];
};
// 确认检验结果
const handleQuickCheckConfirm = () => {
    if (!quickCheckForm.value.checkResult) {
        proxy.$modal.msgWarning("请选择检测结果");
        return;
    }
    if (!quickCheckForm.value.checkName) {
        proxy.$modal.msgWarning("请选择检验员");
        return;
    }
    if (!quickCheckForm.value.checkTime) {
        proxy.$modal.msgWarning("请选择检测日期");
        return;
    }
    const checkNameToPass = quickCheckForm.value.checkName;
    const checkResultToPass = quickCheckForm.value.checkResult;
    const checkTimeToPass = quickCheckForm.value.checkTime;
    const testStandardIdToPass = quickCheckForm.value.testStandardId;
    const checkCompanyToPass = quickCheckForm.value.checkCompany;
    const qualityInspectParamsToPass = [...quickCheckTableData.value];
    if (quickCheckForm.value.checkResult === "合格") {
        // 合格:直接提交
        const data = {
            id: currentRow.value.id,
            checkResult: "合格",
            checkName: checkNameToPass,
            checkTime: checkTimeToPass,
            testStandardId: testStandardIdToPass,
            checkCompany: checkCompanyToPass,
            inspectType: 2,
            qualityInspectParams: qualityInspectParamsToPass
        };
        qualityInspectUpdate(data).then(res => {
            proxy.$modal.msgSuccess("检验成功");
            closeQuickCheck();
            getList();
        });
    } else {
        // 不合格:打开详细填写页面
        closeQuickCheck();
        nextTick(() => {
            currentRow.value.checkTime = checkTimeToPass;
            currentRow.value.checkCompany = checkCompanyToPass;
            currentRow.value.testStandardId = testStandardIdToPass;
            formDia.value?.openDialog("edit", currentRow.value, checkResultToPass, checkNameToPass, testStandardIdToPass, checkCompanyToPass, qualityInspectParamsToPass);
        });
    }
};
onMounted(() => {
  getList();
});
</script>
<style scoped></style>
src/views/qualityManagement/metricBinding/index.vue
@@ -1,138 +1,205 @@
<template>
  <div class="app-container metric-binding">
    <el-tabs v-model="activeTab" @tab-change="handleTabChange" class="metric-tabs">
      <el-tab-pane label="原材料检验" name="0" />
      <el-tab-pane label="过程检验" name="1" />
      <el-tab-pane label="出厂检验" name="2" />
    <el-tabs v-model="activeTab"
             @tab-change="handleTabChange"
             class="metric-tabs">
      <el-tab-pane label="原材料检验"
                   name="0" />
      <el-tab-pane label="过程检验"
                   name="1" />
      <el-tab-pane label="出厂检验"
                   name="2" />
    </el-tabs>
    <el-row :gutter="16" class="metric-binding-row">
    <el-row :gutter="16"
            class="metric-binding-row">
      <!-- 左侧:检测标准列表 -->
      <el-col :xs="24" :sm="24" :md="12" :lg="14" :xl="14" class="left-col">
      <el-col :xs="24"
              :sm="24"
              :md="12"
              :lg="14"
              :xl="14"
              class="left-col">
        <div class="panel left-panel">
      <PIMTable
        rowKey="id"
        :column="standardColumns"
        :tableData="standardTableData"
        :page="page"
        :isSelection="false"
        :rowClassName="rowClassNameCenter"
        :tableLoading="tableLoading"
        :rowClick="handleTableRowClick"
        @pagination="handlePagination"
        :total="page.total"
      >
        <template #standardNoCell="{ row }">
          <span class="clickable-link" @click="handleStandardRowClick(row)">
            {{ row.standardNo }}
          </span>
        </template>
        <!-- 表头搜索 -->
        <template #standardNoHeader>
          <el-input
            v-model="searchForm.standardNo"
            placeholder="标准编号"
            clearable
            size="small"
            @change="handleQuery"
            @clear="handleQuery"
          />
        </template>
        <template #standardNameHeader>
          <el-input
            v-model="searchForm.standardName"
            placeholder="标准名称"
            clearable
            size="small"
            @change="handleQuery"
            @clear="handleQuery"
          />
        </template>
        <template #stateHeader>
          <el-select
            v-model="searchForm.state"
            placeholder="状态"
            clearable
            size="small"
            style="width: 110px"
            @change="handleQuery"
            @clear="handleQuery"
          >
            <el-option label="草稿" value="0" />
            <el-option label="通过" value="1" />
            <el-option label="撤销" value="2" />
          </el-select>
        </template>
      </PIMTable>
          <PIMTable rowKey="id"
                    :column="standardColumns"
                    :tableData="standardTableData"
                    :page="page"
                    :isSelection="false"
                    :rowClassName="rowClassNameCenter"
                    :tableLoading="tableLoading"
                    :rowClick="handleTableRowClick"
                    @pagination="handlePagination"
                    :total="page.total">
            <template #standardNoCell="{ row }">
              <span class="clickable-link"
                    @click="handleStandardRowClick(row)">
                {{ row.standardNo }}
              </span>
            </template>
            <!-- 表头搜索 -->
            <template #standardNoHeader>
              <el-input v-model="searchForm.standardNo"
                        placeholder="标准编号"
                        clearable
                        size="small"
                        @change="handleQuery"
                        @clear="handleQuery" />
            </template>
            <template #standardNameHeader>
              <el-input v-model="searchForm.standardName"
                        placeholder="标准名称"
                        clearable
                        size="small"
                        @change="handleQuery"
                        @clear="handleQuery" />
            </template>
            <template #stateHeader>
              <el-select v-model="searchForm.state"
                         placeholder="状态"
                         clearable
                         size="small"
                         style="width: 110px"
                         @change="handleQuery"
                         @clear="handleQuery">
                <el-option label="草稿"
                           value="0" />
                <el-option label="通过"
                           value="1" />
                <el-option label="撤销"
                           value="2" />
              </el-select>
            </template>
          </PIMTable>
        </div>
      </el-col>
      <!-- 右侧:绑定列表 -->
      <el-col :xs="24" :sm="24" :md="12" :lg="10" :xl="10" class="right-col">
      <el-col :xs="24"
              :sm="24"
              :md="12"
              :lg="10"
              :xl="10"
              class="right-col">
        <div class="panel right-panel">
      <div class="right-header">
        <div class="title">绑定关系</div>
        <div class="desc" v-if="currentStandard">
          当前检测标准编号:<span class="link-text">{{ currentStandard.standardNo }}</span>
        </div>
        <div class="desc" v-else>请选择左侧检测标准</div>
      </div>
      <div class="right-toolbar">
        <el-button type="primary" :disabled="!currentStandard" @click="openBindingDialog">添加绑定</el-button>
        <el-button type="danger" plain :disabled="!currentStandard" @click="handleBatchUnbind">删除</el-button>
      </div>
      <el-table
        v-loading="bindingLoading"
        :data="bindingTableData"
        border
        :row-class-name="() => 'row-center'"
        class="center-table"
        style="width: 100%"
        height="calc(100vh - 220px)"
        @selection-change="handleBindingSelectionChange"
      >
        <el-table-column type="selection" width="48" align="center" />
        <el-table-column type="index" label="序号" width="60" align="center" />
        <el-table-column prop="productName" label="产品名称" min-width="140" />
        <el-table-column label="操作" width="120" fixed="right" align="center">
          <template #default="{ row }">
            <el-button link type="danger" size="small" @click="handleUnbind(row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
          <div class="right-header">
            <div class="title">绑定关系</div>
            <div class="desc"
                 v-if="currentStandard">
              当前检测标准编号:<span class="link-text">{{ currentStandard.standardNo }}</span>
            </div>
            <div class="desc"
                 v-else>请选择左侧检测标准</div>
          </div>
          <div class="right-toolbar">
            <el-button type="primary"
                       :disabled="!currentStandard"
                       @click="openBindingDialog">添加绑定</el-button>
            <el-button v-if="activeTab === '1' || activeTab === '2'"
                       type="primary"
                       :disabled="!leftProcessOptions.length"
                       @click="openProcessBindingDialog">按工序批量绑定</el-button>
            <el-button type="danger"
                       plain
                       :disabled="!currentStandard"
                       @click="handleBatchUnbind">删除</el-button>
          </div>
          <el-table v-loading="bindingLoading"
                    :data="bindingTableData"
                    border
                    :row-class-name="() => 'row-center'"
                    class="center-table"
                    style="width: 100%"
                    height="calc(100vh - 220px)"
                    @selection-change="handleBindingSelectionChange">
            <el-table-column type="selection"
                             width="48"
                             align="center" />
            <el-table-column type="index"
                             label="序号"
                             width="60"
                             align="center" />
            <el-table-column prop="productName"
                             label="产品名称"
                             min-width="140" />
            <el-table-column label="操作"
                             width="120"
                             fixed="right"
                             align="center">
              <template #default="{ row }">
                <el-button link
                           type="danger"
                           size="small"
                           @click="handleUnbind(row)">删除</el-button>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </el-col>
    </el-row>
    <!-- 添加绑定弹框 -->
    <el-dialog
      v-model="bindingDialogVisible"
      title="添加绑定"
      width="520px"
      @close="closeBindingDialog"
    >
    <el-dialog v-model="bindingDialogVisible"
               title="添加绑定"
               width="520px"
               @close="closeBindingDialog">
      <el-form label-width="100px">
        <el-form-item label="产品">
          <el-tree-select
            v-model="selectedProductIds"
            multiple
            collapse-tags
            collapse-tags-tooltip
            placeholder="请选择产品(可多选)"
            clearable
            check-strictly
            :data="productOptions"
            :render-after-expand="false"
            style="width: 100%"
          />
          <el-tree-select v-model="selectedProductIds"
                          multiple
                          collapse-tags
                          collapse-tags-tooltip
                          placeholder="请选择产品(可多选)"
                          clearable
                          check-strictly
                          :data="productOptions"
                          :render-after-expand="false"
                          style="width: 100%" />
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="closeBindingDialog">取消</el-button>
          <el-button type="primary" @click="submitBinding">确定</el-button>
          <el-button type="primary"
                     @click="submitBinding">确定</el-button>
        </span>
      </template>
    </el-dialog>
    <el-dialog v-model="processBindingDialogVisible"
               title="按工序批量绑定"
               width="520px"
               @close="closeProcessBindingDialog">
      <el-form label-width="100px">
        <el-form-item label="产品">
          <el-tree-select v-model="processBindingProductIds"
                          multiple
                          collapse-tags
                          collapse-tags-tooltip
                          placeholder="请选择产品(可多选)"
                          clearable
                          check-strictly
                          :data="productOptions"
                          :render-after-expand="false"
                          style="width: 100%" />
        </el-form-item>
        <el-form-item label="工序">
          <el-select v-model="processBindingProcessIds"
                     multiple
                     filterable
                     clearable
                     collapse-tags
                     collapse-tags-tooltip
                     placeholder="请选择工序(必选,可多选)"
                     style="width: 100%">
            <el-option v-for="item in leftProcessOptions"
                       :key="item.value"
                       :label="item.label"
                       :value="item.value" />
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="closeProcessBindingDialog">取消</el-button>
          <el-button type="primary"
                     @click="submitProcessBinding">确定</el-button>
        </span>
      </template>
    </el-dialog>
@@ -140,410 +207,516 @@
</template>
<script setup>
import { Search } from '@element-plus/icons-vue'
import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
import { ElMessageBox } from 'element-plus'
import PIMTable from '@/components/PIMTable/PIMTable.vue'
import { productTreeList } from '@/api/basicData/product.js'
import {
  qualityTestStandardListPage
} from '@/api/qualityManagement/metricMaintenance.js'
import { productProcessListPage } from '@/api/basicData/productProcess.js'
import {
  qualityTestStandardBindingList,
  qualityTestStandardBindingAdd,
  qualityTestStandardBindingDel
} from '@/api/qualityManagement/qualityTestStandardBinding.js'
  import { Search } from "@element-plus/icons-vue";
  import {
    ref,
    reactive,
    toRefs,
    onMounted,
    getCurrentInstance,
    computed,
  } from "vue";
  import { ElMessageBox } from "element-plus";
  import PIMTable from "@/components/PIMTable/PIMTable.vue";
  import { productTreeList } from "@/api/basicData/product.js";
  import { qualityTestStandardListPage } from "@/api/qualityManagement/metricMaintenance.js";
  import { productProcessListPage } from "@/api/basicData/productProcess.js";
  import {
    qualityTestStandardBindingList,
    qualityTestStandardBindingAdd,
    qualityTestStandardBindingDel,
  } from "@/api/qualityManagement/qualityTestStandardBinding.js";
const { proxy } = getCurrentInstance()
  const { proxy } = getCurrentInstance();
// 类别 Tab(0=原材料,1=过程,2=出厂)
const activeTab = ref('0')
  // 类别 Tab(0=原材料,1=过程,2=出厂)
  const activeTab = ref("0");
// 左侧标准列表:整行内容居中(配合样式)
const rowClassNameCenter = () => 'row-center'
  // 左侧标准列表:整行内容居中(配合样式)
  const rowClassNameCenter = () => "row-center";
const data = reactive({
  searchForm: {
    standardNo: '',
    standardName: '',
    state: '',
    inspectType: ''
  const data = reactive({
    searchForm: {
      standardNo: "",
      standardName: "",
      state: "",
      inspectType: "",
    },
  });
  const { searchForm } = toRefs(data);
  // 左侧
  const standardTableData = ref([]);
  const tableLoading = ref(false);
  const page = reactive({ current: 1, size: 10, total: 0 });
  // 工序下拉(用于列表回显)
  const processOptions = ref([]);
  const getProcessList = async () => {
    try {
      const res = await productProcessListPage({ current: 1, size: 1000 });
      if (res?.code === 200) {
        const records = res?.data?.records || [];
        processOptions.value = records.map(item => ({
          label: item.processName || item.name || item.label,
          value: item.id || item.processId || item.value,
        }));
      }
    } catch (error) {
      console.error("获取工序列表失败:", error);
    }
  };
  const standardColumns = ref([
    {
      label: "标准编号",
      prop: "standardNo",
      dataType: "slot",
      slot: "standardNoCell",
      minWidth: 160,
      align: "center",
      headerSlot: "standardNoHeader",
    },
    {
      label: "标准名称",
      prop: "standardName",
      minWidth: 180,
      align: "center",
      headerSlot: "standardNameHeader",
    },
    {
      label: "类别",
      prop: "inspectType",
      align: "center",
      dataType: "tag",
      formatData: val => {
        const map = { 0: "原材料检验", 1: "过程检验", 2: "出厂检验" };
        return map[val] || val;
      },
    },
    {
      label: "工序",
      prop: "processId",
      align: "center",
      dataType: "tag",
      formatData: val => {
        const target = processOptions.value.find(
          item => String(item.value) === String(val)
        );
        return target?.label || val;
      },
    },
    {
      label: "备注",
      prop: "remark",
      minWidth: 160,
      align: "center",
    },
    // {
    //   label: '状态',
    //   prop: 'state',
    //   headerSlot: 'stateHeader',
    //   dataType: 'tag',
    //   formatData: (val) => {
    //     const map = { 0: '草稿', 1: '通过', 2: '撤销' }
    //     return map[val] || val
    //   },
    //   formatType: (val) => {
    //     if (val == 1) return 'success'
    //     if (val == 2) return 'warning'
    //     return 'info'
    //   }
    // }
  ]);
  const handleTabChange = () => {
    searchForm.value.standardNo = "";
    searchForm.value.standardName = "";
    searchForm.value.state = "";
    searchForm.value.inspectType = activeTab.value;
    page.current = 1;
    currentStandard.value = null;
    bindingTableData.value = [];
    bindingSelectedRows.value = [];
    bindingDialogVisible.value = false;
    processBindingDialogVisible.value = false;
    getStandardList();
  };
  const currentStandard = ref(null);
  // 右侧绑定
  const bindingTableData = ref([]);
  const bindingLoading = ref(false);
  const bindingSelectedRows = ref([]);
  const bindingDialogVisible = ref(false);
  const processBindingDialogVisible = ref(false);
  // 产品树(用于绑定选择)
  const productOptions = ref([]);
  const selectedProductIds = ref([]);
  const processBindingProductIds = ref([]);
  const processBindingProcessIds = ref([]);
  const leftProcessOptions = computed(() => {
    const ids = new Set(
      (standardTableData.value || [])
        .map(i => i?.processId)
        .filter(v => v !== null && v !== undefined && String(v) !== "")
        .map(v => String(v))
    );
    if (!ids.size) return [];
    return (processOptions.value || [])
      .filter(p => ids.has(String(p.value)))
      .sort((a, b) => String(a.label).localeCompare(String(b.label), "zh-CN"));
  });
  const getProductOptions = async () => {
    // 避免重复请求
    if (productOptions.value?.length) return;
    const res = await productTreeList();
    productOptions.value = convertIdToValue(Array.isArray(res) ? res : []);
  };
  function convertIdToValue(data) {
    return (data || []).map(item => {
      const { id, children, ...rest } = item;
      const newItem = {
        ...rest,
        value: id,
      };
      if (children && children.length > 0) {
        newItem.children = convertIdToValue(children);
      }
      return newItem;
    });
  }
})
const { searchForm } = toRefs(data)
// 左侧
const standardTableData = ref([])
const tableLoading = ref(false)
const page = reactive({ current: 1, size: 10, total: 0 })
  const handleQuery = () => {
    page.current = 1;
    getStandardList();
  };
// 工序下拉(用于列表回显)
const processOptions = ref([])
  const handlePagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getStandardList();
  };
const getProcessList = async () => {
  try {
    const res = await productProcessListPage({ current: 1, size: 1000 })
    if (res?.code === 200) {
      const records = res?.data?.records || []
      processOptions.value = records.map((item) => ({
        label: item.processName || item.name || item.label,
        value: item.id || item.processId || item.value
      }))
    }
  } catch (error) {
    console.error('获取工序列表失败:', error)
  }
}
const standardColumns = ref([
  { label: '标准编号', prop: 'standardNo', dataType: 'slot', slot: 'standardNoCell', minWidth: 160, align: 'center', headerSlot: 'standardNoHeader' },
  { label: '标准名称', prop: 'standardName', minWidth: 180, align: 'center', headerSlot: 'standardNameHeader' },
  {
    label: '类别',
    prop: 'inspectType',
    align: 'center',
    dataType: 'tag',
    formatData: (val) => {
      const map = { 0: '原材料检验', 1: '过程检验', 2: '出厂检验' }
      return map[val] || val
    }
  },
  {
    label: '工序',
    prop: 'processId',
    align: 'center',
    dataType: 'tag',
    formatData: (val) => {
      const target = processOptions.value.find(
        (item) => String(item.value) === String(val)
      )
      return target?.label || val
    }
  },
  {
    label: '备注',
    prop: 'remark',
    minWidth: 160,
    align: 'center'
  }
  // {
  //   label: '状态',
  //   prop: 'state',
  //   headerSlot: 'stateHeader',
  //   dataType: 'tag',
  //   formatData: (val) => {
  //     const map = { 0: '草稿', 1: '通过', 2: '撤销' }
  //     return map[val] || val
  //   },
  //   formatType: (val) => {
  //     if (val == 1) return 'success'
  //     if (val == 2) return 'warning'
  //     return 'info'
  //   }
  // }
])
const handleTabChange = () => {
  searchForm.value.standardNo = ''
  searchForm.value.standardName = ''
  searchForm.value.state = ''
  searchForm.value.inspectType = activeTab.value
  page.current = 1
  currentStandard.value = null
  bindingTableData.value = []
  bindingSelectedRows.value = []
  getStandardList()
}
const currentStandard = ref(null)
// 右侧绑定
const bindingTableData = ref([])
const bindingLoading = ref(false)
const bindingSelectedRows = ref([])
const bindingDialogVisible = ref(false)
// 产品树(用于绑定选择)
const productOptions = ref([])
const selectedProductIds = ref([])
const getProductOptions = async () => {
  // 避免重复请求
  if (productOptions.value?.length) return
  const res = await productTreeList()
  productOptions.value = convertIdToValue(Array.isArray(res) ? res : [])
}
function convertIdToValue(data) {
  return (data || []).map((item) => {
    const { id, children, ...rest } = item
    const newItem = {
      ...rest,
      value: id
    }
    if (children && children.length > 0) {
      newItem.children = convertIdToValue(children)
    }
    return newItem
  })
}
const handleQuery = () => {
  page.current = 1
  getStandardList()
}
const handlePagination = (obj) => {
  page.current = obj.page
  page.size = obj.limit
  getStandardList()
}
const getStandardList = () => {
  tableLoading.value = true
  qualityTestStandardListPage({
    ...searchForm.value,
    current: page.current,
    size: page.size,
    state: 1
  })
    .then((res) => {
      const records = res?.data?.records || []
      standardTableData.value = records
      page.total = res?.data?.total || records.length
  const getStandardList = () => {
    tableLoading.value = true;
    qualityTestStandardListPage({
      ...searchForm.value,
      current: page.current,
      size: page.size,
      state: 1,
    })
    .finally(() => {
      tableLoading.value = false
    })
}
      .then(res => {
        const records = res?.data?.records || [];
        standardTableData.value = records;
        page.total = res?.data?.total || records.length;
      })
      .finally(() => {
        tableLoading.value = false;
      });
  };
// 表格行点击,加载右侧绑定列表
const handleTableRowClick = (row) => {
  currentStandard.value = row
  loadBindingList()
}
  // 表格行点击,加载右侧绑定列表
  const handleTableRowClick = row => {
    currentStandard.value = row;
    loadBindingList();
  };
// 左侧行点击,加载右侧绑定列表(保留用于标准编号列的点击)
const handleStandardRowClick = (row) => {
  currentStandard.value = row
  loadBindingList()
}
  // 左侧行点击,加载右侧绑定列表(保留用于标准编号列的点击)
  const handleStandardRowClick = row => {
    currentStandard.value = row;
    loadBindingList();
  };
const loadBindingList = () => {
  if (!currentStandard.value?.id) {
    bindingTableData.value = []
    return
  }
  bindingLoading.value = true
  qualityTestStandardBindingList({ testStandardId: currentStandard.value.id })
    .then((res) => {
      const base = res?.data || []
      // 将当前标准的工序和备注带到绑定列表中展示
      bindingTableData.value = base.map((item) => ({
        ...item,
        processId: currentStandard.value?.processId,
        remark: currentStandard.value?.remark
      }))
    })
    .finally(() => {
      bindingLoading.value = false
    })
}
  const loadBindingList = () => {
    if (!currentStandard.value?.id) {
      bindingTableData.value = [];
      return;
    }
    bindingLoading.value = true;
    qualityTestStandardBindingList({ testStandardId: currentStandard.value.id })
      .then(res => {
        const base = res?.data || [];
        // 将当前标准的工序和备注带到绑定列表中展示
        bindingTableData.value = base.map(item => ({
          ...item,
          processId: currentStandard.value?.processId,
          remark: currentStandard.value?.remark,
        }));
      })
      .finally(() => {
        bindingLoading.value = false;
      });
  };
const handleBindingSelectionChange = (selection) => {
  bindingSelectedRows.value = selection
}
  const handleBindingSelectionChange = selection => {
    bindingSelectedRows.value = selection;
  };
const openBindingDialog = () => {
  if (!currentStandard.value?.id) return
  selectedProductIds.value = []
  getProductOptions()
  bindingDialogVisible.value = true
}
  const openBindingDialog = () => {
    if (!currentStandard.value?.id) return;
    selectedProductIds.value = [];
    getProductOptions();
    bindingDialogVisible.value = true;
  };
const closeBindingDialog = () => {
  bindingDialogVisible.value = false
}
  const closeBindingDialog = () => {
    bindingDialogVisible.value = false;
  };
const submitBinding = async () => {
  const testStandardId = currentStandard.value?.id
  if (!testStandardId) return
  const ids = (selectedProductIds.value || []).filter(Boolean)
  if (!ids.length) {
    proxy.$message.warning('请选择产品')
    return
  }
  const payload = ids.map((pid) => ({
    productId: pid,
    testStandardId
  }))
  await qualityTestStandardBindingAdd(payload)
  proxy.$message.success('添加成功')
  bindingDialogVisible.value = false
  loadBindingList()
}
  const openProcessBindingDialog = () => {
    if (activeTab.value !== "1" && activeTab.value !== "2") return;
    processBindingProductIds.value = [];
    processBindingProcessIds.value = [];
    getProductOptions();
    processBindingDialogVisible.value = true;
  };
const handleUnbind = async (row) => {
  const id = row?.id ?? row?.qualityTestStandardBindingId
  if (id == null || id === '') return
  try {
    await ElMessageBox.confirm('确认删除该绑定?', '提示', { type: 'warning' })
  } catch {
    return
  }
  try {
    await qualityTestStandardBindingDel([id])
    proxy.$message.success('删除成功')
    loadBindingList()
  } catch (err) {
    console.error('删除绑定失败:', err)
    proxy.$message?.error(err?.message || '删除失败')
  }
}
  const closeProcessBindingDialog = () => {
    processBindingDialogVisible.value = false;
  };
const handleBatchUnbind = async () => {
  if (!bindingSelectedRows.value.length) {
    proxy.$message.warning('请选择数据')
    return
  }
  const ids = bindingSelectedRows.value
    .map((i) => i?.id ?? i?.qualityTestStandardBindingId)
    .filter((id) => id != null && id !== '')
  if (!ids.length) {
    proxy.$message.warning('选中数据缺少有效 id')
    return
  }
  try {
    await ElMessageBox.confirm('选中的内容将被删除,是否确认删除?', '删除提示', { type: 'warning' })
  } catch {
    return
  }
  try {
    await qualityTestStandardBindingDel(ids)
    proxy.$message.success('删除成功')
    loadBindingList()
  } catch (err) {
    console.error('批量删除绑定失败:', err)
    proxy.$message?.error(err?.message || '删除失败')
  }
}
  const submitBinding = async () => {
    const ids = (selectedProductIds.value || []).filter(Boolean);
    if (!ids.length) {
      proxy.$message.warning("请选择产品");
      return;
    }
    const testStandardId = currentStandard.value?.id;
    if (!testStandardId) return;
    const payload = ids.map(pid => ({
      productId: pid,
      testStandardId,
    }));
    await qualityTestStandardBindingAdd(payload);
    proxy.$message.success("添加成功");
    bindingDialogVisible.value = false;
    loadBindingList();
  };
onMounted(() => {
  searchForm.value.inspectType = activeTab.value
  getStandardList()
  getProcessList()
})
  const submitProcessBinding = async () => {
    const ids = (processBindingProductIds.value || []).filter(Boolean);
    if (!ids.length) {
      proxy.$message.warning("请选择产品");
      return;
    }
    const processIds = (processBindingProcessIds.value || []).filter(Boolean);
    if (!processIds.length) {
      proxy.$message.warning("请选择工序");
      return;
    }
    const allow = new Set(leftProcessOptions.value.map(i => String(i.value)));
    const processIdSet = new Set(processIds.map(i => String(i)));
    for (const pid of processIdSet) {
      if (!allow.has(pid)) {
        proxy.$message.warning("所选工序必须在左侧列表中出现过");
        return;
      }
    }
    const standards = (standardTableData.value || []).filter(s =>
      processIdSet.has(String(s?.processId))
    );
    if (!standards.length) {
      proxy.$message.warning("左侧列表未找到匹配工序的检测标准");
      return;
    }
    const uniq = new Set();
    const payload = [];
    for (const std of standards) {
      const testStandardId = std?.id;
      if (!testStandardId) continue;
      for (const pid of ids) {
        const key = `${String(testStandardId)}__${String(pid)}`;
        if (uniq.has(key)) continue;
        uniq.add(key);
        payload.push({
          productId: pid,
          testStandardId,
        });
      }
    }
    if (!payload.length) {
      proxy.$message.warning("未生成有效绑定数据");
      return;
    }
    await qualityTestStandardBindingAdd(payload);
    proxy.$message.success(`已添加 ${payload.length} 条绑定`);
    processBindingDialogVisible.value = false;
    if (currentStandard.value?.id) {
      loadBindingList();
    }
  };
  const handleUnbind = async row => {
    const id = row?.id ?? row?.qualityTestStandardBindingId;
    if (id == null || id === "") return;
    try {
      await ElMessageBox.confirm("确认删除该绑定?", "提示", { type: "warning" });
    } catch {
      return;
    }
    try {
      await qualityTestStandardBindingDel([id]);
      proxy.$message.success("删除成功");
      loadBindingList();
    } catch (err) {
      console.error("删除绑定失败:", err);
      proxy.$message?.error(err?.message || "删除失败");
    }
  };
  const handleBatchUnbind = async () => {
    if (!bindingSelectedRows.value.length) {
      proxy.$message.warning("请选择数据");
      return;
    }
    const ids = bindingSelectedRows.value
      .map(i => i?.id ?? i?.qualityTestStandardBindingId)
      .filter(id => id != null && id !== "");
    if (!ids.length) {
      proxy.$message.warning("选中数据缺少有效 id");
      return;
    }
    try {
      await ElMessageBox.confirm(
        "选中的内容将被删除,是否确认删除?",
        "删除提示",
        { type: "warning" }
      );
    } catch {
      return;
    }
    try {
      await qualityTestStandardBindingDel(ids);
      proxy.$message.success("删除成功");
      loadBindingList();
    } catch (err) {
      console.error("批量删除绑定失败:", err);
      proxy.$message?.error(err?.message || "删除失败");
    }
  };
  onMounted(() => {
    searchForm.value.inspectType = activeTab.value;
    getStandardList();
    getProcessList();
  });
</script>
<style scoped>
.metric-binding {
  padding: 0;
}
  .metric-binding {
    padding: 0;
  }
.metric-tabs {
  margin: 10px;
}
  .metric-tabs {
    margin: 10px;
  }
.metric-binding-row {
  width: 100%;
}
  .metric-binding-row {
    width: 100%;
  }
.metric-binding-row .left-col,
.metric-binding-row .right-col {
  margin-bottom: 16px;
}
  .metric-binding-row .left-col,
  .metric-binding-row .right-col {
    margin-bottom: 16px;
  }
.metric-binding-row .panel {
  background: #ffffff;
  padding: 16px;
  box-sizing: border-box;
  height: 100%;
  min-height: 400px;
}
  .metric-binding-row .panel {
    background: #ffffff;
    padding: 16px;
    box-sizing: border-box;
    height: 100%;
    min-height: 400px;
  }
.left-panel,
.right-panel {
  height: 100%;
}
  .left-panel,
  .right-panel {
    height: 100%;
  }
.toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}
  .toolbar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 12px;
  }
.toolbar-right {
  flex-shrink: 0;
}
  .toolbar-right {
    flex-shrink: 0;
  }
.right-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 10px;
}
  .right-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: 10px;
  }
.right-header .title {
  font-size: 16px;
  font-weight: 600;
}
  .right-header .title {
    font-size: 16px;
    font-weight: 600;
  }
.right-header .desc {
  font-size: 13px;
  color: #666;
}
  .right-header .desc {
    font-size: 13px;
    color: #666;
  }
.right-toolbar {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-bottom: 10px;
}
  .right-toolbar {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    margin-bottom: 10px;
  }
.link-text {
  color: #409eff;
  cursor: default;
}
  .link-text {
    color: #409eff;
    cursor: default;
  }
.clickable-link {
  color: #409eff;
  cursor: pointer;
}
  .clickable-link {
    color: #409eff;
    cursor: pointer;
  }
.clickable-link:hover {
  text-decoration: underline;
}
  .clickable-link:hover {
    text-decoration: underline;
  }
:deep(.row-center td) {
  text-align: center !important;
}
  :deep(.row-center td) {
    text-align: center !important;
  }
/* el-table 表头/内容统一居中(row-class-name 不作用于表头) */
:deep(.center-table .el-table__header-wrapper th .cell) {
  text-align: center !important;
}
:deep(.center-table .el-table__body-wrapper td .cell) {
  text-align: center !important;
}
  /* el-table 表头/内容统一居中(row-class-name 不作用于表头) */
  :deep(.center-table .el-table__header-wrapper th .cell) {
    text-align: center !important;
  }
  :deep(.center-table .el-table__body-wrapper td .cell) {
    text-align: center !important;
  }
/* PIMTable 表头居中 */
:deep(.lims-table .pim-table-header-cell) {
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
  /* PIMTable 表头居中 */
  :deep(.lims-table .pim-table-header-cell) {
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
  }
:deep(.lims-table .pim-table-header-title) {
  text-align: center;
  width: 100%;
}
  :deep(.lims-table .pim-table-header-title) {
    text-align: center;
    width: 100%;
  }
:deep(.lims-table .pim-table-header-extra) {
  width: 100%;
  margin-top: 4px;
}
  :deep(.lims-table .pim-table-header-extra) {
    width: 100%;
    margin-top: 4px;
  }
</style>
src/views/qualityManagement/metricMaintenance/StandardFormDialog.vue
@@ -1,128 +1,137 @@
<template>
  <FormDialog
    v-model="dialogVisible"
    :title="computedTitle"
    :operation-type="operationType"
    :width="width"
    @close="emit('close')"
    @cancel="handleCancel"
    @confirm="handleConfirm"
  >
    <el-form
      ref="formRef"
      :model="form"
      :rules="rules"
      label-width="100px"
    >
      <el-form-item label="标准编号" prop="standardNo">
        <el-input v-model="form.standardNo" placeholder="请输入标准编号" />
  <FormDialog v-model="dialogVisible"
              :title="computedTitle"
              :operation-type="operationType"
              :width="width"
              @close="emit('close')"
              @cancel="handleCancel"
              @confirm="handleConfirm">
    <el-form ref="formRef"
             :model="form"
             :rules="rules"
             label-width="100px">
      <el-form-item label="标准编号"
                    prop="standardNo">
        <el-input v-model="form.standardNo"
                  placeholder="请输入标准编号" />
      </el-form-item>
      <el-form-item label="标准名称" prop="standardName">
        <el-input v-model="form.standardName" placeholder="请输入标准名称" />
      <el-form-item label="标准名称"
                    prop="standardName">
        <el-input v-model="form.standardName"
                  placeholder="请输入标准名称" />
      </el-form-item>
      <el-form-item v-if="needProcess" label="工序" prop="processId">
        <el-select v-model="form.processId" placeholder="请选择工序" style="width: 100%">
          <el-option
            v-for="item in processOptions"
            :key="item.value"
            :label="item.label"
            :value="item.value"
          />
      <el-form-item v-if="needProcess"
                    label="工序"
                    prop="processId">
        <el-select v-model="form.processId"
                   placeholder="请选择工序"
                   style="width: 100%">
          <el-option v-for="item in processOptions"
                     :key="item.value"
                     :label="item.label"
                     :value="item.value" />
        </el-select>
      </el-form-item>
      <el-form-item label="状态" prop="state">
        <el-select v-model="form.state" placeholder="请选择状态" style="width: 100%">
          <el-option label="草稿" value="0" />
          <el-option label="通过" value="1" />
          <el-option label="撤销" value="2" />
      <el-form-item label="状态"
                    prop="state">
        <el-select v-model="form.state"
                   placeholder="请选择状态"
                   style="width: 100%">
          <el-option label="草稿"
                     value="0" />
          <el-option label="通过"
                     value="1" />
          <el-option label="撤销"
                     value="2" />
        </el-select>
      </el-form-item>
      <el-form-item label="备注" prop="remark">
        <el-input
          v-model="form.remark"
          type="textarea"
          :rows="3"
          placeholder="请输入备注"
        />
      <el-form-item label="备注"
                    prop="remark">
        <el-input v-model="form.remark"
                  type="textarea"
                  :rows="3"
                  placeholder="请输入备注" />
      </el-form-item>
    </el-form>
  </FormDialog>
</template>
<script setup>
import { computed, ref } from 'vue'
import FormDialog from '@/components/Dialog/FormDialog.vue'
  import { computed, ref } from "vue";
  import FormDialog from "@/components/Dialog/FormDialog.vue";
const props = defineProps({
  modelValue: {
    type: Boolean,
    default: false
  },
  operationType: {
    type: String,
    default: 'add'
  },
  form: {
    type: Object,
    required: true
  },
  rules: {
    type: Object,
    default: () => ({})
  },
  processOptions: {
    type: Array,
    default: () => []
  },
  width: {
    type: String,
    default: '500px'
  },
  inspectType:{
    type: String,
    default: null
  }
})
  const props = defineProps({
    modelValue: {
      type: Boolean,
      default: false,
    },
    operationType: {
      type: String,
      default: "add",
    },
    form: {
      type: Object,
      required: true,
    },
    rules: {
      type: Object,
      default: () => ({}),
    },
    processOptions: {
      type: Array,
      default: () => [],
    },
    width: {
      type: String,
      default: "500px",
    },
    inspectType: {
      type: String,
      default: null,
    },
  });
const emit = defineEmits(['update:modelValue', 'close', 'cancel', 'confirm'])
  const emit = defineEmits(["update:modelValue", "close", "cancel", "confirm"]);
const dialogVisible = computed({
  get: () => props.modelValue,
  set: (val) => emit('update:modelValue', val)
})
  const dialogVisible = computed({
    get: () => props.modelValue,
    set: val => emit("update:modelValue", val),
  });
const formRef = ref(null)
  const formRef = ref(null);
const needProcess = computed(() => String(props.inspectType ?? '') === '1')
  const needProcess = computed(() =>
    ["1", "2"].includes(String(props.inspectType ?? ""))
  );
const computedTitle = computed(() => {
  if (props.operationType === 'edit') return '编辑检测标准'
  if (props.operationType === 'copy') return '复制检测标准'
  return '新增检测标准'
})
  const computedTitle = computed(() => {
    if (props.operationType === "edit") return "编辑检测标准";
    if (props.operationType === "copy") return "复制检测标准";
    return "新增检测标准";
  });
const handleConfirm = () => {
  if (!formRef.value) {
    emit('confirm')
    return
  }
  formRef.value.validate((valid) => {
    if (valid) {
      emit('confirm')
  const handleConfirm = () => {
    if (!formRef.value) {
      emit("confirm");
      return;
    }
  })
}
    formRef.value.validate(valid => {
      if (valid) {
        emit("confirm");
      }
    });
  };
const handleCancel = () => {
  emit('cancel')
  dialogVisible.value = false
}
  const handleCancel = () => {
    emit("cancel");
    dialogVisible.value = false;
  };
const resetFields = () => {
  formRef.value?.resetFields?.()
}
  const resetFields = () => {
    formRef.value?.resetFields?.();
  };
defineExpose({
  resetFields
})
  defineExpose({
    resetFields,
  });
</script>
src/views/qualityManagement/metricMaintenance/index.vue
@@ -1,860 +1,938 @@
<template>
  <div class="app-container metric-maintenance">
    <el-tabs v-model="activeTab" @tab-change="handleTabChange" class="metric-tabs">
      <el-tab-pane label="原材料检验" name="0" />
      <el-tab-pane label="过程检验" name="1" />
      <el-tab-pane label="出厂检验" name="2" />
    <el-tabs v-model="activeTab"
             @tab-change="handleTabChange"
             class="metric-tabs">
      <el-tab-pane label="原材料检验"
                   name="0" />
      <el-tab-pane label="过程检验"
                   name="1" />
      <el-tab-pane label="出厂检验"
                   name="2" />
    </el-tabs>
    <el-row :gutter="16" class="metric-maintenance-row">
    <el-row :gutter="16"
            class="metric-maintenance-row">
      <!-- 左侧:检测标准列表 -->
      <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12" class="left-col">
      <el-col :xs="24"
              :sm="24"
              :md="12"
              :lg="12"
              :xl="12"
              class="left-col">
        <div class="left-panel">
      <div class="toolbar">
        <div class="toolbar-left"></div>
        <div class="toolbar-right">
          <el-button type="primary" @click="openStandardDialog('add')">新增</el-button>
          <el-button type="success" plain @click="handleBatchAudit(1)">批准</el-button>
          <el-button type="warning" plain @click="handleBatchAudit(2)">撤销</el-button>
          <el-button type="danger" plain @click="handleBatchDelete">删除</el-button>
        </div>
      </div>
      <PIMTable
        rowKey="id"
        :column="standardColumns"
        :tableData="standardTableData"
        :page="page"
        :isSelection="true"
        :tableLoading="tableLoading"
        :rowClassName="rowClassNameCenter"
        :rowClick="handleTableRowClick"
        @selection-change="handleSelectionChange"
        @pagination="handlePagination"
        :total="page.total"
      >
        <template #standardNoCell="{ row }">
          <span class="clickable-link" @click="handleStandardRowClick(row)">
            {{ row.standardNo }}
          </span>
        </template>
        <!-- 表头搜索插槽 -->
        <template #standardNoHeader>
          <el-input
            v-model="searchForm.standardNo"
            placeholder="标准编号"
            clearable
            size="small"
            @change="handleQuery"
            @clear="handleQuery"
          />
        </template>
        <template #standardNameHeader>
          <el-input
            v-model="searchForm.standardName"
            placeholder="标准名称"
            clearable
            size="small"
            @change="handleQuery"
            @clear="handleQuery"
          />
        </template>
        <template #stateHeader>
          <el-select
            v-model="searchForm.state"
            placeholder="状态"
            clearable
            size="small"
            style="width: 110px"
            @change="handleQuery"
            @clear="handleQuery"
          >
            <el-option label="草稿" value="0" />
            <el-option label="通过" value="1" />
            <el-option label="撤销" value="2" />
          </el-select>
        </template>
      </PIMTable>
          <div class="toolbar">
            <div class="toolbar-left"></div>
            <div class="toolbar-right">
              <el-button type="primary"
                         @click="openStandardDialog('add')">新增</el-button>
              <el-button type="success"
                         plain
                         @click="handleBatchAudit(1)">批准</el-button>
              <el-button type="warning"
                         plain
                         @click="handleBatchAudit(2)">撤销</el-button>
              <el-button type="danger"
                         plain
                         @click="handleBatchDelete">删除</el-button>
            </div>
          </div>
          <PIMTable rowKey="id"
                    :column="standardColumns"
                    :tableData="standardTableData"
                    :page="page"
                    :isSelection="true"
                    :tableLoading="tableLoading"
                    :rowClassName="rowClassNameCenter"
                    :rowClick="handleTableRowClick"
                    @selection-change="handleSelectionChange"
                    @pagination="handlePagination"
                    :total="page.total">
            <template #standardNoCell="{ row }">
              <span class="clickable-link"
                    @click="handleStandardRowClick(row)">
                {{ row.standardNo }}
              </span>
            </template>
            <!-- 表头搜索插槽 -->
            <template #standardNoHeader>
              <el-input v-model="searchForm.standardNo"
                        placeholder="标准编号"
                        clearable
                        size="small"
                        @change="handleQuery"
                        @clear="handleQuery" />
            </template>
            <template #standardNameHeader>
              <el-input v-model="searchForm.standardName"
                        placeholder="标准名称"
                        clearable
                        size="small"
                        @change="handleQuery"
                        @clear="handleQuery" />
            </template>
            <template #stateHeader>
              <el-select v-model="searchForm.state"
                         placeholder="状态"
                         clearable
                         size="small"
                         style="width: 110px"
                         @change="handleQuery"
                         @clear="handleQuery">
                <el-option label="草稿"
                           value="0" />
                <el-option label="通过"
                           value="1" />
                <el-option label="撤销"
                           value="2" />
              </el-select>
            </template>
          </PIMTable>
        </div>
      </el-col>
      <!-- 右侧:标准参数列表 -->
      <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12" class="right-col">
      <el-col :xs="24"
              :sm="24"
              :md="12"
              :lg="12"
              :xl="12"
              class="right-col">
        <div class="right-panel">
      <div class="right-header">
        <div class="title">标准参数</div>
        <div class="desc" v-if="currentStandard">
          您当前选择的检测标准编号是:
          <span class="link-text">{{ currentStandard.standardNo }}</span>
        </div>
        <div class="desc" v-else>请先在左侧选择一个检测标准</div>
      </div>
      <div class="right-toolbar">
        <el-button type="primary" :disabled="!currentStandard || isStandardReadonly" @click="openParamDialog('add')">
          新增
        </el-button>
        <el-button type="danger" plain :disabled="!currentStandard || isStandardReadonly" @click="handleParamBatchDelete">
          删除
        </el-button>
      </div>
      <el-table
        v-loading="detailLoading"
        :data="detailTableData"
        border
        :row-class-name="() => 'row-center'"
        class="center-table"
        style="width: 100%"
        height="calc(100vh - 220px)"
        @selection-change="handleParamSelectionChange"
      >
        <el-table-column type="selection" width="48" align="center" />
        <el-table-column type="index" label="序号" width="60" align="center" />
        <el-table-column prop="parameterItem" label="参数项" min-width="120" />
        <el-table-column prop="unit" label="单位" width="80" />
        <el-table-column prop="standardValue" label="标准值" min-width="120" />
        <el-table-column prop="controlValue" label="内控值" min-width="120" />
        <el-table-column prop="defaultValue" label="默认值" min-width="120" />
        <el-table-column label="操作" width="140" fixed="right" align="center">
          <template #default="{ row }">
            <el-button link type="primary" size="small" :disabled="isStandardReadonly" @click="openParamDialog('edit', row)">
              编辑
          <div class="right-header">
            <div class="title">标准参数</div>
            <div class="desc"
                 v-if="currentStandard">
              您当前选择的检测标准编号是:
              <span class="link-text">{{ currentStandard.standardNo }}</span>
            </div>
            <div class="desc"
                 v-else>请先在左侧选择一个检测标准</div>
          </div>
          <div class="right-toolbar">
            <el-button type="primary"
                       :disabled="!currentStandard || isStandardReadonly"
                       @click="openParamDialog('add')">
              新增
            </el-button>
            <el-button link type="danger" size="small" :disabled="isStandardReadonly" @click="handleParamDelete(row)">
            <el-button type="danger"
                       plain
                       :disabled="!currentStandard || isStandardReadonly"
                       @click="handleParamBatchDelete">
              删除
            </el-button>
          </template>
        </el-table-column>
      </el-table>
          </div>
          <el-table v-loading="detailLoading"
                    :data="detailTableData"
                    border
                    :row-class-name="() => 'row-center'"
                    class="center-table"
                    style="width: 100%"
                    height="calc(100vh - 220px)"
                    @selection-change="handleParamSelectionChange">
            <el-table-column type="selection"
                             width="48"
                             align="center" />
            <el-table-column type="index"
                             label="序号"
                             width="60"
                             align="center" />
            <el-table-column prop="parameterItem"
                             label="参数项"
                             min-width="120" />
            <el-table-column prop="unit"
                             label="单位"
                             width="80" />
            <el-table-column prop="standardValue"
                             label="标准值"
                             min-width="120" />
            <el-table-column prop="controlValue"
                             label="内控值"
                             min-width="120" />
            <el-table-column prop="defaultValue"
                             label="默认值"
                             min-width="120" />
            <el-table-column label="操作"
                             width="140"
                             fixed="right"
                             align="center">
              <template #default="{ row }">
                <el-button link
                           type="primary"
                           size="small"
                           :disabled="isStandardReadonly"
                           @click="openParamDialog('edit', row)">
                  编辑
                </el-button>
                <el-button link
                           type="danger"
                           size="small"
                           :disabled="isStandardReadonly"
                           @click="handleParamDelete(row)">
                  删除
                </el-button>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </el-col>
    </el-row>
    <!-- 新增 / 编辑检测标准 -->
    <StandardFormDialog
      ref="standardFormDialogRef"
      v-model="standardDialogVisible"
      :operation-type="standardOperationType"
      :form="standardForm"
      :rules="standardRules"
      :inspect-type="activeTab"
      :process-options="processOptions"
      @confirm="submitStandardForm"
      @close="closeStandardDialog"
      @cancel="closeStandardDialog"
    />
    <ParamFormDialog
      ref="paramFormDialogRef"
      v-model="paramDialogVisible"
      :operation-type="paramOperationType"
      :inspectType="activeTab.value"
      :form="paramForm"
      @confirm="submitParamForm"
      @close="closeParamDialog"
      @cancel="closeParamDialog"
    />
    <StandardFormDialog ref="standardFormDialogRef"
                        v-model="standardDialogVisible"
                        :operation-type="standardOperationType"
                        :form="standardForm"
                        :rules="standardRules"
                        :inspect-type="activeTab"
                        :process-options="processOptions"
                        @confirm="submitStandardForm"
                        @close="closeStandardDialog"
                        @cancel="closeStandardDialog" />
    <ParamFormDialog ref="paramFormDialogRef"
                     v-model="paramDialogVisible"
                     :operation-type="paramOperationType"
                     :inspectType="activeTab.value"
                     :form="paramForm"
                     @confirm="submitParamForm"
                     @close="closeParamDialog"
                     @cancel="closeParamDialog" />
  </div>
</template>
<script setup>
import { Search } from '@element-plus/icons-vue'
import { ref, reactive, toRefs, onMounted, getCurrentInstance, computed } from 'vue'
import { ElMessageBox } from 'element-plus'
import {
  qualityTestStandardListPage,
  qualityTestStandardAdd,
  qualityTestStandardUpdate,
  qualityTestStandardDel,
  qualityTestStandardCopyParam,
  qualityTestStandardAudit,
  qualityTestStandardParamList,
  qualityTestStandardParamAdd,
  qualityTestStandardParamUpdate,
  qualityTestStandardParamDel
} from '@/api/qualityManagement/metricMaintenance.js'
import { productProcessListPage } from '@/api/basicData/productProcess.js'
import StandardFormDialog from './StandardFormDialog.vue'
import ParamFormDialog from './ParamFormDialog.vue'
  import { Search } from "@element-plus/icons-vue";
  import {
    ref,
    reactive,
    toRefs,
    onMounted,
    getCurrentInstance,
    computed,
  } from "vue";
  import { ElMessageBox } from "element-plus";
  import {
    qualityTestStandardListPage,
    qualityTestStandardAdd,
    qualityTestStandardUpdate,
    qualityTestStandardDel,
    qualityTestStandardCopyParam,
    qualityTestStandardAudit,
    qualityTestStandardParamList,
    qualityTestStandardParamAdd,
    qualityTestStandardParamUpdate,
    qualityTestStandardParamDel,
  } from "@/api/qualityManagement/metricMaintenance.js";
  import { productProcessListPage } from "@/api/basicData/productProcess.js";
  import StandardFormDialog from "./StandardFormDialog.vue";
  import ParamFormDialog from "./ParamFormDialog.vue";
const { proxy } = getCurrentInstance()
  const { proxy } = getCurrentInstance();
// 类别 Tab(0=原材料,1=过程,2=出厂)
const activeTab = ref('0')
  // 类别 Tab(0=原材料,1=过程,2=出厂)
  const activeTab = ref("0");
// 左侧标准列表:整行内容居中(配合样式)
const rowClassNameCenter = () => 'row-center'
  // 左侧标准列表:整行内容居中(配合样式)
  const rowClassNameCenter = () => "row-center";
// 标准状态为“通过(1)”时,右侧参数禁止增删改
const isStandardReadonly = computed(() => {
  const state = currentStandard.value?.state
  return state === 1 || state === '1'
})
  // 标准状态为“通过(1)”时,右侧参数禁止增删改
  const isStandardReadonly = computed(() => {
    const state = currentStandard.value?.state;
    return state === 1 || state === "1";
  });
// 搜索条件
const data = reactive({
  searchForm: {
    standardNo: '',
    standardName: '',
    remark: '',
    state: '',
    inspectType: '',
    processId: ''
  },
  standardForm: {
    id: undefined,
    standardNo: '',
    standardName: '',
    remark: '',
    state: '0',
    inspectType: '',
    processId: ''
  },
  standardRules: {
    standardNo: [{ required: true, message: '请输入标准编号', trigger: 'blur' }],
    standardName: [{ required: true, message: '请输入标准名称', trigger: 'blur' }],
    processId: [{
      validator: (_rule, value, callback) => {
        const inspectType = String(standardForm.value.inspectType ?? activeTab.value ?? '')
        if (inspectType === '1' && (value === '' || value === null || value === undefined)) {
          callback(new Error('请选择工序'))
          return
        }
        callback()
      },
      trigger: 'change'
    }]
  }
})
const { searchForm, standardForm, standardRules } = toRefs(data)
// 左侧表格
const standardTableData = ref([])
const selectedRows = ref([])
const tableLoading = ref(false)
const page = reactive({
  current: 1,
  size: 10,
  total: 0
})
// 工序下拉
const processOptions = ref([])
// 获取工序列表
const getProcessList = async () => {
  try {
    const res = await productProcessListPage({ current: 1, size: 1000 })
    if (res?.code === 200) {
      const records = res?.data?.records || []
      processOptions.value = records.map(item => ({
        label: item.processName || item.name || item.label,
        value: item.id || item.processId || item.value
      }))
    }
  } catch (error) {
    console.error('获取工序列表失败:', error)
  }
}
// 当前选中的标准 & 右侧详情
const currentStandard = ref(null)
const detailTableData = ref([])
const detailLoading = ref(false)
const paramSelectedRows = ref([])
const paramDialogVisible = ref(false)
const paramOperationType = ref('add') // add | edit
const paramFormDialogRef = ref(null)
const paramForm = reactive({
  id: undefined,
  parameterItem: '',
  unit: '',
  standardValue: '',
  controlValue: '',
  defaultValue: ''
})
// 弹窗
const standardDialogVisible = ref(false)
const standardOperationType = ref('add') // add | edit | copy
const standardFormDialogRef = ref(null)
// 列定义
const standardColumns = ref([
  {
    label: '标准编号',
    prop: 'standardNo',
    dataType: 'slot',
    slot: 'standardNoCell',
    minWidth: 160,
    align: 'center',
    headerSlot: 'standardNoHeader'
  },
  {
    label: '标准名称',
    prop: 'standardName',
    minWidth: 180,
    align: 'center',
    headerSlot: 'standardNameHeader'
  },
  {
    label: '类别',
    prop: 'inspectType',
    align: 'center',
    dataType: 'tag',
    minWidth: 120,
    formatData: (val) => {
      const map = {
        0: '原材料检验',
        1: '过程检验',
        2: '出厂检验'
      }
      return map[val] || val
    }
  },
  {
    label: '工序',
    prop: 'processId',
    align: 'center',
    dataType: 'tag',
    visible: visible => activeTab.value === '1',
    formatData: (val) => {
      const target = processOptions.value.find(
        (item) => String(item.value) === String(val)
      )
      return target?.label || val
    }
  },
  {
    label: '状态',
    prop: 'state',
    headerSlot: 'stateHeader',
    align: 'center',
    dataType: 'tag',
    formatData: (val) => {
      const map = {
        0: '草稿',
        1: '通过',
        2: '撤销'
      }
      return map[val] || val
  // 搜索条件
  const data = reactive({
    searchForm: {
      standardNo: "",
      standardName: "",
      remark: "",
      state: "",
      inspectType: "",
      processId: "",
    },
    formatType: (val) => {
      if (val === '1' || val === 1) return 'success'
      if (val === '2' || val === 2) return 'warning'
      return 'info'
    }
  },
  {
    label: '备注',
    prop: 'remark',
    minWidth: 160,
    align: 'center'
  },
  {
    dataType: 'action',
    label: '操作',
    align: 'center',
    fixed: 'right',
    width: 220,
    operation: [
      {
        name: '编辑',
        type: 'text',
        clickFun: (row) => {
          openStandardDialog('edit', row)
        }
      },
      {
        name: '复制',
        type: 'text',
        clickFun: async (row) => {
          if (!row?.id) return
          try {
            await ElMessageBox.confirm('确认复制该标准参数?', '提示', { type: 'warning' })
          } catch {
            return
          }
          await qualityTestStandardCopyParam(row.id)
          proxy.$message.success('复制成功')
          getStandardList()
          if (currentStandard.value?.id === row.id) {
            loadDetail(row.id)
          }
        }
      },
      {
        name: '删除',
        type: 'text',
        clickFun: (row) => {
          handleDelete(row)
        }
    standardForm: {
      id: undefined,
      standardNo: "",
      standardName: "",
      remark: "",
      state: "0",
      inspectType: "",
      processId: "",
    },
    standardRules: {
      standardNo: [
        { required: true, message: "请输入标准编号", trigger: "blur" },
      ],
      standardName: [
        { required: true, message: "请输入标准名称", trigger: "blur" },
      ],
      processId: [
        {
          validator: (_rule, value, callback) => {
            const inspectType = String(
              standardForm.value.inspectType ?? activeTab.value ?? ""
            );
            if (
              (inspectType === "1" || inspectType === "2") &&
              (value === "" || value === null || value === undefined)
            ) {
              callback(new Error("请选择工序"));
              return;
            }
            callback();
          },
          trigger: "change",
        },
      ],
    },
  });
  const { searchForm, standardForm, standardRules } = toRefs(data);
  // 左侧表格
  const standardTableData = ref([]);
  const selectedRows = ref([]);
  const tableLoading = ref(false);
  const page = reactive({
    current: 1,
    size: 10,
    total: 0,
  });
  // 工序下拉
  const processOptions = ref([]);
  // 获取工序列表
  const getProcessList = async () => {
    try {
      const res = await productProcessListPage({ current: 1, size: 1000 });
      if (res?.code === 200) {
        const records = res?.data?.records || [];
        processOptions.value = records.map(item => ({
          label: item.processName || item.name || item.label,
          value: item.id || item.processId || item.value,
        }));
      }
    ]
  }
])
const handleTabChange = () => {
  searchForm.value.standardNo = ''
  searchForm.value.standardName = ''
  searchForm.value.remark = ''
  searchForm.value.state = ''
  searchForm.value.processId = ''
  searchForm.value.inspectType = activeTab.value
  page.current = 1
  currentStandard.value = null
  detailTableData.value = []
  paramSelectedRows.value = []
  getStandardList()
}
    } catch (error) {
      console.error("获取工序列表失败:", error);
    }
  };
// 查询列表
const getStandardList = () => {
  tableLoading.value = true
  const params = {
    ...searchForm.value,
    current: page.current,
    size: page.size
  }
  qualityTestStandardListPage(params)
    .then((res) => {
      const records = res?.data?.records || []
      standardTableData.value = records
      page.total = res?.data?.total || records.length
  // 当前选中的标准 & 右侧详情
  const currentStandard = ref(null);
  const detailTableData = ref([]);
  const detailLoading = ref(false);
  const paramSelectedRows = ref([]);
  const paramDialogVisible = ref(false);
  const paramOperationType = ref("add"); // add | edit
  const paramFormDialogRef = ref(null);
  const paramForm = reactive({
    id: undefined,
    parameterItem: "",
    unit: "",
    standardValue: "",
    controlValue: "",
    defaultValue: "",
  });
  // 弹窗
  const standardDialogVisible = ref(false);
  const standardOperationType = ref("add"); // add | edit | copy
  const standardFormDialogRef = ref(null);
  // 列定义
  const standardColumns = ref([
    {
      label: "标准编号",
      prop: "standardNo",
      dataType: "slot",
      slot: "standardNoCell",
      minWidth: 160,
      align: "center",
      headerSlot: "standardNoHeader",
    },
    {
      label: "标准名称",
      prop: "standardName",
      minWidth: 180,
      align: "center",
      headerSlot: "standardNameHeader",
    },
    {
      label: "类别",
      prop: "inspectType",
      align: "center",
      dataType: "tag",
      minWidth: 120,
      formatData: val => {
        const map = {
          0: "原材料检验",
          1: "过程检验",
          2: "出厂检验",
        };
        return map[val] || val;
      },
    },
    {
      label: "工序",
      prop: "processId",
      align: "center",
      dataType: "tag",
      visible: visible => ["1", "2"].includes(String(activeTab.value)),
      formatData: val => {
        const target = processOptions.value.find(
          item => String(item.value) === String(val)
        );
        return target?.label || val;
      },
    },
    {
      label: "状态",
      prop: "state",
      headerSlot: "stateHeader",
      align: "center",
      dataType: "tag",
      formatData: val => {
        const map = {
          0: "草稿",
          1: "通过",
          2: "撤销",
        };
        return map[val] || val;
      },
      formatType: val => {
        if (val === "1" || val === 1) return "success";
        if (val === "2" || val === 2) return "warning";
        return "info";
      },
    },
    {
      label: "备注",
      prop: "remark",
      minWidth: 160,
      align: "center",
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
      width: 220,
      operation: [
        {
          name: "编辑",
          type: "text",
          clickFun: row => {
            openStandardDialog("edit", row);
          },
        },
        {
          name: "复制",
          type: "text",
          clickFun: async row => {
            if (!row?.id) return;
            try {
              await ElMessageBox.confirm("确认复制该标准参数?", "提示", {
                type: "warning",
              });
            } catch {
              return;
            }
            await qualityTestStandardCopyParam(row.id);
            proxy.$message.success("复制成功");
            getStandardList();
            if (currentStandard.value?.id === row.id) {
              loadDetail(row.id);
            }
          },
        },
        {
          name: "删除",
          type: "text",
          clickFun: row => {
            handleDelete(row);
          },
        },
      ],
    },
  ]);
  const handleTabChange = () => {
    searchForm.value.standardNo = "";
    searchForm.value.standardName = "";
    searchForm.value.remark = "";
    searchForm.value.state = "";
    searchForm.value.processId = "";
    searchForm.value.inspectType = activeTab.value;
    page.current = 1;
    currentStandard.value = null;
    detailTableData.value = [];
    paramSelectedRows.value = [];
    getStandardList();
  };
  // 查询列表
  const getStandardList = () => {
    tableLoading.value = true;
    const params = {
      ...searchForm.value,
      current: page.current,
      size: page.size,
    };
    qualityTestStandardListPage(params)
      .then(res => {
        const records = res?.data?.records || [];
        standardTableData.value = records;
        page.total = res?.data?.total || records.length;
      })
      .finally(() => {
        tableLoading.value = false;
      });
  };
  const handleQuery = () => {
    page.current = 1;
    getStandardList();
  };
  const resetQuery = () => {
    searchForm.value.standardNo = "";
    searchForm.value.standardName = "";
    searchForm.value.remark = "";
    searchForm.value.state = "";
    searchForm.value.inspectType = "";
    searchForm.value.processId = "";
    handleQuery();
  };
  const handlePagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getStandardList();
  };
  const handleSelectionChange = selection => {
    selectedRows.value = selection;
  };
  // 批量审核:状态 1=批准,2=撤销
  const handleBatchAudit = async state => {
    if (!selectedRows.value.length) {
      proxy.$message.warning("请选择数据");
      return;
    }
    const text = state === 1 ? "批准" : "撤销";
    const payload = selectedRows.value
      .filter(i => i?.id)
      .map(item => ({ id: item.id, state }));
    if (!payload.length) {
      proxy.$message.warning("请选择有效数据");
      return;
    }
    try {
      await ElMessageBox.confirm(`确认${text}选中的标准?`, "提示", {
        type: "warning",
      });
    } catch {
      return;
    }
    await qualityTestStandardAudit(payload);
    proxy.$message.success(`${text}成功`);
    getStandardList();
  };
  // 表格行点击,加载右侧参数
  const handleTableRowClick = row => {
    currentStandard.value = row;
    loadDetail(row.id);
  };
  // 左侧行点击,加载右侧参数(保留用于标准编号列的点击)
  const handleStandardRowClick = row => {
    currentStandard.value = row;
    loadDetail(row.id);
  };
  const loadDetail = standardId => {
    if (!standardId) {
      detailTableData.value = [];
      return;
    }
    detailLoading.value = true;
    qualityTestStandardParamList({ testStandardId: standardId })
      .then(res => {
        detailTableData.value = res?.data || [];
      })
      .finally(() => {
        detailLoading.value = false;
      });
  };
  const handleParamSelectionChange = selection => {
    paramSelectedRows.value = selection;
  };
  const openParamDialog = (type, row) => {
    if (!currentStandard.value?.id) return;
    if (isStandardReadonly.value) {
      proxy.$message.warning("该标准已通过,参数不可编辑");
      return;
    }
    paramOperationType.value = type;
    if (type === "add") {
      Object.assign(paramForm, {
        id: undefined,
        parameterItem: "",
        unit: "",
        standardValue: "",
        controlValue: "",
        defaultValue: "",
      });
    } else if (type === "edit" && row) {
      Object.assign(paramForm, row);
    }
    paramDialogVisible.value = true;
  };
  const closeParamDialog = () => {
    paramDialogVisible.value = false;
    paramFormDialogRef.value?.resetFields?.();
  };
  const submitParamForm = async () => {
    const testStandardId = currentStandard.value?.id;
    if (!testStandardId) return;
    if (isStandardReadonly.value) {
      proxy.$message.warning("该标准已通过,参数不可编辑");
      return;
    }
    const payload = { ...paramForm, testStandardId };
    if (paramOperationType.value === "edit") {
      await qualityTestStandardParamUpdate(payload);
      proxy.$message.success("提交成功");
    } else {
      await qualityTestStandardParamAdd(payload);
      proxy.$message.success("提交成功");
    }
    closeParamDialog();
    loadDetail(testStandardId);
  };
  const handleParamDelete = async row => {
    if (!row?.id) return;
    if (isStandardReadonly.value) {
      proxy.$message.warning("该标准已通过,参数不可编辑");
      return;
    }
    try {
      await ElMessageBox.confirm("确认删除该参数?", "提示", { type: "warning" });
    } catch {
      return;
    }
    await qualityTestStandardParamDel([row.id]);
    proxy.$message.success("删除成功");
    loadDetail(currentStandard.value?.id);
  };
  const handleParamBatchDelete = async () => {
    if (isStandardReadonly.value) {
      proxy.$message.warning("该标准已通过,参数不可编辑");
      return;
    }
    if (!paramSelectedRows.value.length) {
      proxy.$message.warning("请选择数据");
      return;
    }
    const ids = paramSelectedRows.value.map(i => i.id);
    try {
      await ElMessageBox.confirm(
        "选中的内容将被删除,是否确认删除?",
        "删除提示",
        { type: "warning" }
      );
    } catch {
      return;
    }
    await qualityTestStandardParamDel(ids);
    proxy.$message.success("删除成功");
    loadDetail(currentStandard.value?.id);
  };
  // 新增 / 编辑 / 复制
  const openStandardDialog = (type, row) => {
    standardOperationType.value = type;
    if (type === "add") {
      Object.assign(standardForm.value, {
        id: undefined,
        standardNo: "",
        standardName: "",
        remark: "",
        state: "0",
        inspectType: activeTab.value,
        processId: ["1", "2"].includes(String(activeTab.value)) ? "" : null,
      });
    } else if (type === "edit" && row) {
      Object.assign(standardForm.value, {
        ...row,
        // 确保 inspectType 和 state 转换为字符串,以匹配 el-select 的 value 类型
        inspectType:
          row.inspectType !== null && row.inspectType !== undefined
            ? String(row.inspectType)
            : "",
        state:
          row.state !== null && row.state !== undefined ? String(row.state) : "0",
        // 确保 processId 转换为字符串或数字(根据实际需要)
        processId:
          ["1", "2"].includes(String(row.inspectType)) &&
          row.processId !== null &&
          row.processId !== undefined
            ? row.processId
            : null,
      });
    } else if (type === "copy" && row) {
      const { id, ...rest } = row;
      Object.assign(standardForm.value, {
        ...rest,
        id: undefined,
        standardNo: "",
        state: "0",
        // 确保 inspectType 转换为字符串
        inspectType: activeTab.value,
        processId: ["1", "2"].includes(String(activeTab.value))
          ? rest.processId ?? ""
          : null,
      });
    }
    standardDialogVisible.value = true;
  };
  const closeStandardDialog = () => {
    standardDialogVisible.value = false;
    standardFormDialogRef.value?.resetFields?.();
  };
  const submitStandardForm = () => {
    const payload = { ...standardForm.value };
    payload.inspectType = activeTab.value;
    if (!["1", "2"].includes(String(payload.inspectType))) {
      payload.processId = null;
    }
    const isEdit = standardOperationType.value === "edit";
    if (isEdit) {
      qualityTestStandardUpdate(payload).then(() => {
        proxy.$message.success("提交成功");
        standardDialogVisible.value = false;
        getStandardList();
      });
    } else {
      qualityTestStandardAdd(payload).then(() => {
        proxy.$message.success("提交成功");
        standardDialogVisible.value = false;
        getStandardList();
      });
    }
  };
  // 删除(单条)
  const handleDelete = row => {
    const ids = [row.id];
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
    .finally(() => {
      tableLoading.value = false
      .then(() => {
        tableLoading.value = true;
        qualityTestStandardDel(ids)
          .then(() => {
            proxy.$message.success("删除成功");
            getStandardList();
            if (currentStandard.value && currentStandard.value.id === row.id) {
              currentStandard.value = null;
              detailTableData.value = [];
            }
          })
          .finally(() => {
            tableLoading.value = false;
          });
      })
      .catch(() => {
        proxy.$modal?.msg("已取消");
      });
  };
  // 批量删除
  const handleBatchDelete = () => {
    if (!selectedRows.value.length) {
      proxy.$message.warning("请选择数据");
      return;
    }
    const ids = selectedRows.value.map(item => item.id);
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
}
      .then(() => {
        tableLoading.value = true;
        qualityTestStandardDel(ids)
          .then(() => {
            proxy.$message.success("删除成功");
            getStandardList();
            if (currentStandard.value && ids.includes(currentStandard.value.id)) {
              currentStandard.value = null;
              detailTableData.value = [];
            }
          })
          .finally(() => {
            tableLoading.value = false;
          });
      })
      .catch(() => {
        proxy.$modal?.msg("已取消");
      });
  };
const handleQuery = () => {
  page.current = 1
  getStandardList()
}
const resetQuery = () => {
  searchForm.value.standardNo = ''
  searchForm.value.standardName = ''
  searchForm.value.remark = ''
  searchForm.value.state = ''
  searchForm.value.inspectType = ''
  searchForm.value.processId = ''
  handleQuery()
}
const handlePagination = (obj) => {
  page.current = obj.page
  page.size = obj.limit
  getStandardList()
}
const handleSelectionChange = (selection) => {
  selectedRows.value = selection
}
// 批量审核:状态 1=批准,2=撤销
const handleBatchAudit = async (state) => {
  if (!selectedRows.value.length) {
    proxy.$message.warning('请选择数据')
    return
  }
  const text = state === 1 ? '批准' : '撤销'
  const payload = selectedRows.value
    .filter(i => i?.id)
    .map((item) => ({ id: item.id, state }))
  if (!payload.length) {
    proxy.$message.warning('请选择有效数据')
    return
  }
  try {
    await ElMessageBox.confirm(`确认${text}选中的标准?`, '提示', { type: 'warning' })
  } catch {
    return
  }
  await qualityTestStandardAudit(payload)
  proxy.$message.success(`${text}成功`)
  getStandardList()
}
// 表格行点击,加载右侧参数
const handleTableRowClick = (row) => {
  currentStandard.value = row
  loadDetail(row.id)
}
// 左侧行点击,加载右侧参数(保留用于标准编号列的点击)
const handleStandardRowClick = (row) => {
  currentStandard.value = row
  loadDetail(row.id)
}
const loadDetail = (standardId) => {
  if (!standardId) {
    detailTableData.value = []
    return
  }
  detailLoading.value = true
  qualityTestStandardParamList({ testStandardId: standardId }).then((res) => {
    detailTableData.value = res?.data || []
  })
    .finally(() => {
      detailLoading.value = false
    })
}
const handleParamSelectionChange = (selection) => {
  paramSelectedRows.value = selection
}
const openParamDialog = (type, row) => {
  if (!currentStandard.value?.id) return
  if (isStandardReadonly.value) {
    proxy.$message.warning('该标准已通过,参数不可编辑')
    return
  }
  paramOperationType.value = type
  if (type === 'add') {
    Object.assign(paramForm, {
      id: undefined,
      parameterItem: '',
      unit: '',
      standardValue: '',
      controlValue: '',
      defaultValue: ''
    })
  } else if (type === 'edit' && row) {
    Object.assign(paramForm, row)
  }
  paramDialogVisible.value = true
}
const closeParamDialog = () => {
  paramDialogVisible.value = false
  paramFormDialogRef.value?.resetFields?.()
}
const submitParamForm = async () => {
  const testStandardId = currentStandard.value?.id
  if (!testStandardId) return
  if (isStandardReadonly.value) {
    proxy.$message.warning('该标准已通过,参数不可编辑')
    return
  }
  const payload = { ...paramForm, testStandardId }
  if (paramOperationType.value === 'edit') {
    await qualityTestStandardParamUpdate(payload)
    proxy.$message.success('提交成功')
  } else {
    await qualityTestStandardParamAdd(payload)
    proxy.$message.success('提交成功')
  }
  closeParamDialog()
  loadDetail(testStandardId)
}
const handleParamDelete = async (row) => {
  if (!row?.id) return
  if (isStandardReadonly.value) {
    proxy.$message.warning('该标准已通过,参数不可编辑')
    return
  }
  try {
    await ElMessageBox.confirm('确认删除该参数?', '提示', { type: 'warning' })
  } catch {
    return
  }
  await qualityTestStandardParamDel([row.id])
  proxy.$message.success('删除成功')
  loadDetail(currentStandard.value?.id)
}
const handleParamBatchDelete = async () => {
  if (isStandardReadonly.value) {
    proxy.$message.warning('该标准已通过,参数不可编辑')
    return
  }
  if (!paramSelectedRows.value.length) {
    proxy.$message.warning('请选择数据')
    return
  }
  const ids = paramSelectedRows.value.map((i) => i.id)
  try {
    await ElMessageBox.confirm('选中的内容将被删除,是否确认删除?', '删除提示', { type: 'warning' })
  } catch {
    return
  }
  await qualityTestStandardParamDel(ids)
  proxy.$message.success('删除成功')
  loadDetail(currentStandard.value?.id)
}
// 新增 / 编辑 / 复制
const openStandardDialog = (type, row) => {
  standardOperationType.value = type
  if (type === 'add') {
    Object.assign(standardForm.value, {
      id: undefined,
      standardNo: '',
      standardName: '',
      remark: '',
      state: '0',
      inspectType: activeTab.value,
      processId: activeTab.value === '1' ? '' : null
    })
  } else if (type === 'edit' && row) {
    Object.assign(standardForm.value, {
      ...row,
      // 确保 inspectType 和 state 转换为字符串,以匹配 el-select 的 value 类型
      inspectType: row.inspectType !== null && row.inspectType !== undefined ? String(row.inspectType) : '',
      state: row.state !== null && row.state !== undefined ? String(row.state) : '0',
      // 确保 processId 转换为字符串或数字(根据实际需要)
      processId: String(row.inspectType) === '1' && row.processId !== null && row.processId !== undefined ? row.processId : null
    })
  } else if (type === 'copy' && row) {
    const { id, ...rest } = row
    Object.assign(standardForm.value, {
      ...rest,
      id: undefined,
      standardNo: '',
      state: '0',
      // 确保 inspectType 转换为字符串
      inspectType: activeTab.value,
      processId: activeTab.value === '1' ? (rest.processId ?? '') : null
    })
  }
  standardDialogVisible.value = true
}
const closeStandardDialog = () => {
  standardDialogVisible.value = false
  standardFormDialogRef.value?.resetFields?.()
}
const submitStandardForm = () => {
  const payload = { ...standardForm.value }
  payload.inspectType = activeTab.value
  if (String(payload.inspectType) !== '1') {
    payload.processId = null
  }
  const isEdit = standardOperationType.value === 'edit'
  if (isEdit) {
    qualityTestStandardUpdate(payload).then(() => {
      proxy.$message.success('提交成功')
      standardDialogVisible.value = false
      getStandardList()
    })
  } else {
    qualityTestStandardAdd(payload).then(() => {
       proxy.$message.success('提交成功')
      standardDialogVisible.value = false
      getStandardList()
    })
  }
}
// 删除(单条)
const handleDelete = (row) => {
  const ids = [row.id]
  ElMessageBox.confirm('选中的内容将被删除,是否确认删除?', '删除提示', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning'
  })
    .then(() => {
      tableLoading.value = true
      qualityTestStandardDel(ids)
        .then(() => {
          proxy.$message.success('删除成功')
          getStandardList()
          if (currentStandard.value && currentStandard.value.id === row.id) {
            currentStandard.value = null
            detailTableData.value = []
          }
        })
        .finally(() => {
          tableLoading.value = false
        })
    })
    .catch(() => {
      proxy.$modal?.msg('已取消')
    })
}
// 批量删除
const handleBatchDelete = () => {
  if (!selectedRows.value.length) {
    proxy.$message.warning('请选择数据')
    return
  }
  const ids = selectedRows.value.map((item) => item.id)
  ElMessageBox.confirm('选中的内容将被删除,是否确认删除?', '删除提示', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning'
  })
    .then(() => {
      tableLoading.value = true
      qualityTestStandardDel(ids)
        .then(() => {
           proxy.$message.success('删除成功')
          getStandardList()
          if (currentStandard.value && ids.includes(currentStandard.value.id)) {
            currentStandard.value = null
            detailTableData.value = []
          }
        })
        .finally(() => {
          tableLoading.value = false
        })
    })
    .catch(() => {
      proxy.$modal?.msg('已取消')
    })
}
onMounted(() => {
  searchForm.value.inspectType = activeTab.value
  getProcessList()
  getStandardList()
})
  onMounted(() => {
    searchForm.value.inspectType = activeTab.value;
    getProcessList();
    getStandardList();
  });
</script>
<style scoped>
.metric-maintenance {
  padding: 0;
  min-width: 0;
}
  .metric-maintenance {
    padding: 0;
    min-width: 0;
  }
.metric-tabs {
  margin: 10px;
}
  .metric-tabs {
    margin: 10px;
  }
.metric-maintenance-row {
  width: 100%;
}
  .metric-maintenance-row {
    width: 100%;
  }
.metric-maintenance-row .left-col,
.metric-maintenance-row .right-col {
  margin-bottom: 16px;
}
  .metric-maintenance-row .left-col,
  .metric-maintenance-row .right-col {
    margin-bottom: 16px;
  }
.left-panel,
.right-panel {
  min-width: 0;
  background: #ffffff;
  padding: 16px;
  box-sizing: border-box;
  overflow: hidden;
  height: 100%;
  min-height: 400px;
}
@media (max-width: 768px) {
  .left-panel,
  .right-panel {
    padding: 12px;
    min-width: 0;
    background: #ffffff;
    padding: 16px;
    box-sizing: border-box;
    overflow: hidden;
    height: 100%;
    min-height: 400px;
  }
}
.toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
  flex-wrap: wrap;
  gap: 8px;
}
  @media (max-width: 768px) {
    .left-panel,
    .right-panel {
      padding: 12px;
    }
  }
.toolbar-left {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px;
}
  .toolbar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 12px;
    flex-wrap: wrap;
    gap: 8px;
  }
.toolbar-right {
  flex-shrink: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
  .toolbar-left {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px;
  }
.search-label {
  margin: 0 4px 0 12px;
}
  .toolbar-right {
    flex-shrink: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
  }
.search-label:first-of-type {
  margin-left: 0;
}
  .search-label {
    margin: 0 4px 0 12px;
  }
.right-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 10px;
}
  .search-label:first-of-type {
    margin-left: 0;
  }
.right-header .title {
  font-size: 16px;
  font-weight: 600;
}
  .right-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: 10px;
  }
.right-header .desc {
  font-size: 13px;
  color: #666;
}
  .right-header .title {
    font-size: 16px;
    font-weight: 600;
  }
.right-toolbar {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-bottom: 10px;
}
  .right-header .desc {
    font-size: 13px;
    color: #666;
  }
.link-text {
  color: #409eff;
  cursor: default;
}
  .right-toolbar {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    margin-bottom: 10px;
  }
.clickable-link {
  color: #409eff;
  cursor: pointer;
}
  .link-text {
    color: #409eff;
    cursor: default;
  }
.clickable-link:hover {
  text-decoration: underline;
}
  .clickable-link {
    color: #409eff;
    cursor: pointer;
  }
:deep(.row-center td) {
  text-align: center !important;
}
  .clickable-link:hover {
    text-decoration: underline;
  }
/* el-table 表头/内容统一居中(row-class-name 不作用于表头) */
:deep(.center-table .el-table__header-wrapper th .cell) {
  text-align: center !important;
}
:deep(.center-table .el-table__body-wrapper td .cell) {
  text-align: center !important;
}
  :deep(.row-center td) {
    text-align: center !important;
  }
/* PIMTable 表头居中 */
:deep(.lims-table .pim-table-header-cell) {
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
  /* el-table 表头/内容统一居中(row-class-name 不作用于表头) */
  :deep(.center-table .el-table__header-wrapper th .cell) {
    text-align: center !important;
  }
  :deep(.center-table .el-table__body-wrapper td .cell) {
    text-align: center !important;
  }
:deep(.lims-table .pim-table-header-title) {
  text-align: center;
  width: 100%;
}
  /* PIMTable 表头居中 */
  :deep(.lims-table .pim-table-header-cell) {
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
  }
:deep(.lims-table .pim-table-header-extra) {
  width: 100%;
  margin-top: 4px;
}
  :deep(.lims-table .pim-table-header-title) {
    text-align: center;
    width: 100%;
  }
  :deep(.lims-table .pim-table-header-extra) {
    width: 100%;
    margin-top: 4px;
  }
</style>