pinia和Vuex的区别以及使用pinia

pinia不用像Vuex那样,把同步,异步函数分开。

API 风格对比

Vuex 示例(Option 风格)
javascript 复制代码
// store/index.js
export default new Vuex.Store({
  state: {
    count: 0
  },
  getters: {
    doubleCount(state) {
      return state.count * 2
    }
  },
  mutations: {
    increment(state) {
      state.count++
    }
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment')
      }, 1000)
    }
  }
})

使用
this.$store.state.count
this.$store.commit('increment')
this.$store.dispatch('incrementAsync')

问题
mutations / actions 区分不直观
字符串形式的 mutation type 易出错
TypeScript 支持差
Pinia 示例(Composition API 风格)
javascript 复制代码
// stores/counter.ts
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)

  const doubleCount = computed(() => count.value * 2)

  function increment() {
    count.value++
  }

  function incrementAsync() {
    setTimeout(() => {
      count.value++
    }, 1000)
  }

  return {
    count,
    doubleCount,
    increment,
    incrementAsync
  }
})

使用
const counter = useCounterStore()
counter.count
counter.increment()
counter.incrementAsync()

优点
像写 setup() 一样写 store
无需 commit / dispatch
逻辑更自然

模块化方式对比

Vuex Modules
javascript 复制代码
modules: {
  user,
  cart
}

问题:
命名空间复杂
嵌套模块难以维护
Pinia(天然模块化)
javascript 复制代码
stores/
  user.ts
  cart.ts

✅ 每个 store 独立
✅ 无需 namespace
✅ 可自由组合

性能与体积

javascript 复制代码
Pinia 更轻量
无 mutations 逻辑
Tree-shaking 友好

核心概念差异

Vuex
html 复制代码
State
Getters
Mutations ✅
Actions
Modules
Pinia
html 复制代码
State
Getters
Actions ✅

📌 关键区别:
Pinia 移除了 Mutations
所有状态变更统一在 actions 中完成

✅ 优点:
减少概念
降低心智负担
更符合组合式 API 风格

总体定位对比

使用pinia

stores/production.js
javascript 复制代码
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

/**
 * 生产管理 Store
 * 统一管理:生产工单、工序排程、设备状态 三类业务数据
 * 采用 Setup Store 写法:state 用 ref,getters 用 computed,actions 用普通函数
 */
export const useProductionStore = defineStore('production', () => {

  /* ===================== 常量 ===================== */

  /** 工单状态流转 */
  const orderStatusFlow = ['待排产', '已排产', '生产中', '已完工', '已取消']

  /** 工序状态 */
  const processStatusList = ['未开始', '进行中', '已完成', '已延期', '已取消']

  /** 设备状态 */
  const equipmentStatusList = ['运行', '停机', '维修', '保养', '故障']

  /* ===================== State:生产工单 ===================== */

  /**
   * 生产工单列表
   * 结构: {
   *   id, orderNo(工单号), productName(产品名称), productCode(产品编码), spec(规格型号),
   *   quantity(计划数量), completedQty(已完成数量), unit(单位),
   *   priority(紧急/高/中/低), type(新产品/常规/返修/来料加工),
   *   status(待排产/已排产/生产中/已完工/已取消),
   *   line(产线), planStart, planEnd, actualStart, actualEnd,
   *   bom: [{ name, qty, unit, code }],  // 物料清单
   *   qualityStandard(质量标准), remark
   * }
   */
  const workOrders = ref([
    {
      id: 1, orderNo: 'WO-2026-0901', productName: '减速器总成 A型', productCode: 'RDC-A-001', spec: '1:30',
      quantity: 200, completedQty: 120, unit: '台', priority: '高', type: '常规', status: '生产中',
      line: '一号装配线', planStart: '2026-09-01', planEnd: '2026-09-15', actualStart: '2026-09-02', actualEnd: '',
      bom: [
        { name: '齿轮箱外壳', code: 'HO-001', qty: 1, unit: '个' },
        { name: '齿轮副', code: 'GR-002', qty: 1, unit: '套' },
        { name: '轴承', code: 'BR-6205', qty: 4, unit: '个' },
        { name: '润滑脂', code: 'GS-001', qty: 0.2, unit: 'kg' },
      ],
      qualityStandard: '噪音 ≤ 65dB,扭矩偏差 ≤ 5%', remark: '需全检',
    },
    {
      id: 2, orderNo: 'WO-2026-0902', productName: '铝合金支架 B型', productCode: 'AL-B-022', spec: '300×200×5mm',
      quantity: 500, completedQty: 500, unit: '件', priority: '中', type: '常规', status: '已完工',
      line: '二号焊接线', planStart: '2026-09-03', planEnd: '2026-09-08', actualStart: '2026-09-03', actualEnd: '2026-09-07',
      bom: [
        { name: '铝合金型材', code: 'AL-6063', qty: 2.5, unit: 'kg' },
        { name: '不锈钢螺钉', code: 'SC-M4', qty: 8, unit: '个' },
      ],
      qualityStandard: '尺寸公差 ±0.1mm,表面无划痕', remark: '',
    },
    {
      id: 3, orderNo: 'WO-2026-0903', productName: '控制电路板 C型', productCode: 'PCB-C-100', spec: '4层板',
      quantity: 300, completedQty: 0, unit: '块', priority: '紧急', type: '新产品', status: '待排产',
      line: '', planStart: '2026-09-12', planEnd: '2026-09-20', actualStart: '', actualEnd: '',
      bom: [
        { name: 'PCB 基板', code: 'PCB-4L', qty: 1, unit: '块' },
        { name: '贴片元件包', code: 'SMD-KIT', qty: 1, unit: '套' },
        { name: '连接器', code: 'CN-8P', qty: 2, unit: '个' },
      ],
      qualityStandard: '焊点无虚焊,通电测试通过率 ≥ 99%', remark: '首次试产,需工艺验证',
    },
    {
      id: 4, orderNo: 'WO-2026-0904', productName: '不锈钢外壳 D型', productCode: 'SS-D-050', spec: '1.2mm 304',
      quantity: 150, completedQty: 45, unit: '个', priority: '低', type: '返修', status: '生产中',
      line: '三号喷涂线', planStart: '2026-09-05', planEnd: '2026-09-18', actualStart: '2026-09-06', actualEnd: '',
      bom: [
        { name: '304 不锈钢板', code: 'SS-304-12', qty: 0.8, unit: 'kg' },
        { name: '粉末涂料', code: 'POW-W', qty: 0.05, unit: 'kg' },
      ],
      qualityStandard: '涂层厚度 60-80μm,附着力 1 级', remark: '返修批次,加强外观检验',
    },
  ])

  /* ===================== State:工序排程 ===================== */

  /**
   * 工序排程列表
   * 结构: {
   *   id, scheduleNo, workOrderId(关联工单), workOrderNo,
   *   line, processName(工序名), processSeq(工序序号),
   *   operator(操作工), planStart/planEnd/actualStart/actualEnd,
   *   status(未开始/进行中/已完成/已延期/已取消),
   *   durationHours(计划工时), actualHours(实际工时),
   *   resources(资源: [{ name, type }]), remark
   * }
   */
  const processSchedules = ref([
    {
      id: 1, scheduleNo: 'PS-2026-0901-01', workOrderId: 1, workOrderNo: 'WO-2026-0901',
      line: '一号装配线', processName: '齿轮装配', processSeq: 1, operator: '王强',
      planStart: '2026-09-02 08:00', planEnd: '2026-09-03 18:00', actualStart: '2026-09-02 08:15', actualEnd: '',
      status: '进行中', durationHours: 16, actualHours: 8,
      resources: [{ name: '装配工作台 A1', type: '设备' }, { name: '扭力扳手 TN-01', type: '工具' }],
      remark: '',
    },
    {
      id: 2, scheduleNo: 'PS-2026-0901-02', workOrderId: 1, workOrderNo: 'WO-2026-0901',
      line: '一号装配线', processName: '整机装配', processSeq: 2, operator: '张伟',
      planStart: '2026-09-04 08:00', planEnd: '2026-09-08 18:00', actualStart: '', actualEnd: '',
      status: '未开始', durationHours: 32, actualHours: 0,
      resources: [{ name: '装配工作台 A2', type: '设备' }],
      remark: '等待齿轮装配完成',
    },
    {
      id: 3, scheduleNo: 'PS-2026-0901-03', workOrderId: 1, workOrderNo: 'WO-2026-0901',
      line: '一号装配线', processName: '性能测试', processSeq: 3, operator: '李娜',
      planStart: '2026-09-09 08:00', planEnd: '2026-09-10 18:00', actualStart: '', actualEnd: '',
      status: '未开始', durationHours: 16, actualHours: 0,
      resources: [{ name: '测试台 TT-02', type: '设备' }, { name: '噪音检测仪', type: '仪器' }],
      remark: '',
    },
    {
      id: 4, scheduleNo: 'PS-2026-0904-01', workOrderId: 4, workOrderNo: 'WO-2026-0904',
      line: '三号喷涂线', processName: '喷涂烘干', processSeq: 1, operator: '赵敏',
      planStart: '2026-09-06 08:00', planEnd: '2026-09-07 12:00', actualStart: '2026-09-06 08:00', actualEnd: '2026-09-07 10:00',
      status: '已完成', durationHours: 12, actualHours: 10,
      resources: [{ name: '喷涂机器人 RB-01', type: '设备' }, { name: '烘干炉 OV-03', type: '设备' }],
      remark: '提前 2 小时完成',
    },
  ])

  /* ===================== State:设备状态 ===================== */

  /**
   * 设备台账
   * 结构: {
   *   id, equipmentNo, name, model, line, type,
   *   status(运行/停机/维修/保养/故障),
   *   temperature(℃), speed(转速/速率), runHours(运行小时),
   *   utilizationRate(利用率 %), lastMaintainDate, nextMaintainDate,
   *   currentTask(当前任务), errorCount(本月故障次数)
   * }
   */
  const equipments = ref([
    { id: 1, equipmentNo: 'EQ-001', name: '装配工作台 A1', model: 'ASM-A1', line: '一号装配线', type: '装配设备', status: '运行', temperature: 42, speed: 120, runHours: 1280, utilizationRate: 88, lastMaintainDate: '2026-08-10', nextMaintainDate: '2026-09-25', currentTask: '齿轮装配', errorCount: 0 },
    { id: 2, equipmentNo: 'EQ-002', name: '装配工作台 A2', model: 'ASM-A2', line: '一号装配线', type: '装配设备', status: '空闲', temperature: 28, speed: 0, runHours: 980, utilizationRate: 45, lastMaintainDate: '2026-08-20', nextMaintainDate: '2026-09-30', currentTask: '', errorCount: 1 },
    { id: 3, equipmentNo: 'EQ-003', name: '焊接机器人 RB-01', model: 'ABB-IRB6700', line: '二号焊接线', type: '焊接设备', status: '运行', temperature: 56, speed: 85, runHours: 2100, utilizationRate: 92, lastMaintainDate: '2026-07-15', nextMaintainDate: '2026-09-15', currentTask: '铝合金支架焊接', errorCount: 0 },
    { id: 4, equipmentNo: 'EQ-004', name: '喷涂机器人 RB-01', model: 'FANUC-P250', line: '三号喷涂线', type: '喷涂设备', status: '维修', temperature: 0, speed: 0, runHours: 1560, utilizationRate: 0, lastMaintainDate: '2026-09-10', nextMaintainDate: '2026-09-10', currentTask: '', errorCount: 2 },
    { id: 5, equipmentNo: 'EQ-005', name: '烘干炉 OV-03', model: 'OV-300', line: '三号喷涂线', type: '热处理设备', status: '运行', temperature: 180, speed: 0, runHours: 3400, utilizationRate: 76, lastMaintainDate: '2026-08-01', nextMaintainDate: '2026-10-01', currentTask: '外壳烘干', errorCount: 0 },
    { id: 6, equipmentNo: 'EQ-006', name: '测试台 TT-02', model: 'TEST-200', line: '一号装配线', type: '测试设备', status: '保养', temperature: 0, speed: 0, runHours: 650, utilizationRate: 0, lastMaintainDate: '2026-09-12', nextMaintainDate: '2026-09-12', currentTask: '', errorCount: 0 },
  ])

  /* ===================== State:设备维修记录 ===================== */

  /**
   * 设备维修/保养记录
   * 结构: { id, equipmentNo, equipmentName, type(故障维修/定期保养/点检), description, handler, startTime, endTime, durationHours, cost(元), parts(更换配件[]), status(处理中/已完成) }
   */
  const maintenanceRecords = ref([
    { id: 1, equipmentNo: 'EQ-004', equipmentName: '喷涂机器人 RB-01', type: '故障维修', description: '喷枪堵塞,雾化不良', handler: '陈刚', startTime: '2026-09-10 09:00', endTime: '', durationHours: 0, cost: 0, parts: [{ name: '喷枪', qty: 1 }], status: '处理中' },
    { id: 2, equipmentNo: 'EQ-006', equipmentName: '测试台 TT-02', type: '定期保养', description: '季度校准与清洁', handler: '赵敏', startTime: '2026-09-12 08:00', endTime: '', durationHours: 0, cost: 0, parts: [], status: '处理中' },
    { id: 3, equipmentNo: 'EQ-002', equipmentName: '装配工作台 A2', type: '故障维修', description: '气动夹爪漏气', handler: '王强', startTime: '2026-09-08 14:00', endTime: '2026-09-08 16:30', durationHours: 2.5, cost: 320, parts: [{ name: '密封圈', qty: 2 }], status: '已完成' },
  ])

  /** 自增 id */
  const seq = ref(1000)

  /* ===================== Getters ===================== */

  /** 工单统计:按状态分类 */
  const orderStats = computed(() => {
    const stats = { 待排产: 0, 已排产: 0, 生产中: 0, 已完工: 0, 已取消: 0 }
    workOrders.value.forEach(o => { stats[o.status] = (stats[o.status] || 0) + 1 })
    return stats
  })

  /** 工单总计划数 / 已完工数 / 完成率 */
  const orderSummary = computed(() => {
    const total = workOrders.value.reduce((s, o) => s + o.quantity, 0)
    const completed = workOrders.value.reduce((s, o) => s + o.completedQty, 0)
    return {
      total,
      completed,
      completionRate: total > 0 ? Math.round((completed / total) * 1000) / 10 : 0,
    }
  })

  /** 工序统计:进行中 / 已延期 */
  const processStats = computed(() => ({
    total: processSchedules.value.length,
    inProgress: processSchedules.value.filter(p => p.status === '进行中').length,
    completed: processSchedules.value.filter(p => p.status === '已完成').length,
    delayed: processSchedules.value.filter(p => p.status === '已延期').length,
  }))

  /** 设备状态统计 */
  const equipmentStats = computed(() => {
    const stats = { 运行: 0, 空闲: 0, 停机: 0, 维修: 0, 保养: 0, 故障: 0 }
    equipments.value.forEach(e => { stats[e.status] = (stats[e.status] || 0) + 1 })
    return stats
  })

  /** 设备平均利用率 */
  const avgUtilization = computed(() => {
    const running = equipments.value.filter(e => e.status === '运行')
    if (running.length === 0) return 0
    return Math.round(running.reduce((s, e) => s + e.utilizationRate, 0) / running.length)
  })

  /** 待处理维修单数 */
  const pendingMaintenance = computed(() =>
    maintenanceRecords.value.filter(m => m.status === '处理中').length
  )

  /** 工单号 → 工单对象映射 */
  const orderMap = computed(() => {
    const map = {}
    workOrders.value.forEach(o => { map[o.id] = o })
    return map
  })

  /** 设备编号 → 设备对象映射 */
  const equipmentMap = computed(() => {
    const map = {}
    equipments.value.forEach(e => { map[e.equipmentNo] = e })
    return map
  })

  /** 产线列表(工单下拉 / 排程下拉用) */
  const lineOptions = computed(() =>
    Array.from(new Set([...workOrders.value.map(o => o.line), ...equipments.value.map(e => e.line)]))
      .filter(Boolean)
      .map(l => ({ label: l, value: l }))
  )

  /** 工单号下拉选项 */
  const orderOptions = computed(() =>
    workOrders.value
      .filter(o => o.status !== '已完工' && o.status !== '已取消')
      .map(o => ({ label: `${o.orderNo} ${o.productName}`, value: o.id, orderNo: o.orderNo, line: o.line }))
  )

  /** 设备下拉选项 */
  const equipmentOptions = computed(() =>
    equipments.value.map(e => ({ label: `${e.equipmentNo} ${e.name}`, value: e.equipmentNo, name: e.name }))
  )

  /* ===================== Actions:生产工单 ===================== */

  /** 新增工单 */
  function addWorkOrder(form) {
    workOrders.value.unshift({
      id: ++seq.value,
      orderNo: form.orderNo,
      productName: form.productName,
      productCode: form.productCode,
      spec: form.spec,
      quantity: Number(form.quantity),
      completedQty: 0,
      unit: form.unit,
      priority: form.priority,
      type: form.type,
      status: form.status || '待排产',
      line: form.line,
      planStart: form.planStart,
      planEnd: form.planEnd,
      actualStart: '',
      actualEnd: '',
      bom: [...form.bom],
      qualityStandard: form.qualityStandard,
      remark: form.remark,
    })
  }

  /** 更新工单 */
  function updateWorkOrder(form) {
    const target = workOrders.value.find(o => o.id === form.id)
    if (target) {
      Object.assign(target, {
        orderNo: form.orderNo,
        productName: form.productName,
        productCode: form.productCode,
        spec: form.spec,
        quantity: Number(form.quantity),
        unit: form.unit,
        priority: form.priority,
        type: form.type,
        status: form.status,
        line: form.line,
        planStart: form.planStart,
        planEnd: form.planEnd,
        actualStart: form.actualStart,
        actualEnd: form.actualEnd,
        bom: [...form.bom],
        qualityStandard: form.qualityStandard,
        remark: form.remark,
      })
    }
  }

  /** 删除工单 */
  function deleteWorkOrder(id) {
    const i = workOrders.value.findIndex(o => o.id === id)
    if (i !== -1) workOrders.value.splice(i, 1)
  }

  /** 更新工单完工数量(支持进度填写) */
  function updateCompletedQty(id, qty) {
    const target = workOrders.value.find(o => o.id === id)
    if (target) target.completedQty = Math.min(Number(qty), target.quantity)
  }

  /* ===================== Actions:工序排程 ===================== */

  /** 新增工序排程 */
  function addProcessSchedule(form) {
    const order = orderMap.value[form.workOrderId]
    processSchedules.value.unshift({
      id: ++seq.value,
      scheduleNo: form.scheduleNo,
      workOrderId: form.workOrderId,
      workOrderNo: order ? order.orderNo : '',
      line: form.line,
      processName: form.processName,
      processSeq: Number(form.processSeq),
      operator: form.operator,
      planStart: form.planStart,
      planEnd: form.planEnd,
      actualStart: form.actualStart || '',
      actualEnd: form.actualEnd || '',
      status: form.status || '未开始',
      durationHours: Number(form.durationHours),
      actualHours: Number(form.actualHours) || 0,
      resources: [...form.resources],
      remark: form.remark,
    })
    // 若工单还在"待排产",自动推进到"已排产"
    if (order && order.status === '待排产') order.status = '已排产'
  }

  /** 更新工序 */
  function updateProcessSchedule(form) {
    const target = processSchedules.value.find(p => p.id === form.id)
    if (target) {
      Object.assign(target, {
        line: form.line,
        processName: form.processName,
        processSeq: Number(form.processSeq),
        operator: form.operator,
        planStart: form.planStart,
        planEnd: form.planEnd,
        actualStart: form.actualStart,
        actualEnd: form.actualEnd,
        status: form.status,
        durationHours: Number(form.durationHours),
        actualHours: Number(form.actualHours) || 0,
        resources: [...form.resources],
        remark: form.remark,
      })
    }
  }

  /** 删除工序 */
  function deleteProcessSchedule(id) {
    const i = processSchedules.value.findIndex(p => p.id === id)
    if (i !== -1) processSchedules.value.splice(i, 1)
  }

  /* ===================== Actions:设备维修 ===================== */

  /** 新增维修/保养记录,并同步设备状态 */
  function addMaintenanceRecord(form) {
    const equipment = equipmentMap.value[form.equipmentNo]
    maintenanceRecords.value.unshift({
      id: ++seq.value,
      equipmentNo: form.equipmentNo,
      equipmentName: equipment ? equipment.name : '',
      type: form.type,
      description: form.description,
      handler: form.handler,
      startTime: form.startTime,
      endTime: '',
      durationHours: 0,
      cost: 0,
      parts: [...form.parts],
      status: '处理中',
    })
    // 同步设备状态
    if (equipment) {
      equipment.status = form.type === '故障维修' ? '维修' : '保养'
      equipment.currentTask = ''
      equipment.temperature = 0
      equipment.speed = 0
      equipment.utilizationRate = 0
    }
  }

  /** 完成维修记录,恢复设备状态为"空闲" */
  function completeMaintenanceRecord(id, form) {
    const record = maintenanceRecords.value.find(m => m.id === id)
    if (record) {
      record.endTime = form.endTime
      record.durationHours = Number(form.durationHours)
      record.cost = Number(form.cost)
      record.status = '已完成'
      // 恢复设备状态
      const equipment = equipmentMap.value[record.equipmentNo]
      if (equipment) {
        equipment.status = '空闲'
        equipment.lastMaintainDate = form.endTime.slice(0, 10)
      }
    }
  }

  return {
    // 常量
    orderStatusFlow,
    processStatusList,
    equipmentStatusList,
    // State
    workOrders,
    processSchedules,
    equipments,
    maintenanceRecords,
    // Getters
    orderStats,
    orderSummary,
    processStats,
    equipmentStats,
    avgUtilization,
    pendingMaintenance,
    orderMap,
    equipmentMap,
    lineOptions,
    orderOptions,
    equipmentOptions,
    // Actions
    addWorkOrder,
    updateWorkOrder,
    deleteWorkOrder,
    updateCompletedQty,
    addProcessSchedule,
    updateProcessSchedule,
    deleteProcessSchedule,
    addMaintenanceRecord,
    completeMaintenanceRecord,
  }
})

ProductionManagement.vue

javascript 复制代码
<!--
  生产管理模块(基于 Ant Design Vue + Pinia)
  三个子功能:生产工单(CRUD + BOM 物料清单 + 进度填写)、工序排程(甘特式时间线 + 复杂表单)、设备状态(实时卡片 + 维修记录)
-->
<template>
  <div class="prod-wrap">
  <a-tabs v-model:activeKey="activeTab">

    <!-- ==================== 生产工单 ==================== -->
    <a-tab-pane key="order" tab="生产工单">
      <!-- 顶部统计 -->
      <a-row :gutter="16" class="stat-row">
        <a-col :span="5"><a-card size="small"><a-statistic title="工单总数" :value="production.workOrders.length" /></a-card></a-col>
        <a-col :span="5"><a-card size="small"><a-statistic title="生产中" :value="production.orderStats['生产中']" :value-style="{ color: '#1677ff' }" /></a-card></a-col>
        <a-col :span="5"><a-card size="small"><a-statistic title="已完工" :value="production.orderStats['已完工']" :value-style="{ color: '#2eb85c' }" /></a-card></a-col>
        <a-col :span="5"><a-card size="small"><a-statistic title="待排产" :value="production.orderStats['待排产']" :value-style="{ color: '#e6a23c' }" /></a-card></a-col>
        <a-col :span="4" class="add-col">
          <a-button type="primary" block @click="openOrderModal()">
            <template #icon><PlusOutlined /></template>
            新建工单
          </a-button>
        </a-col>
      </a-row>

      <!-- 筛选栏 -->
      <a-card size="small" class="filter-card">
        <a-form layout="inline" :model="orderFilter">
          <a-form-item label="状态">
            <a-select v-model:value="orderFilter.status" :options="statusFilterOptions" allow-clear placeholder="全部" style="width: 120px" />
          </a-form-item>
          <a-form-item label="优先级">
            <a-select v-model:value="orderFilter.priority" :options="priorityOptions" allow-clear placeholder="全部" style="width: 120px" />
          </a-form-item>
          <a-form-item label="关键词">
            <a-input v-model:value="orderFilter.keyword" placeholder="工单号/产品名" allow-clear style="width: 180px" />
          </a-form-item>
        </a-form>
      </a-card>

      <!-- 工单表格 -->
      <a-table
        :columns="orderColumns"
        :data-source="filteredOrders"
        row-key="id"
        :pagination="{ pageSize: 4 }"
        :scroll="{ x: 1400 }"
      >
        <template #bodyCell="{ column, record }">
          <template v-if="column.key === 'priority'">
            <a-tag :color="priorityColor[record.priority]">{{ record.priority }}</a-tag>
          </template>
          <template v-else-if="column.key === 'status'">
            <a-tag :color="statusColor[record.status]">{{ record.status }}</a-tag>
          </template>
          <template v-else-if="column.key === 'progress'">
            <a-progress
              :percent="Math.round(record.completedQty / record.quantity * 100)"
              size="small"
              :status="record.status === '已完工' ? 'success' : (record.completedQty > 0 ? 'active' : 'normal')"
            />
            <span class="qty-text">{{ record.completedQty }}/{{ record.quantity }} {{ record.unit }}</span>
          </template>
          <template v-else-if="column.key === 'action'">
            <a-space>
              <a-button type="link" size="small" @click="openOrderDetail(record)">详情</a-button>
              <a-button type="link" size="small" @click="openOrderModal(record)">编辑</a-button>
              <a-button type="link" size="small" @click="openProgressModal(record)">报工</a-button>
              <a-popconfirm title="确定删除该工单吗?" @confirm="production.deleteWorkOrder(record.id)">
                <a-button type="link" size="small" danger>删除</a-button>
              </a-popconfirm>
            </a-space>
          </template>
        </template>
      </a-table>
    </a-tab-pane>

    <!-- ==================== 工序排程 ==================== -->
    <a-tab-pane key="process" tab="工序排程">
      <a-row :gutter="16" class="stat-row">
        <a-col :span="5"><a-card size="small"><a-statistic title="排程总数" :value="production.processStats.total" /></a-card></a-col>
        <a-col :span="5"><a-card size="small"><a-statistic title="进行中" :value="production.processStats.inProgress" :value-style="{ color: '#1677ff' }" /></a-card></a-col>
        <a-col :span="5"><a-card size="small"><a-statistic title="已完成" :value="production.processStats.completed" :value-style="{ color: '#2eb85c' }" /></a-card></a-col>
        <a-col :span="5"><a-card size="small"><a-statistic title="已延期" :value="production.processStats.delayed" :value-style="{ color: '#e74c3c' }" /></a-card></a-col>
        <a-col :span="4" class="add-col">
          <a-button type="primary" block @click="openProcessModal()">
            <template #icon><PlusOutlined /></template>
            新增工序
          </a-button>
        </a-col>
      </a-row>

      <!-- 排程甘特式时间线 -->
      <a-card size="small" title="排程时间线" class="timeline-card">
        <a-timeline mode="left" class="gantt-timeline">
          <a-timeline-item
            v-for="p in processSchedules"
            :key="p.id"
            :color="timelineColor[p.status]"
          >
            <template #label>{{ p.planStart.slice(0, 10) }}</template>
            <div class="tl-item">
              <div class="tl-head">
                <a-tag color="blue">{{ p.workOrderNo }}</a-tag>
                <strong>{{ p.processName }}</strong>
                <a-tag :color="statusColor[p.status]">{{ p.status }}</a-tag>
              </div>
              <div class="tl-meta">
                产线:{{ p.line }} | 操作:{{ p.operator }} | 计划:{{ p.planStart.slice(5) }} ~ {{ p.planEnd.slice(5) }}
                | 工时:{{ p.actualHours }}/{{ p.durationHours }}h
              </div>
              <div class="tl-progress">
                <a-progress
                  :percent="p.durationHours > 0 ? Math.round(p.actualHours / p.durationHours * 100) : 0"
                  size="small"
                />
              </div>
              <div class="tl-resources" v-if="p.resources.length">
                <a-tag v-for="r in p.resources" :key="r.name" class="res-tag">
                  <template #icon><ToolOutlined /></template>
                  {{ r.name }}
                </a-tag>
              </div>
            </div>
          </a-timeline-item>
        </a-timeline>
      </a-card>

      <!-- 工序表格 -->
      <a-table
        :columns="processColumns"
        :data-source="processSchedules"
        row-key="id"
        :pagination="{ pageSize: 5 }"
        :scroll="{ x: 1300 }"
      >
        <template #bodyCell="{ column, record }">
          <template v-if="column.key === 'status'">
            <a-tag :color="statusColor[record.status]">{{ record.status }}</a-tag>
          </template>
          <template v-else-if="column.key === 'action'">
            <a-space>
              <a-button type="link" size="small" @click="openProcessModal(record)">编辑</a-button>
              <a-popconfirm title="删除该工序排程?" @confirm="production.deleteProcessSchedule(record.id)">
                <a-button type="link" size="small" danger>删除</a-button>
              </a-popconfirm>
            </a-space>
          </template>
        </template>
      </a-table>
    </a-tab-pane>

    <!-- ==================== 设备状态 ==================== -->
    <a-tab-pane key="equipment" tab="设备状态">
      <a-row :gutter="16" class="stat-row">
        <a-col :span="5"><a-card size="small"><a-statistic title="设备总数" :value="production.equipments.length" /></a-card></a-col>
        <a-col :span="5"><a-card size="small"><a-statistic title="运行中" :value="production.equipmentStats['运行']" :value-style="{ color: '#2eb85c' }" /></a-card></a-col>
        <a-col :span="5"><a-card size="small"><a-statistic title="维修/保养" :value="(production.equipmentStats['维修']||0) + (production.equipmentStats['保养']||0)" :value-style="{ color: '#e6a23c' }" /></a-card></a-col>
        <a-col :span="5"><a-card size="small"><a-statistic title="平均利用率" :value="production.avgUtilization" suffix="%" :value-style="{ color: '#1677ff' }" /></a-card></a-col>
        <a-col :span="4" class="add-col">
          <a-button type="primary" block @click="openMaintenanceModal()">
            <template #icon><ToolOutlined /></template>
            报修/保养
          </a-button>
        </a-col>
      </a-row>

      <!-- 设备卡片网格 -->
      <a-row :gutter="[16, 16]">
        <a-col :xs="24" :sm="12" :lg="8" v-for="eq in equipments" :key="eq.id">
          <a-card size="small" class="eq-card">
            <div class="eq-head">
              <div>
                <div class="eq-name">{{ eq.name }}</div>
                <div class="eq-no">{{ eq.equipmentNo }} · {{ eq.model }}</div>
              </div>
              <a-tag :color="eqStatusColor[eq.status]" class="eq-status">{{ eq.status }}</a-tag>
            </div>
            <a-row :gutter="8" class="eq-metrics">
              <a-col :span="8"><div class="metric">{{ eq.temperature }}<span class="unit">℃</span></div><div class="metric-label">温度</div></a-col>
              <a-col :span="8"><div class="metric">{{ eq.speed }}<span class="unit">{{ eq.type==='测试设备'?'':'rpm' }}</span></div><div class="metric-label">转速</div></a-col>
              <a-col :span="8"><div class="metric">{{ eq.runHours }}<span class="unit">h</span></div><div class="metric-label">累计运行</div></a-col>
            </a-row>
            <a-progress
              :percent="eq.utilizationRate"
              size="small"
              :status="eq.status === '运行' ? 'active' : 'normal'"
            />
            <div class="eq-foot">
              <span>产线:{{ eq.line }}</span>
              <span v-if="eq.currentTask">当前:{{ eq.currentTask }}</span>
              <span>下次保养:{{ eq.nextMaintainDate }}</span>
            </div>
          </a-card>
        </a-col>
      </a-row>

      <!-- 维修记录 -->
      <a-card size="small" title="维修 / 保养记录" style="margin-top: 1rem">
        <a-table
          :columns="maintenanceColumns"
          :data-source="maintenanceRecords"
          row-key="id"
          :pagination="{ pageSize: 5 }"
        >
          <template #bodyCell="{ column, record }">
            <template v-if="column.key === 'type'">
              <a-tag :color="record.type === '故障维修' ? 'red' : 'blue'">{{ record.type }}</a-tag>
            </template>
            <template v-else-if="column.key === 'status'">
              <a-tag :color="record.status === '已完成' ? 'green' : 'orange'">{{ record.status }}</a-tag>
            </template>
            <template v-else-if="column.key === 'parts'">
              <a-tag v-for="p in record.parts" :key="p.name" class="res-tag">{{ p.name }} ×{{ p.qty }}</a-tag>
              <span v-if="!record.parts.length" class="muted">---</span>
            </template>
            <template v-else-if="column.key === 'action'">
              <a-popconfirm
                v-if="record.status === '处理中'"
                title="确认完成维修?"
                @confirm="openCompleteModal(record)"
              >
                <a-button type="link" size="small">完成</a-button>
              </a-popconfirm>
              <span v-else class="muted">---</span>
            </template>
          </template>
        </a-table>
      </a-card>
    </a-tab-pane>
  </a-tabs>
  </div>

  <!-- ============ 工单 新增/编辑 弹窗(含动态 BOM 行) ============ -->
  <a-modal
    v-model:open="orderModalOpen"
    :title="orderModalTitle"
    :width="720"
    @ok="submitOrder"
    @cancel="orderModalOpen = false"
  >
    <a-form ref="orderFormRef" :model="orderForm" :rules="orderRules" layout="vertical">
      <a-row :gutter="12">
        <a-col :span="8">
          <a-form-item label="工单号" name="orderNo">
            <a-input v-model:value="orderForm.orderNo" placeholder="如 WO-2026-0910" />
          </a-form-item>
        </a-col>
        <a-col :span="10">
          <a-form-item label="产品名称" name="productName">
            <a-input v-model:value="orderForm.productName" placeholder="产品名称" />
          </a-form-item>
        </a-col>
        <a-col :span="6">
          <a-form-item label="产品编码" name="productCode">
            <a-input v-model:value="orderForm.productCode" />
          </a-form-item>
        </a-col>
      </a-row>
      <a-row :gutter="12">
        <a-col :span="8">
          <a-form-item label="规格型号" name="spec">
            <a-input v-model:value="orderForm.spec" />
          </a-form-item>
        </a-col>
        <a-col :span="6">
          <a-form-item label="计划数量" name="quantity">
            <a-input-number v-model:value="orderForm.quantity" :min="1" style="width: 100%" />
          </a-form-item>
        </a-col>
        <a-col :span="4">
          <a-form-item label="单位" name="unit">
            <a-input v-model:value="orderForm.unit" />
          </a-form-item>
        </a-col>
        <a-col :span="6">
          <a-form-item label="类型" name="type">
            <a-select v-model:value="orderForm.type" :options="orderTypeOptions" />
          </a-form-item>
        </a-col>
      </a-row>
      <a-row :gutter="12">
        <a-col :span="8">
          <a-form-item label="优先级" name="priority">
            <a-select v-model:value="orderForm.priority" :options="priorityOptions" />
          </a-form-item>
        </a-col>
        <a-col :span="8">
          <a-form-item label="状态" name="status">
            <a-select v-model:value="orderForm.status" :options="orderStatusOptions" />
          </a-form-item>
        </a-col>
        <a-col :span="8">
          <a-form-item label="产线" name="line">
            <a-select v-model:value="orderForm.line" :options="production.lineOptions" allow-clear />
          </a-form-item>
        </a-col>
      </a-row>
      <a-row :gutter="12">
        <a-col :span="12">
          <a-form-item label="计划开始" name="planStart">
            <a-date-picker v-model:value="orderForm.planStart" value-format="YYYY-MM-DD" style="width: 100%" />
          </a-form-item>
        </a-col>
        <a-col :span="12">
          <a-form-item label="计划结束" name="planEnd">
            <a-date-picker v-model:value="orderForm.planEnd" value-format="YYYY-MM-DD" style="width: 100%" />
          </a-form-item>
        </a-col>
      </a-row>

      <!-- BOM 物料清单(动态增减行) -->
      <a-divider orientation="left">BOM 物料清单</a-divider>
      <div v-for="(item, idx) in orderForm.bom" :key="idx" class="bom-row">
        <a-row :gutter="8">
          <a-col :span="8"><a-input v-model:value="item.name" placeholder="物料名称" /></a-col>
          <a-col :span="5"><a-input v-model:value="item.code" placeholder="物料编码" /></a-col>
          <a-col :span="4"><a-input-number v-model:value="item.qty" :min="0" :step="0.1" style="width: 100%" /></a-col>
          <a-col :span="4"><a-input v-model:value="item.unit" placeholder="单位" /></a-col>
          <a-col :span="3">
            <a-button type="link" danger @click="removeBomRow(idx)">删除</a-button>
          </a-col>
        </a-row>
      </div>
      <a-button type="dashed" block @click="addBomRow" style="margin-top: 8px">
        <template #icon><PlusOutlined /></template>
        添加物料
      </a-button>

      <a-form-item label="质量标准" name="qualityStandard" style="margin-top: 16px">
        <a-textarea v-model:value="orderForm.qualityStandard" :rows="2" placeholder="如:噪音 ≤ 65dB,扭矩偏差 ≤ 5%" />
      </a-form-item>
      <a-form-item label="备注">
        <a-input v-model:value="orderForm.remark" placeholder="可选" />
      </a-form-item>
    </a-form>
  </a-modal>

  <!-- ============ 工单详情抽屉 ============ -->
  <a-drawer v-model:open="orderDetailOpen" title="工单详情" :width="520">
    <template v-if="currentOrder">
      <a-descriptions :column="2" bordered size="small">
        <a-descriptions-item label="工单号">{{ currentOrder.orderNo }}</a-descriptions-item>
        <a-descriptions-item label="状态"><a-tag :color="statusColor[currentOrder.status]">{{ currentOrder.status }}</a-tag></a-descriptions-item>
        <a-descriptions-item label="产品名称">{{ currentOrder.productName }}</a-descriptions-item>
        <a-descriptions-item label="产品编码">{{ currentOrder.productCode }}</a-descriptions-item>
        <a-descriptions-item label="规格">{{ currentOrder.spec }}</a-descriptions-item>
        <a-descriptions-item label="数量">{{ currentOrder.quantity }} {{ currentOrder.unit }}</a-descriptions-item>
        <a-descriptions-item label="优先级"><a-tag :color="priorityColor[currentOrder.priority]">{{ currentOrder.priority }}</a-tag></a-descriptions-item>
        <a-descriptions-item label="类型">{{ currentOrder.type }}</a-descriptions-item>
        <a-descriptions-item label="产线">{{ currentOrder.line || '---' }}</a-descriptions-item>
        <a-descriptions-item label="计划周期">{{ currentOrder.planStart }} ~ {{ currentOrder.planEnd }}</a-descriptions-item>
      </a-descriptions>

      <a-divider orientation="left">生产进度</a-divider>
      <a-progress
        :percent="Math.round(currentOrder.completedQty / currentOrder.quantity * 100)"
        :status="currentOrder.status === '已完工' ? 'success' : 'active'"
      />
      <p class="progress-text">已完成 {{ currentOrder.completedQty }} / 计划 {{ currentOrder.quantity }} {{ currentOrder.unit }}</p>

      <a-divider orientation="left">BOM 物料清单</a-divider>
      <a-table
        :data-source="currentOrder.bom"
        :pagination="false"
        size="small"
        :columns="bomColumns"
      />

      <a-divider orientation="left">质量标准</a-divider>
      <p>{{ currentOrder.qualityStandard || '---' }}</p>
      <a-divider orientation="left">备注</a-divider>
      <p>{{ currentOrder.remark || '---' }}</p>
    </template>
  </a-drawer>

  <!-- ============ 报工弹窗 ============ -->
  <a-modal v-model:open="progressModalOpen" title="生产报工" @ok="submitProgress" @cancel="progressModalOpen = false">
    <a-form layout="vertical">
      <a-form-item label="当前工单">
        <a-input :value="currentOrder?.orderNo" disabled />
      </a-form-item>
      <a-form-item label="已完成数量">
        <a-input-number
          v-model:value="progressForm.completedQty"
          :min="0"
          :max="currentOrder?.quantity"
          style="width: 100%"
        />
        <div class="muted">计划数量:{{ currentOrder?.quantity }} {{ currentOrder?.unit }}</div>
      </a-form-item>
      <a-form-item label="是否完工">
        <a-switch v-model:checked="progressForm.finished" />
        <span class="muted" style="margin-left: 8px">开启则工单状态自动变为「已完工」</span>
      </a-form-item>
    </a-form>
  </a-modal>

  <!-- ============ 工序 新增/编辑 弹窗 ============ -->
  <a-modal
    v-model:open="processModalOpen"
    :title="processModalTitle"
    :width="680"
    @ok="submitProcess"
    @cancel="processModalOpen = false"
  >
    <a-form ref="processFormRef" :model="processForm" :rules="processRules" layout="vertical">
      <a-row :gutter="12">
        <a-col :span="10">
          <a-form-item label="关联工单" name="workOrderId">
            <a-select v-model:value="processForm.workOrderId" :options="production.orderOptions" @change="onOrderChange" />
          </a-form-item>
        </a-col>
        <a-col :span="6">
          <a-form-item label="工序序号" name="processSeq">
            <a-input-number v-model:value="processForm.processSeq" :min="1" style="width: 100%" />
          </a-form-item>
        </a-col>
        <a-col :span="8">
          <a-form-item label="工序名称" name="processName">
            <a-input v-model:value="processForm.processName" placeholder="如:齿轮装配" />
          </a-form-item>
        </a-col>
      </a-row>
      <a-row :gutter="12">
        <a-col :span="12">
          <a-form-item label="产线" name="line">
            <a-select v-model:value="processForm.line" :options="production.lineOptions" />
          </a-form-item>
        </a-col>
        <a-col :span="12">
          <a-form-item label="操作工" name="operator">
            <a-input v-model:value="processForm.operator" />
          </a-form-item>
        </a-col>
      </a-row>
      <a-row :gutter="12">
        <a-col :span="12">
          <a-form-item label="计划开始" name="planStart">
            <a-date-picker v-model:value="processForm.planStart" show-time value-format="YYYY-MM-DD HH:mm" style="width: 100%" />
          </a-form-item>
        </a-col>
        <a-col :span="12">
          <a-form-item label="计划结束" name="planEnd">
            <a-date-picker v-model:value="processForm.planEnd" show-time value-format="YYYY-MM-DD HH:mm" style="width: 100%" />
          </a-form-item>
        </a-col>
      </a-row>
      <a-row :gutter="12">
        <a-col :span="12">
          <a-form-item label="实际开始">
            <a-date-picker v-model:value="processForm.actualStart" show-time value-format="YYYY-MM-DD HH:mm" style="width: 100%" />
          </a-form-item>
        </a-col>
        <a-col :span="12">
          <a-form-item label="实际结束">
            <a-date-picker v-model:value="processForm.actualEnd" show-time value-format="YYYY-MM-DD HH:mm" style="width: 100%" />
          </a-form-item>
        </a-col>
      </a-row>
      <a-row :gutter="12">
        <a-col :span="8">
          <a-form-item label="计划工时(h)" name="durationHours">
            <a-input-number v-model:value="processForm.durationHours" :min="0" style="width: 100%" />
          </a-form-item>
        </a-col>
        <a-col :span="8">
          <a-form-item label="实际工时(h)">
            <a-input-number v-model:value="processForm.actualHours" :min="0" style="width: 100%" />
          </a-form-item>
        </a-col>
        <a-col :span="8">
          <a-form-item label="状态" name="status">
            <a-select v-model:value="processForm.status" :options="processStatusOptions" />
          </a-form-item>
        </a-col>
      </a-row>

      <!-- 资源配置(动态行) -->
      <a-divider orientation="left">资源配置</a-divider>
      <div v-for="(r, idx) in processForm.resources" :key="idx" class="bom-row">
        <a-row :gutter="8">
          <a-col :span="10"><a-input v-model:value="r.name" placeholder="资源名称" /></a-col>
          <a-col :span="8">
            <a-select v-model:value="r.type" :options="resourceTypeOptions" />
          </a-col>
          <a-col :span="6">
            <a-button type="link" danger @click="removeResource(idx)">删除</a-button>
          </a-col>
        </a-row>
      </div>
      <a-button type="dashed" block @click="addResource" style="margin-top: 8px">
        <template #icon><PlusOutlined /></template>
        添加资源
      </a-button>

      <a-form-item label="备注" style="margin-top: 16px">
        <a-textarea v-model:value="processForm.remark" :rows="2" />
      </a-form-item>
    </a-form>
  </a-modal>

  <!-- ============ 报修/保养 弹窗 ============ -->
  <a-modal
    v-model:open="maintenanceModalOpen"
    title="设备报修 / 保养登记"
    @ok="submitMaintenance"
    @cancel="maintenanceModalOpen = false"
  >
    <a-form ref="maintenanceFormRef" :model="maintenanceForm" :rules="maintenanceRules" layout="vertical">
      <a-form-item label="设备" name="equipmentNo">
        <a-select v-model:value="maintenanceForm.equipmentNo" :options="production.equipmentOptions" show-search option-filter-prop="label" />
      </a-form-item>
      <a-row :gutter="12">
        <a-col :span="12">
          <a-form-item label="类型" name="type">
            <a-select v-model:value="maintenanceForm.type" :options="maintenanceTypeOptions" />
          </a-form-item>
        </a-col>
        <a-col :span="12">
          <a-form-item label="处理人" name="handler">
            <a-input v-model:value="maintenanceForm.handler" />
          </a-form-item>
        </a-col>
      </a-row>
      <a-form-item label="开始时间" name="startTime">
        <a-date-picker v-model:value="maintenanceForm.startTime" show-time value-format="YYYY-MM-DD HH:mm" style="width: 100%" />
      </a-form-item>
      <a-form-item label="故障/保养描述" name="description">
        <a-textarea v-model:value="maintenanceForm.description" :rows="3" placeholder="详细描述故障现象或保养内容" />
      </a-form-item>

      <!-- 更换配件(动态行) -->
      <a-divider orientation="left">更换配件</a-divider>
      <div v-for="(p, idx) in maintenanceForm.parts" :key="idx" class="bom-row">
        <a-row :gutter="8">
          <a-col :span="14"><a-input v-model:value="p.name" placeholder="配件名称" /></a-col>
          <a-col :span="6"><a-input-number v-model:value="p.qty" :min="1" style="width: 100%" /></a-col>
          <a-col :span="4"><a-button type="link" danger @click="removePart(idx)">删除</a-button></a-col>
        </a-row>
      </div>
      <a-button type="dashed" block @click="addPart" style="margin-top: 8px">
        <template #icon><PlusOutlined /></template>
        添加配件
      </a-button>
    </a-form>
  </a-modal>

  <!-- ============ 完成维修 弹窗 ============ -->
  <a-modal
    v-model:open="completeModalOpen"
    title="完成维修"
    @ok="submitComplete"
    @cancel="completeModalOpen = false"
  >
    <a-form layout="vertical">
      <a-form-item label="完成时间" name="endTime">
        <a-date-picker v-model:value="completeForm.endTime" show-time value-format="YYYY-MM-DD HH:mm" style="width: 100%" />
      </a-form-item>
      <a-row :gutter="12">
        <a-col :span="12">
          <a-form-item label="维修耗时(h)">
            <a-input-number v-model:value="completeForm.durationHours" :min="0" style="width: 100%" />
          </a-form-item>
        </a-col>
        <a-col :span="12">
          <a-form-item label="费用(元)">
            <a-input-number v-model:value="completeForm.cost" :min="0" style="width: 100%" />
          </a-form-item>
        </a-col>
      </a-row>
    </a-form>
  </a-modal>
</template>

<script setup>
import { ref, reactive, computed } from 'vue'
import { storeToRefs } from 'pinia'
import { message } from 'ant-design-vue'
import { PlusOutlined, ToolOutlined } from '@ant-design/icons-vue'
import dayjs from 'dayjs'
import { useProductionStore } from '../stores/production'

const production = useProductionStore()
const { workOrders, processSchedules, equipments, maintenanceRecords } = storeToRefs(production)

/* 当前 Tab */
const activeTab = ref('order')

/* ===================== 颜色映射 ===================== */
const priorityColor = { '紧急': 'red', '高': 'orange', '中': 'blue', '低': 'default' }
const statusColor = {
  '待排产': 'default', '已排产': 'cyan', '生产中': 'blue', '已完工': 'green', '已取消': 'red',
  '未开始': 'default', '进行中': 'blue', '已完成': 'green', '已延期': 'red',
}
const eqStatusColor = {
  '运行': 'green', '空闲': 'default', '停机': 'orange', '维修': 'red', '保养': 'blue', '故障': 'red',
}
const timelineColor = {
  '未开始': 'gray', '进行中': 'blue', '已完成': 'green', '已延期': 'red', '已取消': 'gray',
}

/* ===================== 选项常量 ===================== */
const priorityOptions = [
  { label: '紧急', value: '紧急' }, { label: '高', value: '高' },
  { label: '中', value: '中' }, { label: '低', value: '低' },
]
const orderTypeOptions = [
  { label: '新产品', value: '新产品' }, { label: '常规', value: '常规' },
  { label: '返修', value: '返修' }, { label: '来料加工', value: '来料加工' },
]
const orderStatusOptions = production.orderStatusFlow.map(s => ({ label: s, value: s }))
const statusFilterOptions = orderStatusOptions
const processStatusOptions = production.processStatusList.map(s => ({ label: s, value: s }))
const resourceTypeOptions = [
  { label: '设备', value: '设备' }, { label: '工具', value: '工具' },
  { label: '仪器', value: '仪器' }, { label: '工装', value: '工装' },
]
const maintenanceTypeOptions = [
  { label: '故障维修', value: '故障维修' }, { label: '定期保养', value: '定期保养' }, { label: '点检', value: '点检' },
]

/* ===================== 表格列 ===================== */
const orderColumns = [
  { title: '工单号', dataIndex: 'orderNo', key: 'orderNo', fixed: 'left', width: 130 },
  { title: '产品名称', dataIndex: 'productName', key: 'productName', width: 140 },
  { title: '规格', dataIndex: 'spec', key: 'spec', width: 110 },
  { title: '数量', key: 'progress', width: 180 },
  { title: '优先级', key: 'priority', width: 70 },
  { title: '类型', dataIndex: 'type', key: 'type', width: 80 },
  { title: '状态', key: 'status', width: 80 },
  { title: '产线', dataIndex: 'line', key: 'line', width: 110 },
  { title: '计划周期', key: 'plan', width: 180, customRender: ({ record }) => `${record.planStart} ~ ${record.planEnd}` },
  { title: '操作', key: 'action', fixed: 'right', width: 200 },
]
const processColumns = [
  { title: '排程号', dataIndex: 'scheduleNo', key: 'scheduleNo', width: 150 },
  { title: '关联工单', dataIndex: 'workOrderNo', key: 'workOrderNo', width: 130 },
  { title: '工序', key: 'process', width: 130, customRender: ({ record }) => `#${record.processSeq} ${record.processName}` },
  { title: '产线', dataIndex: 'line', key: 'line', width: 110 },
  { title: '操作工', dataIndex: 'operator', key: 'operator', width: 80 },
  { title: '计划区间', key: 'planRange', width: 200, customRender: ({ record }) => `${record.planStart.slice(5)} ~ ${record.planEnd.slice(5)}` },
  { title: '工时(h)', key: 'hours', width: 90, customRender: ({ record }) => `${record.actualHours}/${record.durationHours}` },
  { title: '状态', key: 'status', width: 80 },
  { title: '操作', key: 'action', width: 110 },
]
const maintenanceColumns = [
  { title: '设备', dataIndex: 'equipmentName', key: 'equipmentName', width: 180 },
  { title: '类型', key: 'type', width: 90 },
  { title: '描述', dataIndex: 'description', key: 'description' },
  { title: '处理人', dataIndex: 'handler', key: 'handler', width: 80 },
  { title: '开始时间', dataIndex: 'startTime', key: 'startTime', width: 140 },
  { title: '耗时(h)', dataIndex: 'durationHours', key: 'durationHours', width: 80 },
  { title: '费用', dataIndex: 'cost', key: 'cost', width: 80 },
  { title: '更换配件', key: 'parts', width: 160 },
  { title: '状态', key: 'status', width: 80 },
  { title: '操作', key: 'action', width: 70 },
]
const bomColumns = [
  { title: '物料名称', dataIndex: 'name', key: 'name' },
  { title: '编码', dataIndex: 'code', key: 'code' },
  { title: '用量', dataIndex: 'qty', key: 'qty', width: 80 },
  { title: '单位', dataIndex: 'unit', key: 'unit', width: 70 },
]

/* ===================== 工单筛选 ===================== */
const orderFilter = reactive({ status: undefined, priority: undefined, keyword: '' })
const filteredOrders = computed(() =>
  workOrders.value.filter(o =>
    (!orderFilter.status || o.status === orderFilter.status) &&
    (!orderFilter.priority || o.priority === orderFilter.priority) &&
    (!orderFilter.keyword || o.orderNo.includes(orderFilter.keyword) || o.productName.includes(orderFilter.keyword))
  )
)

/* ===================== 工单 新增/编辑 ===================== */
const orderModalOpen = ref(false)
const orderFormRef = ref(null)
const editingOrderId = ref(null)
const orderModalTitle = computed(() => editingOrderId.value === null ? '新建工单' : '编辑工单')

function emptyOrderForm() {
  return {
    orderNo: '', productName: '', productCode: '', spec: '',
    quantity: 1, unit: '件', priority: '中', type: '常规', status: '待排产',
    line: undefined, planStart: '', planEnd: '',
    bom: [{ name: '', code: '', qty: 1, unit: '' }],
    qualityStandard: '', remark: '',
  }
}
const orderForm = reactive(emptyOrderForm())
const orderRules = {
  orderNo: [{ required: true, message: '请输入工单号' }],
  productName: [{ required: true, message: '请输入产品名称' }],
  productCode: [{ required: true, message: '请输入产品编码' }],
  quantity: [{ required: true, message: '请输入数量' }],
  unit: [{ required: true, message: '请输入单位' }],
  type: [{ required: true, message: '请选择类型' }],
  priority: [{ required: true, message: '请选择优先级' }],
  status: [{ required: true, message: '请选择状态' }],
  planStart: [{ required: true, message: '请选择计划开始' }],
  planEnd: [{ required: true, message: '请选择计划结束' }],
}

function openOrderModal(record) {
  if (record) {
    editingOrderId.value = record.id
    Object.assign(orderForm, {
      orderNo: record.orderNo, productName: record.productName, productCode: record.productCode,
      spec: record.spec, quantity: record.quantity, unit: record.unit, priority: record.priority,
      type: record.type, status: record.status, line: record.line,
      planStart: record.planStart, planEnd: record.planEnd,
      bom: record.bom.map(b => ({ ...b })),
      qualityStandard: record.qualityStandard, remark: record.remark,
    })
  } else {
    editingOrderId.value = null
    Object.assign(orderForm, emptyOrderForm())
  }
  orderModalOpen.value = true
}

async function submitOrder() {
  await orderFormRef.value.validate()
  if (editingOrderId.value === null) {
    production.addWorkOrder({ ...orderForm })
    message.success('工单创建成功')
  } else {
    const original = production.orderMap[editingOrderId.value]
    production.updateWorkOrder({
      id: editingOrderId.value,
      ...orderForm,
      actualStart: original.actualStart,
      actualEnd: original.actualEnd,
      completedQty: original.completedQty,
    })
    message.success('工单已更新')
  }
  orderModalOpen.value = false
}

/* BOM 行操作 */
function addBomRow() { orderForm.bom.push({ name: '', code: '', qty: 1, unit: '' }) }
function removeBomRow(idx) { orderForm.bom.splice(idx, 1) }

/* ===================== 工单详情 / 报工 ===================== */
const orderDetailOpen = ref(false)
const currentOrder = ref(null)
function openOrderDetail(record) {
  currentOrder.value = record
  orderDetailOpen.value = true
}

const progressModalOpen = ref(false)
const progressForm = reactive({ completedQty: 0, finished: false })
function openProgressModal(record) {
  currentOrder.value = record
  progressForm.completedQty = record.completedQty
  progressForm.finished = record.status === '已完工'
  progressModalOpen.value = true
}
function submitProgress() {
  production.updateCompletedQty(currentOrder.value.id, progressForm.completedQty)
  if (progressForm.finished) {
    const order = production.orderMap[currentOrder.value.id]
    order.status = '已完工'
    order.actualEnd = dayjs().format('YYYY-MM-DD')
    order.completedQty = order.quantity
  }
  message.success('报工成功')
  progressModalOpen.value = false
}

/* ===================== 工序 新增/编辑 ===================== */
const processModalOpen = ref(false)
const processFormRef = ref(null)
const editingProcessId = ref(null)
const processModalTitle = computed(() => editingProcessId.value === null ? '新增工序排程' : '编辑工序排程')

function emptyProcessForm() {
  return {
    workOrderId: undefined, processSeq: 1, processName: '', line: undefined, operator: '',
    planStart: '', planEnd: '', actualStart: '', actualEnd: '',
    status: '未开始', durationHours: 8, actualHours: 0,
    resources: [{ name: '', type: '设备' }], remark: '',
  }
}
const processForm = reactive(emptyProcessForm())
const processRules = {
  workOrderId: [{ required: true, message: '请选择关联工单' }],
  processSeq: [{ required: true, message: '请输入工序序号' }],
  processName: [{ required: true, message: '请输入工序名称' }],
  line: [{ required: true, message: '请选择产线' }],
  operator: [{ required: true, message: '请输入操作工' }],
  planStart: [{ required: true, message: '请选择计划开始' }],
  planEnd: [{ required: true, message: '请选择计划结束' }],
  durationHours: [{ required: true, message: '请输入计划工时' }],
  status: [{ required: true, message: '请选择状态' }],
}

function openProcessModal(record) {
  if (record) {
    editingProcessId.value = record.id
    Object.assign(processForm, {
      workOrderId: record.workOrderId, processSeq: record.processSeq, processName: record.processName,
      line: record.line, operator: record.operator,
      planStart: record.planStart, planEnd: record.planEnd,
      actualStart: record.actualStart, actualEnd: record.actualEnd,
      status: record.status, durationHours: record.durationHours, actualHours: record.actualHours,
      resources: record.resources.map(r => ({ ...r })), remark: record.remark,
    })
  } else {
    editingProcessId.value = null
    Object.assign(processForm, emptyProcessForm())
  }
  processModalOpen.value = true
}

/* 选择工单时自动带出产线 */
function onOrderChange(orderId) {
  const opt = production.orderOptions.find(o => o.value === orderId)
  if (opt && !processForm.line) processForm.line = opt.line
}

async function submitProcess() {
  await processFormRef.value.validate()
  // 自动生成排程号
  const scheduleNo = `PS-${dayjs().format('YYYYMMDD')}-${String(Math.floor(Math.random() * 900) + 100)}`
  if (editingProcessId.value === null) {
    production.addProcessSchedule({ scheduleNo, ...processForm })
    message.success('工序排程已创建')
  } else {
    production.updateProcessSchedule({ id: editingProcessId.value, ...processForm })
    message.success('工序排程已更新')
  }
  processModalOpen.value = false
}

/* 资源行操作 */
function addResource() { processForm.resources.push({ name: '', type: '设备' }) }
function removeResource(idx) { processForm.resources.splice(idx, 1) }

/* ===================== 报修/保养 ===================== */
const maintenanceModalOpen = ref(false)
const maintenanceFormRef = ref(null)
function emptyMaintenanceForm() {
  return { equipmentNo: undefined, type: '故障维修', handler: '', startTime: '', description: '', parts: [] }
}
const maintenanceForm = reactive(emptyMaintenanceForm())
const maintenanceRules = {
  equipmentNo: [{ required: true, message: '请选择设备' }],
  type: [{ required: true, message: '请选择类型' }],
  handler: [{ required: true, message: '请输入处理人' }],
  startTime: [{ required: true, message: '请选择开始时间' }],
  description: [{ required: true, message: '请填写描述' }],
}

function openMaintenanceModal() {
  Object.assign(maintenanceForm, emptyMaintenanceForm())
  maintenanceModalOpen.value = true
}

async function submitMaintenance() {
  await maintenanceFormRef.value.validate()
  production.addMaintenanceRecord({ ...maintenanceForm })
  message.success('维修/保养登记成功,设备状态已同步更新')
  maintenanceModalOpen.value = false
}

/* 配件行操作 */
function addPart() { maintenanceForm.parts.push({ name: '', qty: 1 }) }
function removePart(idx) { maintenanceForm.parts.splice(idx, 1) }

/* ===================== 完成维修 ===================== */
const completeModalOpen = ref(false)
const completingRecord = ref(null)
const completeForm = reactive({ endTime: '', durationHours: 0, cost: 0 })

function openCompleteModal(record) {
  completingRecord.value = record
  completeForm.endTime = dayjs().format('YYYY-MM-DD HH:mm')
  completeForm.durationHours = 1
  completeForm.cost = 0
  completeModalOpen.value = true
}

function submitComplete() {
  production.completeMaintenanceRecord(completingRecord.value.id, { ...completeForm })
  message.success('维修已完成,设备已恢复空闲状态')
  completeModalOpen.value = false
}
</script>

<style scoped>
/* 外层包裹:占满父容器剩余高度,自身不滚动 */
.prod-wrap {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* antd Tabs 撑满包裹层:Tab 头固定,内容区独立滚动,外层不出滚动条 */
.prod-wrap :deep(.ant-tabs) {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.prod-wrap :deep(.ant-tabs-nav) {
  margin-bottom: 12px;
  flex-shrink: 0;
}

.prod-wrap :deep(.ant-tabs-content-holder) {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  padding-right: 4px;
  /* Firefox:细滚动条 */
  scrollbar-width: thin;
  scrollbar-color: rgba(128, 128, 128, 0.35) transparent;
}

/* ------ 长椭圆形(胶囊形)滚动条:WebKit 内核(Chrome/Edge/Safari)------ */
.prod-wrap :deep(.ant-tabs-content-holder::-webkit-scrollbar) {
  width: 8px;
  height: 8px;
}

/* 轨道:透明,视觉上只留滑块 */
.prod-wrap :deep(.ant-tabs-content-holder::-webkit-scrollbar-track) {
  background: transparent;
}

/* 滑块:两端完全圆角 => 长椭圆胶囊形 */
.prod-wrap :deep(.ant-tabs-content-holder::-webkit-scrollbar-thumb) {
  background: rgba(128, 128, 128, 0.35);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: padding-box;
  transition: background 0.2s;
}

.prod-wrap :deep(.ant-tabs-content-holder::-webkit-scrollbar-thumb:hover) {
  background: rgba(100, 108, 255, 0.6);
  background-clip: padding-box;
}

/* 角落(横竖滚动条交汇处)透明 */
.prod-wrap :deep(.ant-tabs-content-holder::-webkit-scrollbar-corner) {
  background: transparent;
}

.prod-wrap :deep(.ant-tabs-content),
.prod-wrap :deep(.ant-tabs-tabpane) {
  height: 100%;
}

/* 统计行紧凑排列,不随内容滚动 */
.stat-row { margin-bottom: 0.8rem; flex-shrink: 0; }
.add-col { display: flex; align-items: center; }
.filter-card { margin-bottom: 1rem; }

.qty-text { font-size: 0.78rem; color: var(--text-secondary); }
.muted { color: var(--text-secondary); font-size: 0.82rem; }

/* 甘特时间线 */
.timeline-card { margin-bottom: 1rem; }
.gantt-timeline { padding-left: 0; }
.tl-item { background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 0.6rem 0.8rem; }
.tl-head { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 4px; }
.tl-meta { font-size: 0.8rem; color: var(--text-secondary); margin-bottom: 6px; }
.tl-progress { margin-bottom: 6px; }
.tl-resources { display: flex; gap: 4px; flex-wrap: wrap; }
.res-tag { font-size: 0.72rem; }

/* 设备卡片 */
.eq-card { height: 100%; }
.eq-head { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.6rem; }
.eq-name { font-weight: 600; font-size: 0.95rem; }
.eq-no { font-size: 0.75rem; color: var(--text-secondary); }
.eq-status { font-size: 0.72rem; }
.eq-metrics { text-align: center; margin-bottom: 0.5rem; }
.metric { font-size: 1.1rem; font-weight: 600; color: var(--primary); }
.metric .unit { font-size: 0.7rem; color: var(--text-secondary); font-weight: normal; margin-left: 2px; }
.metric-label { font-size: 0.7rem; color: var(--text-secondary); }
.eq-foot { display: flex; justify-content: space-between; font-size: 0.75rem; color: var(--text-secondary); margin-top: 0.5rem; flex-wrap: wrap; gap: 4px; }

/* 动态行(BOM / 资源 / 配件) */
.bom-row { margin-bottom: 8px; }
.bom-row .ant-input-number,
.bom-row .ant-select { width: 100%; }

.progress-text { color: var(--text-secondary); font-size: 0.85rem; margin-top: 4px; }
</style>
相关推荐
泡海椒1 小时前
脚本包导入实战:JQuick-Java自定义包引入与别名配置教程
java·开发语言·python
fbbqt1 小时前
Python模块与包
开发语言·python
电梯界知识分子1 小时前
江西抚州临川九尊府五层别墅:受限楼梯间里,全黑铝合金观光井道配曳引龙门架的落地记录
大数据·前端·网络·算法·家用电梯
雪落漂泊1 小时前
C++智能指针
开发语言·jvm·c++
清水白石0081 小时前
别被 `Process.start()` 骗了:Python multiprocessing 从面试题到线上生产的进程启动机制实战
开发语言·python
geovindu1 小时前
java: Task Scheduler
java·开发语言·后端
神奇小梵1 小时前
java的特殊语法点,与常规语言相异的点
java·开发语言
晴天162 小时前
Web 页面内存溢出与 CPU 溢出:区别、诊断与最佳实践
前端·javascript
摸鱼仙人~2 小时前
需求深度分析提示词-grill me
前端·chrome