<template>
  <div
    :class="['config-panel', { 'dispatch-config-panel': orderDispatchStyle }]"
  >
    <template v-if="orderDispatchStyle">
      <el-empty v-if="taskList.length === 0" description="暂无工序"></el-empty>
      <div v-else class="dispatch-style-panel">
        <div class="dispatch-report-type">
          <span class="dispatch-required">*</span>
          <span class="dispatch-report-label">报工类型:</span>
          <el-radio-group v-model="dispatchReportType">
            <el-radio :label="1">单件报工</el-radio>
            <el-radio :label="0">批量报工</el-radio>
          </el-radio-group>
          <div class="dispatch-plan-info">
            <span class="dispatch-plan-item">
              <span class="dispatch-plan-label">计划数量:</span>
              <span class="dispatch-plan-value">
                {{ dispatchPlanQuantityDisplay }}
              </span>
            </span>
            <span class="dispatch-plan-item">
              <span class="dispatch-plan-label">计划时间:</span>
              <span class="dispatch-plan-value">
                {{ dispatchPlanTimeDisplay }}
              </span>
            </span>
          </div>
        </div>
        <el-tabs
          v-model="activeTaskKey"
          type="border-card"
          class="dispatch-process-tabs"
        >
          <el-tab-pane
            v-for="(item, index) in dispatchTaskTabs"
            :key="dispatchTaskGroupKey(item)"
            :label="taskName(item, index)"
            :name="dispatchTaskGroupKey(item)"
          />
        </el-tabs>

        <div class="dispatch-process-meta">
          工序编码：{{ taskCodeDisplay(activeTask) }}，所属班组：{{
            dispatchTeamDisplay(activeTask)
          }}
        </div>

        <div class="dispatch-toolbar">
          <div class="dispatch-toolbar-left">
            <el-button
              v-if="showFirstTaskConfirmButton"
              type="primary"
              size="mini"
              :loading="dispatchToolbarLoading"
              :disabled="readonlyMode || isDispatchToolbarDisabled('confirm')"
              @click="handleFirstTaskConfirm"
            >
              确定
            </el-button>
            <el-button
              type="primary"
              size="mini"
              :loading="dispatchToolbarLoading"
              :disabled="readonlyMode || isDispatchToolbarDisabled(1)"
              @click="handleDispatchToolbarAction(1)"
            >
              任务派单
            </el-button>
            <el-button
              type="primary"
              size="mini"
              :loading="dispatchToolbarLoading"
              :disabled="readonlyMode || isDispatchToolbarDisabled(2)"
              @click="handleDispatchToolbarAction(2)"
            >
              撤回
            </el-button>
            <el-button
              type="primary"
              size="mini"
              :loading="dispatchToolbarLoading"
              :disabled="readonlyMode || isDispatchToolbarDisabled(3)"
              @click="handleDispatchToolbarAction(3)"
            >
              保存
            </el-button>
            <el-button
              type="success"
              size="mini"
              :loading="dispatchToolbarLoading"
              :disabled="
                readonlyMode || isDispatchToolbarDisabled('addStation')
              "
              @click="handleDispatchAddStation"
            >
              添加工位
            </el-button>
          </div>
          <div class="dispatch-assign-mode">
            <span class="dispatch-assign-label">指派:</span>
            <el-radio-group :value="1" size="mini">
              <el-radio-button :label="1">工位</el-radio-button>
              <!-- <el-radio-button :label="3">产线</el-radio-button> -->
            </el-radio-group>
          </div>
          <div class="dispatch-toolbar-icons">
            <el-tooltip content="刷新" placement="top">
              <el-button
                square
                size="mini"
                :loading="dispatchRefreshLoading"
                @click="handleDispatchRefresh"
              >
                <i class="el-icon-refresh"></i>
              </el-button>
            </el-tooltip>
            <el-popover
              placement="bottom-end"
              width="220"
              trigger="click"
              popper-class="dispatch-column-popover"
            >
              <div class="dispatch-column-settings">
                <div class="dispatch-column-settings__title">列设置</div>
                <el-checkbox-group
                  v-model="dispatchVisibleColumns"
                  class="dispatch-column-settings__list"
                  @change="handleDispatchColumnVisibleChange"
                >
                  <el-checkbox
                    v-for="column in dispatchColumnOptions"
                    :key="column.key"
                    :label="column.key"
                  >
                    {{ column.label }}
                  </el-checkbox>
                </el-checkbox-group>
              </div>
              <el-button slot="reference" square size="mini">
                <i class="el-icon-s-operation"></i>
              </el-button>
            </el-popover>
            <el-tooltip
              :content="dotLineFullscreen ? '退出全屏' : '全屏展示'"
              placement="top"
            >
              <el-button square size="mini" @click="$emit('toggle-fullscreen')">
                <i
                  :class="
                    dotLineFullscreen
                      ? 'el-icon-_screen-restore'
                      : 'el-icon-full-screen'
                  "
                ></i>
              </el-button>
            </el-tooltip>
          </div>
        </div>

        <div class="dispatch-table-wrap">
          <el-table
            ref="dispatchTable"
            :data="pagedDispatchObjectRows"
            border
            size="small"
            :row-key="dispatchObjectRowKey"
            :row-class-name="getDispatchTableRowClassName"
            height="100%"
            class="config-table dispatch-table"
          >
            <el-table-column
              type="index"
              label="序号"
              width="52"
              align="center"
            />
            <el-table-column width="44" align="center">
              <template slot="header">
                <el-checkbox
                  class="dispatch-state-checkbox"
                  :value="isCurrentPageDispatchChecked"
                  :indeterminate="isCurrentPageDispatchIndeterminate"
                  :disabled="
                    readonlyMode || !currentPageSelectableDispatchRows.length
                  "
                  @change="handleCurrentPageDispatchCheckChange"
                />
              </template>
              <template slot-scope="{ row }">
                <el-tooltip
                  :disabled="!isDeviceSnapshotLocked(row)"
                  content="该工序已制定设备类型"
                  placement="top"
                  popper-class="dispatch-device-lock-tooltip"
                >
                  <span class="dispatch-checkbox-wrap">
                    <el-checkbox
                      class="dispatch-state-checkbox"
                      :value="isSelectedDispatchObject(row)"
                      :disabled="isDispatchRowControlDisabled(row)"
                      @change="handleDispatchObjectCheckChange(row, $event)"
                    />
                  </span>
                </el-tooltip>
              </template>
            </el-table-column>
            <el-table-column
              v-if="isDispatchColumnVisible('displayName')"
              prop="displayName"
              label="工位名称"
              align="center"
              min-width="180"
            >
              <template slot-scope="{ row }">
                <el-tooltip
                  :disabled="!isDeviceSnapshotLocked(row) && !row.displayName"
                  :content="
                    isDeviceSnapshotLocked(row)
                      ? '该工序已制定设备类型'
                      : row.displayName || ''
                  "
                  placement="top"
                  :popper-class="
                    isDeviceSnapshotLocked(row)
                      ? 'dispatch-device-lock-tooltip'
                      : 'dispatch-workstation-tooltip'
                  "
                >
                  <span class="dispatch-workstation-name">
                    {{ row.displayName }}
                  </span>
                </el-tooltip>
              </template>
            </el-table-column>
            <el-table-column
              v-if="isDispatchColumnVisible('code')"
              prop="code"
              label="编码"
              align="center"
              min-width="140"
              show-overflow-tooltip
            />
            <el-table-column
              v-if="isDispatchColumnVisible('assetCode')"
              prop="assetCode"
              label="设备编码"
              align="center"
              min-width="140"
              show-overflow-tooltip
            />
            <el-table-column
              v-if="isDispatchColumnVisible('assetName')"
              prop="assetName"
              label="设备名称"
              align="center"
              min-width="140"
              show-overflow-tooltip
            />
            <el-table-column
              v-if="isDispatchColumnVisible('assetModelType')"
              prop="assetModelType"
              label="设备型号"
              align="center"
              min-width="140"
              show-overflow-tooltip
            />
            <el-table-column
              v-if="isDispatchColumnVisible('assetCategoryLevelPath')"
              prop="assetCategoryLevelPath"
              label="设备类型"
              align="center"
              min-width="140"
              show-overflow-tooltip
            />
            <el-table-column
              v-if="isDispatchColumnVisible('status')"
              prop="status"
              label="状态"
              align="center"
              width="120"
            >
              <template slot-scope="{ row }">
                <span
                  v-if="getDispatchStatusText(row.status)"
                  :class="[
                    'dispatch-status-badge',
                    { 'is-assigned': isDispatchRowAssigned(row) }
                  ]"
                >
                  {{ getDispatchStatusText(row.status) }}
                </span>
              </template>
            </el-table-column>
            <el-table-column
              v-if="isDispatchColumnVisible('quantity')"
              label="数量"
              align="center"
              width="140"
            >
              <template slot-scope="{ row }">
                <el-tooltip
                  :disabled="!isDeviceSnapshotLocked(row)"
                  content="该工序已制定设备类型"
                  placement="top"
                  popper-class="dispatch-device-lock-tooltip"
                >
                  <span class="dispatch-disabled-field">
                    <el-input
                      :value="row.quantity"
                      placeholder="请输入数量"
                      class="config-table-control"
                      :disabled="isDispatchRowControlDisabled(row)"
                      @input="handleDispatchRowQuantityInput(row, $event)"
                    />
                  </span>
                </el-tooltip>
              </template>
            </el-table-column>
            <el-table-column
              v-if="isDispatchColumnVisible('startTime')"
              label="计划开始时间"
              align="center"
              min-width="220"
            >
              <template slot-scope="{ row }">
                <el-tooltip
                  :disabled="!isDeviceSnapshotLocked(row)"
                  content="该工序已制定设备类型"
                  placement="top"
                  popper-class="dispatch-device-lock-tooltip"
                >
                  <span class="dispatch-disabled-field">
                    <el-date-picker
                      v-model="row.executionStartTime"
                      type="datetime"
                      value-format="yyyy-MM-dd HH:mm:ss"
                      placeholder="开始时间"
                      clearable
                      class="config-table-control"
                      :disabled="isDispatchRowControlDisabled(row)"
                      @change="handleDispatchRowStartTimeChange(row)"
                    />
                  </span>
                </el-tooltip>
              </template>
            </el-table-column>
            <el-table-column
              v-if="isDispatchColumnVisible('endTime')"
              label="计划完成时间"
              align="center"
              min-width="220"
            >
              <template slot-scope="{ row }">
                <div
                  :class="{
                    'last-end-over-plan-wrap': isExecutionEndOverPlanEnd(
                      getDispatchRowTask(row)
                    ),
                    'end-over-delivery-wrap': isExecutionEndOverDelivery(
                      getDispatchRowTask(row)
                    )
                  }"
                >
                  <el-tooltip
                    :disabled="!isDeviceSnapshotLocked(row)"
                    content="该工序已制定设备类型"
                    placement="top"
                    popper-class="dispatch-device-lock-tooltip"
                  >
                    <span class="dispatch-disabled-field">
                      <el-date-picker
                        v-model="row.executionEndTime"
                        type="datetime"
                        value-format="yyyy-MM-dd HH:mm:ss"
                        placeholder="完成时间"
                        clearable
                        class="config-table-control"
                        :disabled="isDispatchRowControlDisabled(row)"
                        @change="handleDispatchRowEndTimeChange(row)"
                      />
                    </span>
                  </el-tooltip>
                </div>
              </template>
            </el-table-column>
            <el-table-column
              v-if="isDispatchColumnVisible('action')"
              label="操作"
              width="88"
              align="center"
            >
              <template slot-scope="{ row }">
                <el-link
                  v-if="canDeleteAddedDispatchStation(row)"
                  type="danger"
                  :underline="false"
                  @click="handleDeleteAddedDispatchStation(row)"
                >
                  删除
                </el-link>
                <el-popconfirm
                  v-if="canResetDispatchRow(row)"
                  title="确定要重置该条数据吗？"
                  @confirm="handleDispatchRowReset(row)"
                >
                  <template v-slot:reference>
                    <el-link type="primary" :underline="false">重置</el-link>
                  </template>
                </el-popconfirm>
              </template>
            </el-table-column>
          </el-table>
        </div>
        <div v-if="dispatchObjectRows.length" class="dispatch-pagination">
          <el-pagination
            background
            :current-page="dispatchPage"
            :page-size="dispatchPageSize"
            :page-sizes="[10, 20, 50, 100]"
            :total="dispatchObjectRows.length"
            layout="total, sizes, prev, pager, next, jumper"
            @size-change="handleDispatchPageSizeChange"
            @current-change="handleDispatchPageChange"
          />
        </div>
      </div>
    </template>

    <template v-else>
      <div class="panel-header">
        <div class="panel-title">工艺配置</div>
        <div class="panel-actions">
          <span v-if="readonlyMode" class="readonly-tip">
            当前状态仅支持查看
          </span>
          <el-tooltip
            :content="dotLineFullscreen ? '退出全屏' : '全屏展示'"
            placement="top"
          >
            <el-button circle size="mini" @click="$emit('toggle-fullscreen')">
              <i
                :class="
                  dotLineFullscreen
                    ? 'el-icon-_screen-restore'
                    : 'el-icon-full-screen'
                "
              ></i>
            </el-button>
          </el-tooltip>
          <el-button
            v-if="showApproveButton"
            type="primary"
            size="mini"
            :loading="approveLoading"
            :disabled="approveButtonDisabled"
            @click="$emit('approve')"
          >
            审批
          </el-button>
          <el-button
            type="primary"
            size="mini"
            :disabled="!currentPlan || dotLineLoading || readonlyMode"
            @click="$emit('submit')"
          >
            保存
          </el-button>
        </div>
      </div>
      <el-empty v-if="taskList.length === 0" description="暂无工艺"></el-empty>
      <div v-else class="task-config-table-wrap">
        <el-table
          :data="taskList"
          border
          size="small"
          row-key="_taskKey"
          :height="normalTableHeight"
          :max-height="normalTableMaxHeight"
          class="config-table"
        >
          <el-table-column
            type="index"
            label="序号"
            width="46"
            align="center"
            class-name="config-table-index-col"
          />
          <el-table-column
            label="工序名称"
            min-width="82"
            show-overflow-tooltip
            class-name="task-name-cell"
            align="center"
          >
            <template slot-scope="{ row, $index }">
              <span class="task-name-text">{{ taskName(row, $index) }}</span>
            </template>
          </el-table-column>
          <el-table-column label="工序并行" min-width="92" align="center">
            <template slot-scope="{ row }">
              <el-select
                v-model="row.isParallelism"
                placeholder="工序并行"
                :disabled="readonlyMode"
                class="config-table-control"
                @change="$emit('parallelism-change', row)"
              >
                <el-option label="否" :value="0" />
                <el-option label="是" :value="1" />
              </el-select>
            </template>
          </el-table-column>
          <el-table-column label="执行模式" min-width="84" align="center">
            <template slot-scope="{ row }">
              <el-select
                v-model="row.executionType"
                placeholder="执行模式"
                clearable
                :disabled="readonlyMode"
                class="config-table-control"
                @change="$emit('execution-type-change', row)"
              >
                <el-option
                  v-for="opt in executionTypeOptions"
                  :key="opt.value"
                  :label="opt.label"
                  :value="opt.value"
                />
              </el-select>
            </template>
          </el-table-column>
          <el-table-column
            :label="homemadeObjectLabel"
            min-width="116"
            align="center"
          >
            <template slot-scope="{ row }">
              <el-select
                v-if="execTypeNum(row) === execType.HOMEMADE"
                v-model="row.executionTeamId"
                placeholder="请选择"
                clearable
                filterable
                :disabled="readonlyMode"
                class="config-table-control"
                @change="$emit('execution-object-change', row)"
              >
                <!-- 班组排程需要按当前工序过滤工位；工厂排程回退为原 teamOptions。 -->
                <el-option
                  v-for="team in homemadeOptions(row)"
                  :key="team.id"
                  :label="team.name"
                  :value="team.id"
                />
              </el-select>
              <el-select
                v-else-if="execTypeNum(row) === execType.ENTRUST"
                v-model="row.executionFactoryId"
                placeholder="请选择"
                clearable
                filterable
                :disabled="readonlyMode"
                class="config-table-control"
                @change="$emit('execution-object-change', row)"
              >
                <el-option
                  v-for="factory in factoryList"
                  :key="factory.value"
                  :label="factory.label"
                  :value="factory.value"
                />
              </el-select>
              <el-select
                v-else-if="execTypeNum(row) === execType.OUTSOURCE"
                :value="''"
                placeholder="委外无需选择"
                disabled
                class="config-table-control"
              />
              <el-select
                v-else
                :value="''"
                placeholder="请先选择执行模式"
                disabled
                class="config-table-control"
              />
            </template>
          </el-table-column>
          <el-table-column
            v-if="showQuantityColumn"
            label="数量"
            min-width="82"
            align="center"
          >
            <!-- 数量只在班组排程工序配置中开放，工厂排程不渲染该列。 -->
            <template slot-scope="{ row }">
              <el-input-number
                v-model="row.quantity"
                :min="0"
                :controls="false"
                :precision="3"
                placeholder="请输入数量"
                :disabled="readonlyMode"
                class="config-table-control"
              />
            </template>
          </el-table-column>
          <el-table-column label="执行开始时间" min-width="164" align="center">
            <template slot-scope="{ row }">
              <el-date-picker
                v-model="row.executionStartTime"
                type="datetime"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="执行开始时间"
                clearable
                :disabled="readonlyMode"
                class="config-table-control"
                @change="$emit('time-change', row, 'executionStartTime')"
              />
            </template>
          </el-table-column>
          <el-table-column label="执行结束时间" min-width="164" align="center">
            <template slot-scope="{ row }">
              <div
                :class="{
                  'last-end-over-plan-wrap': isExecutionEndOverPlanEnd(row),
                  'end-over-delivery-wrap': isExecutionEndOverDelivery(row)
                }"
              >
                <el-date-picker
                  v-model="row.executionEndTime"
                  type="datetime"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="执行结束时间"
                  clearable
                  :disabled="readonlyMode"
                  class="config-table-control"
                  @change="$emit('time-change', row, 'executionEndTime')"
                />
              </div>
            </template>
          </el-table-column>
          <el-table-column label="操作" width="64" align="center">
            <template slot-scope="{ row }">
              <el-button
                type="text"
                size="small"
                :disabled="!currentPlan || dotLineLoading || readonlyMode"
                @click="$emit('row-submit', row)"
              >
                缓存
              </el-button>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </template>

    <choose-station
      v-if="orderDispatchStyle"
      ref="chooseStationRef"
      @chooseStationList="handleChooseStationList"
    />
  </div>
</template>

<script>
  import chooseStation from '@/views/workOrder/components/chooseStation.vue';
  import {
    checkAssignConfirm,
    listAssign,
    listByPlanIdAndTaskId,
    parameterGetByCode,
    resetAssignee,
    taskAssignment,
    taskRevoked,
    taskSave
  } from '@/api/mainData/index.js';
  import {
    getcheckLoginUserIsTeamLeader,
    releaseWorkOrder
  } from '@/api/workOrder/index.js';

  const DISPATCH_TEAM_ID_KEYS = [
    'dispatchTeamId',
    'teamId',
    'firstTaskTeamId',
    'firstTeamId',
    'teamIds',
    'executionTeamId'
  ];
  const DISPATCH_TEAM_NAME_KEYS = [
    'dispatchTeamName',
    'teamName',
    'firstTaskTeamName',
    'firstTeamName',
    'teamNames',
    'executionTeamName'
  ];

  export default {
    name: 'TaskConfigPanel',
    components: {
      chooseStation
    },
    props: {
      dotLineFullscreen: {
        type: Boolean,
        default: false
      },
      isCurrentPlanReadonly: {
        type: Boolean,
        default: false
      },
      orderDetailReadonly: {
        type: Boolean,
        default: false
      },
      currentPlan: {
        type: Object,
        default: null
      },
      dotLineLoading: {
        type: Boolean,
        default: false
      },
      taskList: {
        type: Array,
        default: () => []
      },
      executionTypeOptions: {
        type: Array,
        default: () => []
      },
      teamOptions: {
        type: Array,
        default: () => []
      },
      getHomemadeOptions: {
        type: Function,
        default: null
      },
      homemadeObjectLabel: {
        type: String,
        default: '执行对象'
      },
      showQuantityColumn: {
        type: Boolean,
        default: false
      },
      orderDispatchStyle: {
        type: Boolean,
        default: false
      },
      showApproveButton: {
        type: Boolean,
        default: false
      },
      approveButtonDisabled: {
        type: Boolean,
        default: true
      },
      approveLoading: {
        type: Boolean,
        default: false
      },
      tableMaxHeight: {
        type: Number,
        default: 390
      },
      fillTableHeight: {
        type: Boolean,
        default: false
      },
      factoryList: {
        type: Array,
        default: () => []
      },
      execType: {
        type: Object,
        required: true
      },
      taskName: {
        type: Function,
        required: true
      },
      execTypeNum: {
        type: Function,
        required: true
      },
      isExecutionEndOverDelivery: {
        type: Function,
        required: true
      },
      isExecutionEndOverPlanEnd: {
        type: Function,
        required: true
      }
    },
    data() {
      return {
        activeTaskKey: '',
        dispatchPage: 1,
        dispatchPageSize: 20,
        dispatchRowDraftMap: {},
        dispatchAssignmentMap: {},
        dispatchDeviceSnapshotMap: {},
        dispatchDeviceSnapshotLoading: false,
        dispatchDeviceSnapshotRequestKey: '',
        dispatchToolbarLoading: false,
        dispatchRefreshLoading: false,
        dispatchReportType: 1,
        dispatchColumnOptions: [
          { key: 'displayName', label: '工位名称' },
          { key: 'code', label: '编码' },
          { key: 'assetCode', label: '设备编码' },
          { key: 'assetName', label: '设备名称' },
          { key: 'assetModelType', label: '设备型号' },
          { key: 'assetCategoryLevelPath', label: '设备类型' },
          { key: 'status', label: '状态' },
          { key: 'quantity', label: '数量' },
          { key: 'startTime', label: '计划开始时间' },
          { key: 'endTime', label: '计划完成时间' },
          { key: 'action', label: '操作' }
        ],
        dispatchColumnVisible: {
          displayName: true,
          code: true,
          assetCode: true,
          assetName: true,
          assetModelType: true,
          assetCategoryLevelPath: true,
          status: true,
          quantity: true,
          startTime: true,
          endTime: true,
          action: true
        },
        suppressTaskListAssignReloadUntil: 0
      };
    },
    computed: {
      readonlyMode() {
        return this.orderDetailReadonly || this.isCurrentPlanReadonly;
      },
      dispatchVisibleColumns: {
        get() {
          return this.dispatchColumnOptions
            .filter((item) => this.dispatchColumnVisible[item.key] !== false)
            .map((item) => item.key);
        },
        set(keys) {
          const visibleKeys = new Set(keys);
          this.dispatchColumnOptions.forEach((item) => {
            this.$set(
              this.dispatchColumnVisible,
              item.key,
              visibleKeys.has(item.key)
            );
          });
        }
      },
      dispatchTaskTabs() {
        const groupMap = new Map();
        (this.taskList || []).forEach((item) => {
          const groupKey = this.dispatchTaskGroupKey(item);
          if (!groupMap.has(groupKey)) {
            groupMap.set(groupKey, item);
          }
        });
        return Array.from(groupMap.values());
      },
      activeTask() {
        return (
          (this.taskList || []).find(
            (item) => this.dispatchTaskGroupKey(item) === this.activeTaskKey
          ) ||
          this.dispatchTaskTabs[0] ||
          null
        );
      },
      showFirstTaskConfirmButton() {
        return (
          this.orderDispatchStyle &&
          !this.readonlyMode &&
          this.isFirstDispatchTask(this.activeTask)
        );
      },
      reportTypeValue() {
        return this.normalizeDispatchReportType(this.dispatchReportType);
      },
      dispatchPlanQuantityDisplay() {
        const plan = this.currentPlan || {};
        const num =
          plan.productNum ??
          plan.formingNum ??
          plan.planNum ??
          plan.requiredFormingNum ??
          '';
        const unit = plan.measuringUnit || plan.unit || '';
        const text = [num, unit]
          .filter((item) => item !== null && item !== undefined && item !== '')
          .join('');
        return text || '--';
      },
      dispatchPlanQuantityLimit() {
        const plan = this.currentPlan || {};
        return this.parseDispatchQuantity(
          plan.productNum ??
            plan.formingNum ??
            plan.planNum ??
            plan.requiredFormingNum ??
            ''
        );
      },
      dispatchPlanTimeDisplay() {
        const plan = this.currentPlan || {};
        const start =
          plan.startTime || plan.planStartTime || plan.executionStartTime || '';
        const end =
          plan.endTime ||
          plan.planEndTime ||
          plan.planCompleteTime ||
          plan.executionEndTime ||
          '';
        const startText = this.formatDispatchDateTime(start);
        const endText = this.formatDispatchDateTime(end);
        if (startText && endText) {
          return `${startText} 至 ${endText}`;
        }
        return startText || endText || '--';
      },
      dispatchObjectRows() {
        if (!this.activeTask) {
          return [];
        }
        return this.homemadeOptions(this.activeTask)
          .filter((item) => item.objectType !== 'person')
          .map((item) => {
            const displayName = String(item.name || '').replace(
              /^工位[:：]/,
              ''
            );
            const rowKey = this.dispatchObjectRowKey(item);
            const task = this.getDispatchRowTask(item);
            const draft =
              this.dispatchRowDraftMap[this.dispatchRowDraftKey(item)] || {};
            const assignment = this.getDispatchAssignment(item);
            const defaultStartTime =
              this.activeTask.dispatchDefaultStartTime || '';
            const defaultEndTime = this.activeTask.dispatchDefaultEndTime || '';
            return {
              ...item,
              id: rowKey,
              displayName,
              quantity:
                draft.quantity !== undefined
                  ? draft.quantity
                  : task
                  ? this.formatDispatchRowQuantity(task.quantity)
                  : assignment
                  ? this.formatDispatchRowQuantity(assignment.quantity)
                  : '',
              executionStartTime:
                draft.executionStartTime !== undefined
                  ? draft.executionStartTime
                  : task?.executionStartTime ||
                    assignment?.executionStartTime ||
                    defaultStartTime,
              executionEndTime:
                draft.executionEndTime !== undefined
                  ? draft.executionEndTime
                  : task?.executionEndTime ||
                    assignment?.executionEndTime ||
                    defaultEndTime,
              status: assignment?.status || '',
              changeId: assignment?.changeId || '',
              teamTimeIds: task?.teamTimeIds || assignment?.teamTimeIds,
              weight: task?.weight || assignment?.weight || item.weight || '',
              code: item.code || item.workstationCode || '',
              assetCode: this.pickDispatchSingleValue([
                {
                  source: item,
                  keys: ['assetCode', 'deviceCode']
                },
                {
                  source: item.extInfo,
                  keys: ['assetCode', 'deviceCode']
                }
              ]),
              assetName: this.pickDispatchSingleValue([
                {
                  source: item,
                  keys: ['assetName', 'deviceName']
                },
                {
                  source: item.extInfo,
                  keys: ['assetName', 'deviceName']
                }
              ]),
              assetModelType: item.assetModelType || item.deviceModel || '',
              assetCategoryLevelPath:
                item.assetCategoryLevelPath || item.deviceType || ''
            };
          });
      },
      pagedDispatchObjectRows() {
        const start = (this.dispatchPage - 1) * this.dispatchPageSize;
        return this.dispatchObjectRows.slice(
          start,
          start + this.dispatchPageSize
        );
      },
      selectedDispatchObjectRows() {
        return this.dispatchObjectRows.filter(
          (row) =>
            !this.isDeviceSnapshotLocked(row) &&
            this.isSelectedDispatchObject(row)
        );
      },
      currentPageSelectableDispatchRows() {
        return this.pagedDispatchObjectRows.filter(
          (row) => !this.isDeviceSnapshotLocked(row)
        );
      },
      currentPageSelectedDispatchRows() {
        return this.currentPageSelectableDispatchRows.filter((row) =>
          this.isSelectedDispatchObject(row)
        );
      },
      isCurrentPageDispatchChecked() {
        return (
          this.currentPageSelectableDispatchRows.length > 0 &&
          this.currentPageSelectedDispatchRows.length ===
            this.currentPageSelectableDispatchRows.length
        );
      },
      isCurrentPageDispatchIndeterminate() {
        return (
          this.currentPageSelectedDispatchRows.length > 0 &&
          this.currentPageSelectedDispatchRows.length <
            this.currentPageSelectableDispatchRows.length
        );
      },
      normalTableHeight() {
        return this.fillTableHeight ? '100%' : undefined;
      },
      normalTableMaxHeight() {
        return this.fillTableHeight ? undefined : this.tableMaxHeight;
      }
    },
    watch: {
      activeTaskKey(newKey, oldKey) {
        this.clearDispatchSelectionsByTaskKey(oldKey);
        this.dispatchPage = 1;
        this.loadDispatchAssignData();
        this.loadDispatchDeviceSnapshots();
      },
      currentPlan: {
        immediate: true,
        handler() {
          this.dispatchReportType = this.getDefaultDispatchReportType();
          this.dispatchRowDraftMap = {};
          this.dispatchAssignmentMap = {};
          this.dispatchDeviceSnapshotMap = {};
          this.dispatchPage = 1;
          this.loadDispatchAssignData();
          this.loadDispatchDeviceSnapshots();
        }
      },
      dispatchObjectRows() {
        const maxPage = Math.max(
          1,
          Math.ceil(this.dispatchObjectRows.length / this.dispatchPageSize)
        );
        if (this.dispatchPage > maxPage) {
          this.dispatchPage = maxPage;
        }
      },
      taskList: {
        immediate: true,
        handler(list) {
          if (!Array.isArray(list) || list.length === 0) {
            this.activeTaskKey = '';
            this.dispatchRowDraftMap = {};
            this.dispatchDeviceSnapshotMap = {};
            this.dispatchPage = 1;
            return;
          }
          const hasActive = list.some(
            (item) => this.dispatchTaskGroupKey(item) === this.activeTaskKey
          );
          if (!hasActive) {
            this.activeTaskKey = this.dispatchTaskGroupKey(list[0]);
          }
          if (Date.now() < this.suppressTaskListAssignReloadUntil) {
            return;
          }
          this.$nextTick(() => {
            this.loadDispatchAssignData();
            this.loadDispatchDeviceSnapshots();
          });
        }
      }
    },
    methods: {
      normalizeDispatchReportType(value) {
        return String(value) === '0' ? 0 : 1;
      },
      getDefaultDispatchReportType() {
        const value =
          this.currentPlan?.singleReport ??
          this.currentPlan?.reportType ??
          this.currentPlan?.workOrderSingleReport;
        return this.normalizeDispatchReportType(value);
      },
      isDispatchColumnVisible(key) {
        return this.dispatchColumnVisible[key] !== false;
      },
      handleDispatchColumnVisibleChange(keys) {
        if (!keys.length) {
          this.dispatchVisibleColumns = ['displayName'];
          this.$message.warning('至少保留一列');
        }
        this.$nextTick(() => {
          this.$refs.dispatchTable?.doLayout?.();
        });
      },
      parseDispatchQuantity(value) {
        if (value === null || value === undefined || value === '') {
          return null;
        }
        const text = String(value).trim();
        if (!/^([1-9]\d*(\.\d+)?|0\.\d*[1-9]\d*)$/.test(text)) {
          return null;
        }
        const num = Number(text);
        return Number.isFinite(num) ? num : null;
      },
      formatDispatchDateTime(value) {
        if (!value) {
          return '';
        }
        const text = String(value);
        if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}/.test(text)) {
          return text.slice(0, 16);
        }
        if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(text)) {
          return text.replace('T', ' ').slice(0, 16);
        }
        return text;
      },
      getDispatchPlanStartTime() {
        const plan = this.currentPlan || {};
        return (
          plan.startTime || plan.planStartTime || plan.executionStartTime || ''
        );
      },
      getDispatchPlanEndTime() {
        const plan = this.currentPlan || {};
        return (
          plan.endTime ||
          plan.planEndTime ||
          plan.planCompleteTime ||
          plan.executionEndTime ||
          ''
        );
      },
      getDispatchTaskDefaultStartTime(row = this.activeTask) {
        return row?.executionStartTime || row?.startTime || '';
      },
      getDispatchTaskDefaultEndTime(row = this.activeTask) {
        return row?.executionEndTime || row?.endTime || '';
      },
      parseDispatchTime(value) {
        if (!value) {
          return null;
        }
        if (value instanceof Date) {
          const dateTime = value.getTime();
          return Number.isFinite(dateTime) ? dateTime : null;
        }
        const text = String(value).replace('T', ' ').replace(/-/g, '/');
        const dateTime = new Date(text).getTime();
        return Number.isFinite(dateTime) ? dateTime : null;
      },
      getDispatchSourceTaskId(row) {
        return (
          row?.sourceTaskId ?? row?.firstTaskId ?? row?.taskId ?? row?.id ?? ''
        );
      },
      dispatchTaskGroupKey(row) {
        return String(
          row?.sourceTaskId ??
            row?.firstTaskId ??
            row?.taskId ??
            row?.id ??
            row?._taskKey ??
            ''
        );
      },
      isFirstDispatchTask(row) {
        if (!row) {
          return false;
        }
        const firstTaskId =
          this.currentPlan?.firstTaskId ??
          this.currentPlan?.taskId ??
          this.currentPlan?.sourceTaskId ??
          '';
        if (
          firstTaskId !== null &&
          firstTaskId !== undefined &&
          firstTaskId !== ''
        ) {
          return (
            String(
              row.sourceTaskId ?? row.firstTaskId ?? row.taskId ?? row.id
            ) === String(firstTaskId)
          );
        }
        const index = this.dispatchTaskTabs.findIndex(
          (item) =>
            this.dispatchTaskGroupKey(item) === this.dispatchTaskGroupKey(row)
        );
        return index === 0;
      },
      dispatchObjectRowKey(row) {
        const rawId =
          row?.rawId ??
          row?.workstationId ??
          row?.factoryWorkstationId ??
          row?.stationId ??
          row?.id ??
          row?.code ??
          row?.workstationCode ??
          '';
        if (rawId !== null && rawId !== undefined && rawId !== '') {
          return String(row?.objectType || 'workstation') + ':' + String(rawId);
        }
        return String(row?.name || row?.displayName || '');
      },
      getDispatchAssignmentKey(row) {
        return `${this.dispatchTaskGroupKey(
          this.activeTask
        )}::${this.dispatchObjectRowKey(row)}`;
      },
      getDispatchAssignment(row) {
        return this.dispatchAssignmentMap[this.getDispatchAssignmentKey(row)];
      },
      removeDispatchAssignment(row) {
        const key = this.getDispatchAssignmentKey(row);
        if (key) {
          this.$delete(this.dispatchAssignmentMap, key);
        }
      },
      getDispatchWorkOrderId() {
        const plan = this.currentPlan || {};
        return plan.workOrderId || plan.orderId || plan.sourceWorkOrderId || '';
      },
      getDispatchPlanId() {
        const plan = this.currentPlan || {};
        return plan.productionPlanId || plan.planId || plan.id || '';
      },
      getDeviceSnapshotRequestKey(row = this.activeTask) {
        const planId = this.getDispatchPlanId();
        const taskId = this.getDispatchSourceTaskId(row);
        return planId && taskId ? `${planId}::${taskId}` : '';
      },
      normalizeDeviceSnapshotCode(value) {
        if (value === null || value === undefined || value === '') {
          return '';
        }
        return String(value).trim();
      },
      getDispatchDeviceSnapshotCandidateCodes(row) {
        return [
          row?.assetCode,
          row?.deviceCode,
          row?.extInfo?.assetCode,
          row?.extInfo?.deviceCode
        ]
          .map((item) => this.normalizeDeviceSnapshotCode(item))
          .filter(Boolean);
      },
      isDispatchDeviceSnapshotCodeMatched(row, snapshotCodes = {}) {
        return this.getDispatchDeviceSnapshotCandidateCodes(row).some(
          (code) => snapshotCodes[code]
        );
      },
      hasDispatchDeviceSnapshotMatchedRow(snapshotCodes = {}) {
        return this.dispatchObjectRows.some((row) =>
          this.isDispatchDeviceSnapshotCodeMatched(row, snapshotCodes)
        );
      },
      isDeviceSnapshotLocked(row) {
        if (!this.orderDispatchStyle || !row) {
          return false;
        }
        const requestKey = this.getDeviceSnapshotRequestKey();
        if (
          !requestKey ||
          !Object.prototype.hasOwnProperty.call(
            this.dispatchDeviceSnapshotMap,
            requestKey
          )
        ) {
          return false;
        }
        const snapshotCodes = this.dispatchDeviceSnapshotMap[requestKey] || {};
        if (
          !Object.keys(snapshotCodes).length ||
          !this.hasDispatchDeviceSnapshotMatchedRow(snapshotCodes)
        ) {
          return false;
        }
        return !this.isDispatchDeviceSnapshotCodeMatched(row, snapshotCodes);
      },
      clearDeviceSnapshotLockedSelections() {
        this.dispatchObjectRows.forEach((row) => {
          if (
            this.isDeviceSnapshotLocked(row) &&
            this.isSelectedDispatchObject(row)
          ) {
            this.clearDispatchObject(row);
          }
        });
      },
      async loadDispatchDeviceSnapshots() {
        if (
          !this.orderDispatchStyle ||
          this.readonlyMode ||
          !this.activeTask ||
          !this.currentPlan
        ) {
          return;
        }
        const planId = this.getDispatchPlanId();
        const taskId = this.getDispatchSourceTaskId(this.activeTask);
        const requestKey = this.getDeviceSnapshotRequestKey(this.activeTask);
        if (!planId || !taskId || !requestKey) {
          return;
        }
        this.dispatchDeviceSnapshotLoading = true;
        this.dispatchDeviceSnapshotRequestKey = requestKey;
        try {
          const res = await listByPlanIdAndTaskId({ planId, taskId });
          if (this.dispatchDeviceSnapshotRequestKey !== requestKey) {
            return;
          }
          const snapshotCodes = {};
          (Array.isArray(res) ? res : []).forEach((item) => {
            const code = this.normalizeDeviceSnapshotCode(item?.deviceCode);
            if (code) {
              snapshotCodes[code] = true;
            }
          });
          this.$set(this.dispatchDeviceSnapshotMap, requestKey, snapshotCodes);
          this.clearDeviceSnapshotLockedSelections();
        } catch (e) {
          if (this.dispatchDeviceSnapshotRequestKey === requestKey) {
            this.$delete(this.dispatchDeviceSnapshotMap, requestKey);
          }
          this.$message.warning(e.message || '获取工序设备快照失败');
        } finally {
          if (this.dispatchDeviceSnapshotRequestKey === requestKey) {
            this.dispatchDeviceSnapshotLoading = false;
          }
        }
      },
      getDispatchWorkCenterId(row = this.activeTask) {
        const plan = this.currentPlan || {};
        return (
          row?.workCenterId ||
          row?.workcenterId ||
          row?.sourceWorkCenterId ||
          plan.workCenterId ||
          ''
        );
      },
      getDispatchWorkCenterName(row = this.activeTask) {
        const plan = this.currentPlan || {};
        return (
          row?.workCenterName ||
          row?.workcenterName ||
          row?.sourceWorkCenterName ||
          plan.workCenterName ||
          ''
        );
      },
      normalizeDispatchSingleValue(value) {
        if (Array.isArray(value)) {
          return value.length
            ? this.normalizeDispatchSingleValue(value[0])
            : '';
        }
        if (value === null || value === undefined) {
          return '';
        }
        const text = String(value).trim();
        if (!text) {
          return '';
        }
        if (['undefined', 'null'].includes(text.toLowerCase())) {
          return '';
        }
        return text.includes(',') ? text.split(',')[0].trim() : text;
      },
      pickDispatchSingleValue(groups = []) {
        for (const group of groups) {
          const source = group?.source || {};
          const keys = Array.isArray(group?.keys) ? group.keys : [];
          for (const key of keys) {
            const value = this.normalizeDispatchSingleValue(source[key]);
            if (value) {
              return value;
            }
          }
        }
        return '';
      },
      pickDispatchTeamId(groups = []) {
        for (const group of groups) {
          const source = group?.source || {};
          const keys = Array.isArray(group?.keys) ? group.keys : [];
          for (const key of keys) {
            const value = this.normalizeDispatchSingleValue(source[key]);
            if (value && !String(value).includes(':')) {
              return value;
            }
          }
        }
        return '';
      },
      getDispatchTeamId() {
        const plan = this.currentPlan || {};
        const task = this.activeTask || {};
        return this.pickDispatchTeamId([
          { source: task, keys: DISPATCH_TEAM_ID_KEYS },
          { source: plan, keys: DISPATCH_TEAM_ID_KEYS }
        ]);
      },
      getDispatchTeamName() {
        const plan = this.currentPlan || {};
        const task = this.activeTask || {};
        return this.pickDispatchSingleValue([
          { source: task, keys: DISPATCH_TEAM_NAME_KEYS },
          { source: plan, keys: DISPATCH_TEAM_NAME_KEYS }
        ]);
      },
      getDispatchTaskTeamId(row = this.activeTask) {
        const teamIdKeys = [
          'dispatchTeamId',
          'teamId',
          'firstTaskTeamId',
          'firstTeamId',
          'teamIds',
          'executionTeamId'
        ];
        return this.pickDispatchTeamId([
          { source: row || {}, keys: teamIdKeys },
          { source: this.currentPlan || {}, keys: teamIdKeys }
        ]);
      },
      getDispatchTaskLeaderId(row = this.activeTask) {
        const leaderKeys = [
          'dispatchTeamLeaderId',
          'executionTeamLeaderId',
          'teamLeaderUserId',
          'leaderUserId',
          'executionLeaderId'
        ];
        return this.pickDispatchSingleValue([
          { source: row || {}, keys: leaderKeys },
          { source: this.currentPlan || {}, keys: leaderKeys }
        ]);
      },
      getUserSingleValueSet(value) {
        const result = new Set();
        const append = (source) => {
          if (Array.isArray(source)) {
            source.forEach(append);
            return;
          }
          if (source === null || source === undefined || source === '') {
            return;
          }
          String(source)
            .split(',')
            .map((item) => item.trim())
            .filter(Boolean)
            .forEach((item) => result.add(String(item)));
        };
        append(value);
        return result;
      },
      getCurrentUserInfo() {
        return this.$store?.state?.user?.info || {};
      },
      getCurrentUserTeamIdSet() {
        const userInfo = this.getCurrentUserInfo();
        const ids = this.getUserSingleValueSet(userInfo.teamId);
        (userInfo.teamList || []).forEach((item) => {
          const id = item?.teamId ?? item?.id;
          if (id !== null && id !== undefined && id !== '') {
            ids.add(String(id));
          }
        });
        return ids;
      },
      getCurrentUserWorkCenterIdSet() {
        const userInfo = this.getCurrentUserInfo();
        const ids = this.getUserSingleValueSet(userInfo.workCenterIds);
        (userInfo.centerList || []).forEach((item) => {
          const id = item?.centerId ?? item?.id;
          if (id !== null && id !== undefined && id !== '') {
            ids.add(String(id));
          }
        });
        return ids;
      },
      getDispatchToolbarPermissionState(action) {
        if (this.readonlyMode) {
          return { allowed: false, message: '当前状态仅支持查看' };
        }
        if (!this.activeTask) {
          return { allowed: false, message: '请选择工序' };
        }
        const teamId = this.getDispatchTaskTeamId(this.activeTask);
        const userTeamIds = this.getCurrentUserTeamIdSet();
        if (!teamId || !userTeamIds.has(String(teamId))) {
          return {
            allowed: false,
            message: '当前登录人不属于该工序所属班组'
          };
        }
        const leaderId = this.getDispatchTaskLeaderId(this.activeTask);
        const userId = this.getCurrentUserInfo().userId;
        if (!leaderId || String(leaderId) !== String(userId)) {
          return { allowed: false, message: '当前班组不是对应组长' };
        }
        if (
          action === 'addStation' &&
          Number(this.activeTask.assignType ?? 1) === 3
        ) {
          return { allowed: false, message: '产线指派不能添加工位' };
        }
        return { allowed: true, message: '' };
      },
      isDispatchToolbarDisabled(action) {
        return !this.getDispatchToolbarPermissionState(action).allowed;
      },
      validateDispatchToolbarPermission(action) {
        const state = this.getDispatchToolbarPermissionState(action);
        if (!state.allowed) {
          this.$message.warning(state.message);
          return false;
        }
        return true;
      },
      getDispatchAssigneeId(row) {
        const rawId =
          row?.rawId ??
          row?.workstationId ??
          row?.factoryWorkstationId ??
          row?.stationId ??
          row?.assigneeId ??
          row?.id ??
          '';
        return String(rawId).replace(/^(workstation|person|team):/, '');
      },
      normalizeDispatchLongId(value) {
        if (value === null || value === undefined || value === '') {
          return '';
        }
        const text = String(value).trim();
        return /^\d+$/.test(text) ? text : '';
      },
      getDispatchDeviceId(row) {
        return (
          this.normalizeDispatchLongId(row?.assetId) ||
          this.normalizeDispatchLongId(row?.extInfo?.assetId) ||
          this.normalizeDispatchLongId(row?.deviceId) ||
          this.normalizeDispatchLongId(row?.extInfo?.deviceId) ||
          ''
        );
      },
      getAssignmentTypeCode(value) {
        if (value && typeof value === 'object') {
          return value.code ?? value.value ?? value.id ?? '';
        }
        return value ?? '';
      },
      getDispatchStatusText(status) {
        if (status === null || status === undefined || status === '') {
          return '';
        }
        if (typeof status === 'object') {
          return status.desc || status.name || status.label || '';
        }
        return String(status);
      },
      canResetDispatchRow(row) {
        if (!this.getDispatchToolbarPermissionState('reset').allowed) {
          return false;
        }
        const data = this.getDispatchActionRowData(row);
        if (
          data.status === null ||
          data.status === undefined ||
          data.status === ''
        ) {
          return false;
        }
        return String(this.getAssignmentTypeCode(data.status)) !== '1';
      },
      canDeleteAddedDispatchStation(row) {
        if (!row || String(row.isNew ?? '') !== '1') {
          return false;
        }
        const data = this.getDispatchActionRowData(row);
        const rawStatusCode = this.getAssignmentTypeCode(data?.status);
        const statusCode =
          rawStatusCode === null ||
          rawStatusCode === undefined ||
          rawStatusCode === ''
            ? NaN
            : Number(rawStatusCode);
        const statusText = this.getDispatchStatusText(data?.status);
        return (
          statusCode !== 0 &&
          statusCode !== 1 &&
          statusText !== '已保存' &&
          statusText !== '保存' &&
          statusText !== '已派单' &&
          statusText !== '派单'
        );
      },
      isDispatchRowAssigned(row) {
        const data =
          typeof this.getDispatchActionRowData === 'function'
            ? this.getDispatchActionRowData(row)
            : row || {};
        const statusCode = this.getAssignmentTypeCode(data?.status);
        if (String(statusCode) === '1') {
          return true;
        }
        const statusText = this.getDispatchStatusText(data?.status);
        return statusText === '已派单' || statusText === '派单';
      },
      isDispatchRowControlDisabled(row) {
        return this.readonlyMode || this.isDeviceSnapshotLocked(row);
      },
      getDispatchTableRowClassName({ row }) {
        if (this.isDeviceSnapshotLocked(row)) {
          return 'dispatch-row-device-locked';
        }
        return this.readonlyMode && this.isDispatchRowAssigned(row)
          ? 'dispatch-row-assigned'
          : '';
      },
      normalizeDispatchAssignment(item) {
        const assigneeId =
          item.assigneeId || item.workStationId || item.workstationId || '';
        return {
          quantity: item.quantity,
          weight: item.weight,
          executionStartTime: item.startTime || item.executionStartTime || '',
          executionEndTime: item.endTime || item.executionEndTime || '',
          changeId: item.id || item.changeId || '',
          status: item.status,
          teamTimeIds: item.teamTimeIds,
          assigneeId
        };
      },
      async loadDispatchAssignData() {
        if (!this.orderDispatchStyle || !this.activeTask || !this.currentPlan) {
          return;
        }
        const taskId = this.getDispatchSourceTaskId(this.activeTask);
        const workOrderId = this.getDispatchWorkOrderId();
        const teamId = this.getDispatchTeamId();
        if (!taskId || !workOrderId || !teamId) {
          return;
        }
        const groupKey = this.dispatchTaskGroupKey(this.activeTask);
        const params = {
          workOrderId,
          workCenterId: this.getDispatchWorkCenterId(),
          taskId,
          teamId
        };
        try {
          const res = await listAssign(params);
          const nextMap = Object.keys(this.dispatchAssignmentMap).reduce(
            (map, key) => {
              if (!key.startsWith(`${groupKey}::`)) {
                map[key] = this.dispatchAssignmentMap[key];
              }
              return map;
            },
            {}
          );
          (Array.isArray(res) ? res : []).forEach((assignRecord) => {
            (assignRecord.assignees || []).forEach((item) => {
              const typeCode = this.getAssignmentTypeCode(item.assigneeType);
              if (String(typeCode) !== '1') {
                return;
              }
              const assignment = this.normalizeDispatchAssignment(item);
              const key = `${groupKey}::workstation:${assignment.assigneeId}`;
              nextMap[key] = assignment;
            });
          });
          this.dispatchAssignmentMap = nextMap;
        } catch (e) {
          this.$message.warning(e.message || '获取派单数据失败');
        }
      },
      async handleDispatchRefresh() {
        if (this.dispatchRefreshLoading) {
          return;
        }
        this.dispatchRefreshLoading = true;
        try {
          await this.loadDispatchAssignData();
          this.$nextTick(() => {
            this.$refs.dispatchTable?.doLayout?.();
          });
        } finally {
          this.dispatchRefreshLoading = false;
        }
      },
      isEmptyDispatchQuantity(value) {
        if (value === null || value === undefined || value === '') {
          return true;
        }
        const text = String(value).trim();
        return text === '0' || /^0\.0*$/.test(text);
      },
      shouldClearDispatchQuantity(value) {
        if (value === null || value === undefined || value === '') {
          return true;
        }
        return false;
      },
      formatDispatchRowQuantity(value) {
        return this.isEmptyDispatchQuantity(value) ? '' : value;
      },
      updateDispatchRowDraft(row, patch) {
        const key = this.dispatchRowDraftKey(row);
        this.$set(this.dispatchRowDraftMap, key, {
          ...(this.dispatchRowDraftMap[key] || {}),
          ...patch
        });
      },
      dispatchRowDraftKey(row, task = this.activeTask) {
        return [
          this.getDispatchWorkOrderId() || this.currentPlan?.id || '',
          this.dispatchTaskGroupKey(task) || '',
          this.dispatchObjectRowKey(row)
        ].join('::');
      },
      getDispatchTaskQuantityTotal(row) {
        const groupKey = this.dispatchTaskGroupKey(row);
        if (
          this.orderDispatchStyle &&
          this.activeTask &&
          groupKey === this.dispatchTaskGroupKey(this.activeTask)
        ) {
          return this.dispatchObjectRows.reduce((total, item) => {
            const data = this.getDispatchActionRowData(item);
            return total + (this.parseDispatchQuantity(data.quantity) || 0);
          }, 0);
        }
        return (this.taskList || []).reduce((total, item) => {
          if (this.dispatchTaskGroupKey(item) !== groupKey) {
            return total;
          }
          return total + (this.parseDispatchQuantity(item.quantity) || 0);
        }, 0);
      },
      clearDispatchQuantity(row) {
        if (row) {
          this.$set(row, 'quantity', '');
        }
      },
      validateDispatchQuantity(row, showMessage = true) {
        if (!row) {
          return false;
        }
        if (
          row.quantity === null ||
          row.quantity === undefined ||
          row.quantity === ''
        ) {
          return true;
        }
        const quantity = this.parseDispatchQuantity(row.quantity);
        if (quantity === null) {
          this.clearDispatchQuantity(row);
          if (showMessage) {
            this.$message.warning('数量只能输入非0开头的整数或小数');
          }
          return false;
        }
        const planQuantity = this.dispatchPlanQuantityLimit;
        if (planQuantity !== null) {
          const total = this.getDispatchTaskQuantityTotal(row);
          if (total > planQuantity) {
            this.clearDispatchQuantity(row);
            if (showMessage) {
              this.$message.warning('工位数量合计不能大于计划数量');
            }
            return false;
          }
        }
        return true;
      },
      handleDispatchQuantityInput(row) {
        return this.validateDispatchQuantity(row);
      },
      getDispatchTaskRows() {
        const groupKey = this.dispatchTaskGroupKey(this.activeTask);
        return (this.taskList || []).filter(
          (item) => this.dispatchTaskGroupKey(item) === groupKey
        );
      },
      getDispatchRowTask(row) {
        return this.getDispatchTaskRows().find((item) =>
          this.optionMatchesDispatchObject(item.executionTeamId, row)
        );
      },
      getDispatchTaskRowsByKey(groupKey) {
        if (!groupKey) {
          return [];
        }
        return (this.taskList || []).filter(
          (item) => this.dispatchTaskGroupKey(item) === groupKey
        );
      },
      emitDispatchTaskListChange(eventName, row) {
        this.suppressTaskListAssignReloadUntil = Date.now() + 300;
        this.$emit(eventName, row);
      },
      clearDispatchSelectionsByTaskKey(groupKey) {
        if (!this.orderDispatchStyle || !groupKey) {
          return;
        }
        this.getDispatchTaskRowsByKey(groupKey).forEach((task) => {
          if (!task?.executionTeamId || this.isDispatchRowAssigned(task)) {
            return;
          }
          if (task._dispatchDraft) {
            this.emitDispatchTaskListChange('remove-task-row', task);
            return;
          }
          this.$set(task, 'executionTeamId', '');
          this.$set(task, 'rawExecutionTeamId', '');
          this.$set(task, 'executionObjectType', '');
          this.$set(task, 'executionObjectName', '');
          this.$set(task, 'executionTeamName', '');
          this.$set(task, 'executionTeamLeader', '');
          this.$set(task, 'executionTeamLeaderId', '');
        });
      },
      createDispatchTaskRow(row) {
        const source = this.activeTask || {};
        const next = {
          ...source,
          id: undefined,
          _dispatchDraft: true,
          quantity: '',
          executionStartTime: row.executionStartTime || '',
          executionEndTime: row.executionEndTime || '',
          _taskKey: `${this.dispatchTaskGroupKey(
            source
          )}-${this.dispatchObjectRowKey(row)}`
        };
        this.emitDispatchTaskListChange('add-task-row', next);
        return next;
      },
      clearDispatchObject(row, task = this.getDispatchRowTask(row)) {
        this.$delete(this.dispatchRowDraftMap, this.dispatchRowDraftKey(row));
        if (!task) {
          row.quantity = '';
          row.executionStartTime = '';
          row.executionEndTime = '';
          return;
        }
        row.quantity = '';
        row.executionStartTime = '';
        row.executionEndTime = '';
        if (task._dispatchDraft) {
          this.emitDispatchTaskListChange('remove-task-row', task);
          return;
        }
        this.$set(task, 'quantity', '');
        this.$set(task, 'executionStartTime', '');
        this.$set(task, 'executionEndTime', '');
        this.$set(task, 'executionTeamId', '');
        this.$set(task, 'rawExecutionTeamId', '');
        this.$set(task, 'executionObjectType', '');
        this.$set(task, 'executionObjectName', '');
        this.$set(task, 'executionTeamName', '');
        this.$set(task, 'executionTeamLeader', '');
        this.$set(task, 'executionTeamLeaderId', '');
      },
      clearDispatchTaskBinding(row, task = this.getDispatchRowTask(row)) {
        if (!task) {
          return;
        }
        if (task._dispatchDraft) {
          this.emitDispatchTaskListChange('remove-task-row', task);
          return;
        }
        this.$set(task, 'quantity', '');
        this.$set(task, 'executionTeamId', '');
        this.$set(task, 'rawExecutionTeamId', '');
        this.$set(task, 'executionObjectType', '');
        this.$set(task, 'executionObjectName', '');
        this.$set(task, 'executionTeamName', '');
        this.$set(task, 'executionTeamLeader', '');
        this.$set(task, 'executionTeamLeaderId', '');
      },
      clearDispatchQuantityInput(row) {
        this.clearDispatchObject(row);
        this.$nextTick(() => {
          row.quantity = '';
          this.$forceUpdate();
        });
      },
      ensureDispatchObjectSelected(row) {
        if (!this.activeTask || !row || this.isDeviceSnapshotLocked(row)) {
          return null;
        }
        const existed = this.getDispatchRowTask(row);
        const canUseActiveTask = !this.activeTask.executionTeamId;
        const task =
          existed ||
          (canUseActiveTask
            ? this.activeTask
            : this.createDispatchTaskRow(row));
        if (!this.optionMatchesDispatchObject(task.executionTeamId, row)) {
          this.selectDispatchObject(row, task);
        }
        return task;
      },
      handleDispatchRowQuantityInput(row, value) {
        if (this.isDispatchRowControlDisabled(row)) {
          return;
        }
        this.$set(row, 'quantity', value);
        this.updateDispatchRowDraft(row, { quantity: value });
        const quantity = this.parseDispatchQuantity(value);
        if (this.shouldClearDispatchQuantity(value)) {
          this.clearDispatchQuantityInput(row);
          return;
        }
        const text = String(value || '').trim();
        if (/^0\d+/.test(text)) {
          this.clearDispatchQuantityInput(row);
          return;
        }
        if (
          quantity === null &&
          (/^[1-9]\d*\.\d*$/.test(text) || /^0(\.\d*)?$/.test(text))
        ) {
          this.clearDispatchTaskBinding(row);
          return;
        }
        const task = this.ensureDispatchObjectSelected(row);
        if (!task) {
          return;
        }
        this.$set(task, 'quantity', value);
        const valid = this.handleDispatchQuantityInput(task);
        if (!valid) {
          this.updateDispatchRowDraft(row, { quantity: '' });
        }
        row.quantity = this.formatDispatchRowQuantity(task.quantity);
      },
      handleDispatchObjectCheckChange(row, checked) {
        if (this.isDispatchRowControlDisabled(row)) {
          return;
        }
        if (checked) {
          const task = this.ensureDispatchObjectSelected(row);
          if (task && row.quantity) {
            this.$set(task, 'quantity', row.quantity);
          }
          if (task) {
            this.$set(task, 'executionStartTime', row.executionStartTime || '');
            this.$set(task, 'executionEndTime', row.executionEndTime || '');
          }
          return;
        }
        const task = this.getDispatchRowTask(row);
        if (!task) {
          return;
        }
        this.updateDispatchRowDraft(row, {
          quantity: row.quantity,
          executionStartTime:
            row.executionStartTime || task.executionStartTime || '',
          executionEndTime: row.executionEndTime || task.executionEndTime || ''
        });
        if (task._dispatchDraft) {
          this.emitDispatchTaskListChange('remove-task-row', task);
          return;
        }
        this.$set(task, 'quantity', '');
        this.$set(task, 'executionTeamId', '');
        this.$set(task, 'rawExecutionTeamId', '');
        this.$set(task, 'executionObjectType', '');
        this.$set(task, 'executionObjectName', '');
        this.$set(task, 'executionTeamName', '');
        this.$set(task, 'executionTeamLeader', '');
        this.$set(task, 'executionTeamLeaderId', '');
      },
      clearDispatchStartTime(row) {
        if (row) {
          this.$set(row, 'executionStartTime', '');
          this.$forceUpdate();
        }
      },
      clearDispatchEndTime(row) {
        if (row) {
          this.$set(row, 'executionEndTime', '');
          this.$forceUpdate();
        }
      },
      validateDispatchStartTime(row, showMessage = true) {
        if (!row?.executionStartTime) {
          return true;
        }
        const startTime = this.parseDispatchTime(row.executionStartTime);
        const planStartTime = this.parseDispatchTime(
          this.getDispatchPlanStartTime()
        );
        if (
          startTime !== null &&
          planStartTime !== null &&
          startTime < planStartTime
        ) {
          this.clearDispatchStartTime(row);
          if (showMessage) {
            this.$message.warning('开始时间不能小于计划开始时间');
          }
          return false;
        }
        const planEndTime = this.parseDispatchTime(
          this.getDispatchPlanEndTime()
        );
        if (
          startTime !== null &&
          planEndTime !== null &&
          startTime > planEndTime
        ) {
          this.clearDispatchStartTime(row);
          if (showMessage) {
            this.$message.warning('开始时间不能大于计划结束时间');
          }
          return false;
        }
        return true;
      },
      validateDispatchEndTime(row, showMessage = true) {
        if (!row?.executionEndTime) {
          return true;
        }
        const endTime = this.parseDispatchTime(row.executionEndTime);
        const startTime = this.parseDispatchTime(row.executionStartTime);
        if (endTime !== null && startTime !== null && endTime < startTime) {
          this.clearDispatchEndTime(row);
          if (showMessage) {
            this.$message.warning('结束时间不能小于开始时间');
          }
          return false;
        }
        const planEndTime = this.parseDispatchTime(
          this.getDispatchPlanEndTime()
        );
        if (endTime !== null && planEndTime !== null && endTime > planEndTime) {
          this.clearDispatchEndTime(row);
          if (showMessage) {
            this.$message.warning('结束时间不能大于计划结束时间');
          }
          return false;
        }
        return true;
      },
      validateDispatchTime(row, showMessage = true) {
        return (
          this.validateDispatchStartTime(row, showMessage) &&
          this.validateDispatchEndTime(row, showMessage)
        );
      },
      handleDispatchStartTimeChange(row) {
        if (!this.validateDispatchStartTime(row)) {
          return;
        }
        const endTimeValid = this.validateDispatchEndTime(row);
        this.$emit('time-change', row, 'executionStartTime');
        if (!endTimeValid) {
          this.$emit('time-change', row, 'executionEndTime');
        }
      },
      handleDispatchEndTimeChange(row) {
        if (!this.validateDispatchEndTime(row)) {
          return;
        }
        this.$emit('time-change', row, 'executionEndTime');
      },
      handleDispatchRowStartTimeChange(row) {
        if (this.isDispatchRowControlDisabled(row)) {
          return;
        }
        const task = this.ensureDispatchObjectSelected(row);
        if (!task) {
          return;
        }
        this.$set(task, 'executionStartTime', row.executionStartTime || '');
        this.handleDispatchStartTimeChange(task);
        row.executionStartTime = task.executionStartTime || '';
        row.executionEndTime =
          task.executionEndTime || row.executionEndTime || '';
        this.updateDispatchRowDraft(row, {
          executionStartTime: row.executionStartTime,
          executionEndTime: row.executionEndTime
        });
      },
      handleDispatchRowEndTimeChange(row) {
        if (this.isDispatchRowControlDisabled(row)) {
          return;
        }
        const task = this.ensureDispatchObjectSelected(row);
        if (!task) {
          return;
        }
        this.$set(task, 'executionEndTime', row.executionEndTime || '');
        this.handleDispatchEndTimeChange(task);
        row.executionEndTime = task.executionEndTime || '';
        this.updateDispatchRowDraft(row, {
          executionStartTime: row.executionStartTime,
          executionEndTime: row.executionEndTime
        });
      },
      handleCurrentPageDispatchCheckChange(checked) {
        if (this.readonlyMode) {
          return;
        }
        this.currentPageSelectableDispatchRows.forEach((row) => {
          this.handleDispatchObjectCheckChange(row, checked);
        });
      },
      getDispatchActionRowData(row) {
        const task = this.getDispatchRowTask(row);
        const assignment = this.getDispatchAssignment(row);
        return {
          ...row,
          quantity: task?.quantity ?? row.quantity,
          executionStartTime:
            task?.executionStartTime ?? row.executionStartTime,
          executionEndTime: task?.executionEndTime ?? row.executionEndTime,
          changeId: assignment?.changeId ?? row.changeId,
          status: assignment?.status ?? row.status,
          teamTimeIds: task?.teamTimeIds ?? row.teamTimeIds,
          weight: task?.weight ?? row.weight
        };
      },
      isDispatchWithdrawable(row) {
        const statusCode = this.getAssignmentTypeCode(row?.status);
        return !!row?.changeId && !!statusCode;
      },
      async handleDispatchRowReset(row) {
        if (!this.validateDispatchToolbarPermission('reset')) {
          return;
        }
        const data = this.getDispatchActionRowData(row);
        if (!data.changeId) {
          this.$message.warning('只能对已撤回跟已保存的数据进行重置');
          return;
        }
        this.dispatchToolbarLoading = true;
        try {
          const res = await resetAssignee(data.changeId);
          if (res) {
            this.removeDispatchAssignment(row);
            this.clearDispatchObject(row);
            this.$message.success('操作成功');
            this.$emit('dispatch-action-success', {
              type: 'reset',
              task: this.activeTask
            });
          }
        } catch (e) {
          this.$message.error(e.message || '操作失败');
        } finally {
          this.dispatchToolbarLoading = false;
        }
      },
      handleDeleteAddedDispatchStation(row) {
        if (!this.canDeleteAddedDispatchStation(row)) {
          this.$message.warning(
            '只有通过添加工位新增且状态不是保存或派单的数据才能删除'
          );
          return;
        }
        const task = this.getDispatchRowTask(row);
        this.clearDispatchObject(row, task);
        this.$emit('remove-dispatch-station', {
          task: this.activeTask,
          row
        });
        this.$message.success('删除成功');
      },
      validateDispatchActionRows(rows, type) {
        if (!rows.length) {
          this.$message.warning('请最少选择一条数据');
          return false;
        }
        if (
          !this.getDispatchWorkOrderId() ||
          !this.getDispatchSourceTaskId(this.activeTask)
        ) {
          this.$message.warning('当前订单或工序信息不完整，无法操作');
          return false;
        }
        const invalidRequired = rows.some((row) => {
          const data = this.getDispatchActionRowData(row);
          return (
            !this.parseDispatchQuantity(data.quantity) ||
            !data.executionStartTime ||
            !data.executionEndTime
          );
        });
        if (invalidRequired) {
          this.$message.warning(
            '请将所选数据的 数量,开始时间,完成时间 填写完毕'
          );
          return false;
        }
        const invalidTime = rows.some((row) => {
          const data = this.getDispatchActionRowData(row);
          return !this.validateDispatchTime(data, false);
        });
        if (invalidTime) {
          this.$message.warning('请检查所选数据的开始时间和完成时间');
          return false;
        }
        if (
          type === 2 &&
          rows.some((row) => !this.isDispatchWithdrawable(row))
        ) {
          this.$message.warning('只有状态为派单的数据才能进行撤回');
          return false;
        }
        return true;
      },
      buildDispatchAssignees(rows) {
        const measuringUnit =
          this.currentPlan?.measuringUnit || this.currentPlan?.unit || '';
        return rows.map((row) => {
          const data = this.getDispatchActionRowData(row);
          const assigneeId = this.getDispatchAssigneeId(row);
          const assigneeName =
            row.displayName || String(row.name || '').replace(/^工位[:：]/, '');
          const assignee = {
            assigneeId,
            quantity: data.quantity,
            weight: data.weight || '',
            startTime: data.executionStartTime,
            endTime: data.executionEndTime,
            assigneeType: 1,
            assigneeName,
            measuringUnit,
            isNew: row.isNew ? row.isNew : '',
            deviceId: this.getDispatchDeviceId(row),
            deviceName:
              row.assetName || row.deviceName || row.extInfo?.assetName || '',
            workStationId: assigneeId,
            workStationName: assigneeName
          };
          if (data.teamTimeIds) {
            assignee.teamTimeIds = data.teamTimeIds;
          }
          return assignee;
        });
      },
      buildDispatchActionPayload(rows) {
        const row = this.activeTask || {};
        return {
          taskId: this.getDispatchSourceTaskId(row),
          taskName: row.taskName || row.name,
          taskCode: row.taskCode || row.code,
          teamName: this.getDispatchTeamName(),
          teamId: this.getDispatchTeamId(),
          workCenterId: this.getDispatchWorkCenterId(row),
          workCenterName: this.getDispatchWorkCenterName(row),
          workOrderId: this.getDispatchWorkOrderId(),
          dispatchMethod: 0,
          dispatchingMethod:
            this.currentPlan?.dispatchingMethod ??
            this.currentPlan?.taskAss ??
            0,
          assignType: 1,
          singleReport: this.reportTypeValue,
          dispatchType: this.reportTypeValue,
          assignees: this.buildDispatchAssignees(rows)
        };
      },
      validateFirstTaskConfirmReportType() {
        if (this.reportTypeValue === 0 || this.reportTypeValue === 1) {
          return true;
        }
        this.$message.warning('请先选择报工类型');
        return false;
      },
      async validateFirstTaskConfirmLeader(teamId) {
        try {
          const permissionRes = await parameterGetByCode({
            code: 'production_order_dispatch_permission'
          });
          if (String(permissionRes?.value || 0) !== '1') {
            return true;
          }
          const isTeamLeader = await getcheckLoginUserIsTeamLeader(teamId);
          if (!isTeamLeader) {
            this.$message.warning('当前班组不是对应组长');
            return false;
          }
          return true;
        } catch (e) {
          this.$message.error(e.message || '校验组长身份失败');
          return false;
        }
      },
      getFirstTaskAssignedRows() {
        return this.dispatchObjectRows.filter((row) =>
          this.isDispatchRowAssigned(row)
        );
      },
      buildFirstTaskConfirmPayload(assignedRows) {
        const row = this.activeTask || {};
        const assignType = Number(row.assignType ?? 1);
        const dispatchIds = assignedRows.map((item) =>
          this.getDispatchAssigneeId(item)
        );
        const payload = {
          assignType,
          singleReport: this.reportTypeValue,
          teamId: this.getDispatchTeamId(),
          dispatchingMethod:
            this.currentPlan?.dispatchingMethod ??
            this.currentPlan?.taskAss ??
            0,
          crewIds: [],
          workstationIds: [],
          factoryLineIds: [],
          id: this.getDispatchWorkOrderId() || this.currentPlan?.id || '',
          planStartTime: this.getDispatchPlanStartTime(),
          planEndTime: this.getDispatchPlanEndTime()
        };
        if (assignType === 2) {
          payload.crewIds = dispatchIds;
        } else if (assignType === 3) {
          payload.factoryLineIds = dispatchIds;
        } else {
          payload.workstationIds = dispatchIds;
        }
        return payload;
      },
      async handleFirstTaskConfirm() {
        if (!this.validateDispatchToolbarPermission('confirm')) {
          return;
        }
        if (!this.validateFirstTaskConfirmReportType()) {
          return;
        }
        const teamId = this.getDispatchTeamId();
        if (!teamId) {
          this.$message.warning('请先选择班组');
          return;
        }
        const workCenterId = this.getDispatchWorkCenterId(this.activeTask);
        const workOrderId =
          this.getDispatchWorkOrderId() || this.currentPlan?.id;
        if (!workCenterId || !workOrderId) {
          this.$message.warning('当前订单或工序信息不完整，无法操作');
          return;
        }
        if (!(await this.validateFirstTaskConfirmLeader(teamId))) {
          return;
        }
        const assignedRows = this.getFirstTaskAssignedRows();
        if (!assignedRows.length) {
          const taskName =
            this.activeTask?.taskName || this.activeTask?.name || '';
          this.$message.warning(`请您先进行首工序任务(${taskName}工序)派单`);
          return;
        }
        this.dispatchToolbarLoading = true;
        try {
          const checked = await checkAssignConfirm({
            teamId,
            workCenterId,
            workOrderId
          });
          if (!checked) {
            return;
          }
          const response = await releaseWorkOrder(
            this.buildFirstTaskConfirmPayload(assignedRows)
          );
          if (response) {
            this.$message.success('操作成功');
            await this.loadDispatchAssignData();
            this.$emit('dispatch-action-success', {
              type: 'confirm',
              task: this.activeTask
            });
          }
        } catch (e) {
          this.$message.error(e.message || '操作失败');
        } finally {
          this.dispatchToolbarLoading = false;
        }
      },
      async handleDispatchToolbarAction(type) {
        if (!this.validateDispatchToolbarPermission(type)) {
          return;
        }
        const rows = this.selectedDispatchObjectRows;
        if (!this.validateDispatchActionRows(rows, type)) {
          return;
        }
        const data =
          type === 2
            ? rows.map((row) => this.getDispatchActionRowData(row).changeId)
            : this.buildDispatchActionPayload(rows);
        const api =
          type === 1 ? taskAssignment : type === 2 ? taskRevoked : taskSave;
        this.dispatchToolbarLoading = true;
        try {
          await api(data);
          await this.loadDispatchAssignData();
          this.$message.success('操作成功');
          this.$emit('dispatch-action-success', {
            type,
            task: this.activeTask
          });
        } catch (e) {
          this.$message.error(e.message || '操作失败');
        } finally {
          this.dispatchToolbarLoading = false;
        }
      },
      handleDispatchAddStation() {
        if (!this.validateDispatchToolbarPermission('addStation')) {
          return;
        }
        if (!this.activeTask) {
          this.$message.warning('请选择工序');
          return;
        }
        const workCenterId = this.getDispatchWorkCenterId(this.activeTask);
        if (!workCenterId) {
          this.$message.warning('当前工序缺少工作中心，无法添加工位');
          return;
        }
        const index = this.dispatchTaskTabs.findIndex(
          (item) =>
            this.dispatchTaskGroupKey(item) ===
            this.dispatchTaskGroupKey(this.activeTask)
        );
        const existedStations = this.dispatchObjectRows.map((row) => ({
          ...row,
          id: this.getDispatchAssigneeId(row)
        }));
        this.$refs.chooseStationRef?.open(
          workCenterId,
          existedStations,
          index,
          2,
          this.getDispatchTeamId()
        );
      },
      handleChooseStationList(list, index) {
        const task = this.dispatchTaskTabs[index];
        const stations = (Array.isArray(list) ? list : []).map((item) => ({
          ...item,
          isNew: 1,
          assetCode: this.pickDispatchSingleValue([
            {
              source: item,
              keys: ['assetCode', 'deviceCode']
            },
            {
              source: item.extInfo,
              keys: ['assetCode', 'deviceCode']
            }
          ]),
          assetId:
            this.normalizeDispatchLongId(item.assetId) ||
            this.normalizeDispatchLongId(item.extInfo?.assetId) ||
            '',
          assetName: this.pickDispatchSingleValue([
            {
              source: item,
              keys: ['assetName', 'deviceName']
            },
            {
              source: item.extInfo,
              keys: ['assetName', 'deviceName']
            }
          ])
        }));
        this.$emit('add-dispatch-stations', {
          task,
          index,
          list: stations
        });
      },
      handleDispatchSubmit(row) {
        if (
          !this.validateDispatchQuantity(row) ||
          !this.validateDispatchTime(row)
        ) {
          return;
        }
        this.$emit('row-submit', row);
      },
      handleDispatchRowSubmit(row) {
        const task = this.ensureDispatchObjectSelected(row);
        if (!task) {
          return;
        }
        this.handleDispatchSubmit(task);
      },
      taskRowKey(row, index) {
        return String(row?._taskKey ?? row?.id ?? row?.taskId ?? index);
      },
      taskCodeDisplay(row) {
        return (
          row?.taskCode ||
          row?.code ||
          row?.sourceTaskCode ||
          row?.firstTaskCode ||
          '-'
        );
      },
      dispatchTeamDisplay(row = this.activeTask) {
        const plan = this.currentPlan || {};
        return (
          this.pickDispatchSingleValue([
            { source: row || {}, keys: DISPATCH_TEAM_NAME_KEYS },
            { source: plan, keys: DISPATCH_TEAM_NAME_KEYS }
          ]) || '所有班组'
        );
      },
      handleDispatchPageSizeChange(size) {
        this.dispatchPageSize = size;
        this.dispatchPage = 1;
      },
      handleDispatchPageChange(page) {
        this.dispatchPage = page;
      },
      homemadeOptions(row) {
        // 由父组件按模式决定选项来源：
        // - 工厂排程：直接返回班组选项。
        // - 班组排程：返回人员 + 当前工序可派工位。
        return this.getHomemadeOptions
          ? this.getHomemadeOptions(row)
          : this.teamOptions;
      },
      optionMatchesDispatchObject(optionId, row) {
        return (
          String(optionId || '') ===
          String(this.dispatchObjectRowKey(row) || '')
        );
      },
      isSelectedDispatchObject(row) {
        const task = this.getDispatchRowTask(row);
        return !!task;
      },
      selectDispatchObject(row, targetTask) {
        const task = targetTask || this.activeTask;
        if (!task || !row) {
          return;
        }
        this.$set(task, 'executionType', this.execType.HOMEMADE);
        this.$set(task, 'executionTeamId', this.dispatchObjectRowKey(row));
        this.$set(task, 'rawExecutionTeamId', row.rawId);
        this.$set(task, 'executionObjectType', row.objectType);
        this.$set(task, 'executionObjectName', row.name);
        this.$set(task, 'executionTeamName', row.name);
        this.$set(task, 'executionTeamLeader', row.leaderUserName || '');
        this.$set(task, 'executionTeamLeaderId', row.leaderUserId || '');
        this.$emit('execution-object-change', task);
      }
    }
  };
</script>

<style lang="scss" scoped>
  .config-panel {
    width: 100%;
    max-width: 100%;
    min-width: 0;
    box-sizing: border-box;
    border: 1px solid #d8e3ef;
    border-radius: 10px;
    padding: 8px 10px 10px;
    background: linear-gradient(180deg, #ffffff 0%, #fafdff 100%);
    margin-top: 10px;
  }

  .config-panel.factory-task-config-panel {
    margin-top: 0;
    padding: 6px 8px 8px;
  }

  .dispatch-config-panel {
    height: 100%;
    margin-top: 0;
    padding: 0;
    border: 0;
    border-radius: 10px;
    overflow: hidden;
  }

  .dispatch-style-panel {
    display: flex;
    flex-direction: column;
    height: 100%;
    width: 100%;
    min-width: 0;
    overflow: hidden;
    background: transparent;
  }

  .dispatch-config-panel ::v-deep .el-empty {
    height: 100%;
    min-height: 240px;
    padding: 24px 0;
    box-sizing: border-box;
    border-radius: 10px;
    background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
  }

  .dispatch-config-panel ::v-deep .el-empty__image {
    width: 118px;
  }

  .dispatch-config-panel ::v-deep .el-empty__description {
    margin-top: 10px;
  }

  .dispatch-report-type {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 8px;
    min-height: 38px;
    padding: 5px 12px;
    box-sizing: border-box;
    color: #606266;
    font-size: 14px;
    background: linear-gradient(180deg, #ffffff 0%, #f4f9fd 100%);
    border-bottom: 1px solid #e5edf6;
  }

  .dispatch-required {
    color: #f56c6c;
    font-size: 14px;
  }

  .dispatch-report-label {
    color: #606266;
    font-weight: 500;
  }

  .dispatch-plan-info {
    display: inline-flex;
    flex: 1 1 auto;
    align-items: center;
    justify-content: flex-end;
    gap: 10px;
    min-width: 0;
    color: #008000;
  }

  .dispatch-plan-item {
    display: inline-flex;
    align-items: baseline;
    gap: 4px;
    min-width: 0;
    padding: 3px 8px;
    border: 1px solid #c8e8cf;
    border-radius: 7px;
    background: linear-gradient(180deg, #f4fcf6 0%, #edf9f0 100%);
    white-space: nowrap;
  }

  .dispatch-plan-label {
    color: #008000;
    font-weight: 600;
  }

  .dispatch-plan-value {
    max-width: 280px;
    overflow: hidden;
    color: #008000;
    font-weight: 600;
    text-overflow: ellipsis;
  }

  .dispatch-process-tabs ::v-deep {
    flex: 0 0 auto;
    border: 0;
    box-shadow: none;

    .el-tabs__header {
      margin: 0;
      background: #f8fbff;
      border-bottom-color: #e5edf6;
    }

    .el-tabs__nav-scroll {
      padding: 0 10px;
    }

    .el-tabs__item {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      height: 34px;
      line-height: 1;
      min-width: 82px;
      margin-right: 6px;
      padding: 0 14px;
      border-radius: 8px 8px 0 0;
      font-size: 14px;
      font-weight: 500;
      color: #909399;
      text-align: center;
      transition: all 0.18s ease;
    }

    .el-tabs__item:not(.is-active):hover {
      color: #1683d8;
      background: rgba(31, 140, 235, 0.06);
    }

    .el-tabs__item.is-active {
      color: #0f75bd;
      background: #fff;
      font-weight: 600;
      box-shadow: inset 0 -2px 0 #16b8a6;
    }

    .el-tabs__content {
      display: none;
    }
  }

  .dispatch-process-meta {
    flex: 0 0 auto;
    position: relative;
    margin: 6px 12px;
    padding: 6px 10px 6px 14px;
    border: 1px solid #c8e8cf;
    border-radius: 9px;
    color: #008000;
    font-size: 14px;
    font-weight: 600;
    background: linear-gradient(90deg, #f2fbf4 0%, #fbfffb 100%);
  }

  .dispatch-process-meta::before {
    position: absolute;
    top: 8px;
    bottom: 8px;
    left: 0;
    width: 3px;
    border-radius: 0 3px 3px 0;
    background: #30b24a;
    content: '';
  }

  .dispatch-toolbar {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: 40px;
    padding: 5px 12px;
    box-sizing: border-box;
    border-top: 1px solid #eef3f8;
    border-bottom: 1px solid #e5edf6;
    background: linear-gradient(180deg, #ffffff 0%, #f6faff 100%);
  }

  .dispatch-toolbar-left,
  .dispatch-toolbar-icons,
  .dispatch-assign-mode {
    display: inline-flex;
    align-items: center;
    gap: 8px;
  }

  .dispatch-toolbar-left {
    flex: 0 0 auto;
    gap: 7px;
  }

  .dispatch-toolbar-left ::v-deep .el-button + .el-button {
    margin-left: 0;
  }

  .dispatch-toolbar ::v-deep .el-button {
    min-height: 28px;
    border-radius: 7px;
    font-weight: 600;
    transition: all 0.18s ease;
  }

  .dispatch-toolbar ::v-deep .el-button:not(.el-button--primary):hover {
    color: #1683d8;
    border-color: #bfdcf5;
    background: #f4f9ff;
  }

  .dispatch-toolbar-left ::v-deep .el-button--primary {
    border-color: #1f8ceb;
    background: linear-gradient(180deg, #2a9af0 0%, #1684df 100%);
    box-shadow: 0 3px 8px rgba(31, 140, 235, 0.2);
  }

  .dispatch-toolbar-left ::v-deep .el-button--success {
    border-color: #58c234;
    background: linear-gradient(180deg, #8edc66 0%, #5fc238 100%);
    box-shadow: 0 3px 8px rgba(95, 194, 56, 0.2);
  }

  .dispatch-toolbar-left ::v-deep .el-button.is-disabled,
  .dispatch-toolbar-left ::v-deep .el-button.is-disabled:hover,
  .dispatch-toolbar-left ::v-deep .el-button.is-disabled:focus,
  .dispatch-toolbar-left ::v-deep .el-button.is-disabled:active {
    color: #9aa6b2;
    border-color: #d8e1eb;
    background: #edf2f7;
    box-shadow: none;
    cursor: not-allowed;
  }

  .dispatch-assign-mode {
    flex: 1 1 auto;
    justify-content: center;
  }

  .dispatch-assign-label {
    color: #606266;
    font-size: 14px;
    font-weight: 600;
  }

  .dispatch-assign-mode ::v-deep .el-button--primary {
    box-shadow: 0 2px 6px rgba(31, 140, 235, 0.2);
  }

  .dispatch-toolbar-icons {
    flex: 0 0 auto;
  }

  .dispatch-table-wrap {
    flex: 1 1 auto;
    min-height: 0;
    padding: 0 8px;
    box-sizing: border-box;
    overflow: hidden;
    background: #fff;
  }

  .dispatch-table {
    height: 100%;
    border-color: #e5edf6;
    border-radius: 9px;
  }

  .dispatch-status-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 58px;
    height: 24px;
    padding: 0 10px;
    border: 1px solid #d8e1eb;
    border-radius: 999px;
    color: #606b78;
    background: #f8fafc;
    font-size: 13px;
    font-weight: 600;
    line-height: 22px;
    box-sizing: border-box;
  }

  .dispatch-status-badge.is-assigned {
    border-color: #8fd19e;
    color: #008000;
    background: #eefaf0;
    box-shadow: inset 0 0 0 1px rgba(48, 178, 74, 0.08);
  }

  .dispatch-empty-control {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 30px;
    width: 100%;
    color: #c0c4cc;
  }

  .dispatch-state-checkbox {
    cursor: pointer;
  }

  .dispatch-pagination {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    min-height: 42px;
    padding: 4px 10px;
    border-top: 1px solid #e5edf6;
    background: linear-gradient(180deg, #ffffff 0%, #f1f6fb 100%);
    box-shadow: 0 -3px 10px rgba(31, 45, 61, 0.04);
  }

  .panel-title {
    font-size: 14px;
    font-weight: 600;
    margin-bottom: 10px;
  }

  .panel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 5px;
    flex-wrap: wrap;
  }

  .factory-task-config-panel .panel-header {
    min-height: 28px;
    margin-bottom: 4px;
  }

  .panel-header .panel-title {
    margin-bottom: 0;
  }

  .panel-actions {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    flex: 0 0 auto;
  }

  .factory-task-config-panel .panel-actions ::v-deep .el-button {
    min-height: 26px;
    padding: 6px 10px;
  }

  .readonly-tip {
    color: #e6a23c;
    font-size: 12px;
  }

  .task-config-table-wrap,
  .last-end-over-plan-wrap,
  .end-over-delivery-wrap {
    width: 100%;
  }

  .task-config-table-wrap {
    min-height: 0;
    overflow: hidden;
  }

  .factory-task-config-panel .task-config-table-wrap {
    flex: 1 1 auto;
    height: 100%;
  }

  .last-end-over-plan-wrap ::v-deep .el-input__inner,
  .end-over-delivery-wrap ::v-deep .el-input__inner {
    color: #f56c6c;
    border-color: #f56c6c;
  }

  .last-end-over-plan-wrap ::v-deep .el-input__inner:focus,
  .end-over-delivery-wrap ::v-deep .el-input__inner:focus {
    border-color: #f56c6c;
  }

  .config-table ::v-deep {
    .dispatch-table.el-table {
      font-size: 14px;
    }

    td.config-table-index-col .cell {
      font-size: 14px !important;
    }

    .el-table td > .cell {
      font-size: 14px;
      text-align: center;
    }

    .el-table th > .cell,
    .el-table__body .cell {
      padding-left: 4px;
      padding-right: 4px;
    }

    td.task-name-cell .task-name-text {
      font-size: 14px;
      font-weight: 500;
      line-height: 1.4;
    }

    .config-table-control .el-input__inner {
      height: 28px;
      padding-left: 10px;
      padding-right: 10px;
      font-size: 13px;
      line-height: 28px;
    }

    .config-table-control .el-input__prefix,
    .config-table-control .el-input__suffix {
      top: 0;
      height: 100%;
      display: inline-flex;
      align-items: center;
    }

    .config-table-control .el-input__prefix {
      left: 8px;
    }

    .config-table-control .el-input__suffix {
      right: 6px;
    }

    .config-table-control .el-input__icon {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      height: 100%;
      line-height: 1;
    }

    .config-table-control .el-select__caret {
      line-height: 1;
    }

    .el-table td,
    .el-table th {
      padding-top: 4px;
      padding-bottom: 4px;
    }

    .el-table__row {
      height: 34px;
    }

    .el-table__body-wrapper {
      padding-bottom: 1px;
      box-sizing: border-box;
    }

    .el-table__body tr:last-child td {
      border-bottom: 1px solid #ebeef5;
    }

    .el-button--small {
      min-width: 36px;
      padding: 7px 0;
    }

    .config-table-control {
      width: 100% !important;
    }

    .config-table-control.el-date-editor {
      max-width: 100%;
    }

    .config-table-control.el-date-editor .el-input__inner {
      padding-left: 28px;
      padding-right: 20px;
    }

    .el-input-number .el-input__inner {
      text-align: left;
    }
  }

  .factory-task-config-panel .config-table ::v-deep {
    .el-table td,
    .el-table th {
      padding-top: 3px;
      padding-bottom: 3px;
    }

    .el-table__row {
      height: 32px;
    }

    .config-table-control .el-input__inner {
      height: 26px;
      line-height: 26px;
    }

    .config-table-control.el-select .el-input__inner {
      padding-right: 30px;
    }

    .config-table-control .el-input--prefix .el-input__inner {
      padding-left: 28px;
    }
  }

  .dispatch-table ::v-deep {
    font-size: 14px;

    .el-table th {
      background: linear-gradient(180deg, #f8fbff 0%, #eef5fb 100%);
      color: #253244;
      font-size: 14px;
      font-weight: 600;
      position: sticky;
      top: 0;
      z-index: 2;
    }

    .el-table td,
    .el-table th {
      border-bottom-color: #e9eff6;
    }

    .el-table__row:hover > td.el-table__cell {
      background: #f5faff;
    }

    .el-table__body tr.current-row > td.el-table__cell {
      background: #eaf5ff;
    }

    .el-table__body tr.dispatch-row-assigned > td.el-table__cell {
      position: relative;
      color: #52606d;
      background: #f4fbf5;
    }

    .el-table__body tr.dispatch-row-assigned:hover > td.el-table__cell {
      background: #e9f7ed;
    }

    .el-table__body tr.dispatch-row-assigned > td:first-child::before {
      position: absolute;
      top: 8px;
      bottom: 8px;
      left: 0;
      width: 3px;
      border-radius: 0 3px 3px 0;
      background: #30b24a;
      content: '';
    }

    .el-table__body tr.dispatch-row-device-locked > td.el-table__cell {
      color: #8a96a3;
      background: #f4f6f8;
      cursor: not-allowed;
    }

    .el-table__body tr.dispatch-row-device-locked:hover > td.el-table__cell {
      background: #eef2f6;
    }

    .el-table__body-wrapper::-webkit-scrollbar {
      width: 8px;
      height: 8px;
    }

    .el-table__body-wrapper::-webkit-scrollbar-thumb {
      border: 2px solid #fff;
      border-radius: 999px;
      background: #bdcad8;
    }

    .el-table__body-wrapper::-webkit-scrollbar-thumb:hover {
      background: #aeb9c8;
    }

    .el-table td,
    .el-table th {
      padding-top: 4px;
      padding-bottom: 4px;
    }

    .el-table__row {
      height: 36px;
    }

    .el-table__fixed-right {
      box-shadow: -4px 0 8px rgba(0, 0, 0, 0.04);
    }

    .el-table__body-wrapper {
      background: linear-gradient(
          90deg,
          rgba(31, 140, 235, 0.025) 1px,
          transparent 1px
        ),
        #fff;
      background-size: 48px 48px;
    }

    .config-table-control .el-input__inner {
      border-color: #dce7f2;
      background: #fbfdff;
    }

    .dispatch-row-assigned .config-table-control .el-input__inner,
    .dispatch-row-assigned
      .config-table-control.el-date-editor
      .el-input__inner {
      color: #5f6977;
      border-color: #b9d7bf;
      background: #eef6f0;
      box-shadow: inset 3px 0 0 #30b24a;
      -webkit-text-fill-color: #5f6977;
    }

    .dispatch-row-assigned .config-table-control .el-input__prefix,
    .dispatch-row-assigned .config-table-control .el-input__suffix {
      color: #6aa875;
    }

    .config-table-control .el-input__inner:focus {
      border-color: #1f8ceb;
      box-shadow: 0 0 0 2px rgba(31, 140, 235, 0.08);
    }
  }

  .dispatch-checkbox-wrap,
  .dispatch-workstation-name,
  .dispatch-disabled-field {
    display: inline-block;
    width: 100%;
  }

  .dispatch-workstation-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  @media (max-width: 576px) {
    .config-panel {
      padding: 8px;
    }

    .dispatch-config-panel {
      padding: 0;
    }

    .panel-header {
      align-items: flex-start;
    }

    .dispatch-toolbar {
      align-items: flex-start;
      flex-direction: column;
      padding: 12px;
    }

    .dispatch-report-type {
      align-items: flex-start;
      flex-wrap: wrap;
      padding: 10px 12px;
    }

    .dispatch-plan-info {
      flex-basis: 100%;
      justify-content: flex-start;
      gap: 10px;
    }

    .dispatch-assign-mode {
      justify-content: flex-start;
    }
  }
</style>

<style lang="scss">
  .dispatch-column-popover {
    padding: 12px 14px;
    border: 1px solid #e6edf5;
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(31, 45, 61, 0.12);

    .dispatch-column-settings__title {
      margin-bottom: 10px;
      color: #263445;
      font-size: 14px;
      font-weight: 600;
    }

    .dispatch-column-settings__list {
      display: grid;
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: 8px 10px;
    }

    .el-checkbox {
      margin-right: 0;
      color: #4b5563;
      line-height: 22px;
    }

    .el-checkbox__label {
      padding-left: 6px;
      font-size: 13px;
    }
  }

  .dispatch-workstation-tooltip {
    max-width: 260px;
    z-index: 4000 !important;
    line-height: 1.5;
    word-break: break-all;
  }

  .dispatch-device-lock-tooltip {
    min-width: 190px;
    max-width: 320px;
    z-index: 7000 !important;
    padding: 10px 14px 10px 18px;
    border: 1px solid #ff8a00;
    border-left: 4px solid #ff5f00;
    border-radius: 6px;
    color: #7a2d00 !important;
    background: linear-gradient(180deg, #fff7dc 0%, #ffe8bd 100%) !important;
    box-shadow: 0 10px 24px rgba(180, 83, 9, 0.28),
      inset 0 0 0 1px rgba(255, 255, 255, 0.65);
    font-size: 14px;
    font-weight: 700;
    line-height: 1.45;
  }

  .dispatch-device-lock-tooltip[x-placement^='top'] .popper__arrow {
    border-top-color: #ff9f1a !important;
  }

  .dispatch-device-lock-tooltip[x-placement^='top'] .popper__arrow::after {
    border-top-color: #fff3dc !important;
  }

  .dispatch-device-lock-tooltip[x-placement^='bottom'] .popper__arrow {
    border-bottom-color: #ff9f1a !important;
  }

  .dispatch-device-lock-tooltip[x-placement^='bottom'] .popper__arrow::after {
    border-bottom-color: #fff3dc !important;
  }

  .dispatch-device-lock-tooltip[x-placement^='left'] .popper__arrow {
    border-left-color: #ff9f1a !important;
  }

  .dispatch-device-lock-tooltip[x-placement^='left'] .popper__arrow::after {
    border-left-color: #fff3dc !important;
  }

  .dispatch-device-lock-tooltip[x-placement^='right'] .popper__arrow {
    border-right-color: #ff9f1a !important;
  }

  .dispatch-device-lock-tooltip[x-placement^='right'] .popper__arrow::after {
    border-right-color: #fff3dc !important;
  }
</style>
