<template>
  <basic-container class="business-crud-page">
    <avue-crud
      :option="option"
      :table-loading="loading"
      :data="data"
      v-model:page="page"
      v-model="form"
      ref="crud"
      :permission="permissionList"
      :before-open="beforeOpen"
      @row-save="rowSave"
      @row-update="rowUpdate"
      @row-del="rowDel"
      @search-change="searchChange"
      @search-reset="searchReset"
      @selection-change="selectionChange"
      @current-change="currentChange"
      @size-change="sizeChange"
      @refresh-change="refreshChange"
      @search-icon-change="handleSearchIconChange"
      @on-load="onLoad"
    >
      <template #menu-left>
        <el-button
          type="primary"
          :icon="actionButtonLikeSearch ? undefined : 'el-icon-plus'"
          v-if="canCreate"
          @click="$refs.crud.rowAdd()"
          >{{ config.createText || '新增' }}
        </el-button>
        <el-button
          type="primary"
          icon="el-icon-upload"
          plain
          v-if="
            (config.importUrl || config.enableTransportPlanImport) &&
            hasPermission(`${config.permission}_import`) &&
            !readonlyScope
          "
          @click="handleImport"
          >{{ config.importText || '批量导入' }}
        </el-button>
        <el-button
          type="primary"
          icon="el-icon-download"
          plain
          v-if="config.templateUrl && hasPermission(`${config.permission}_template`)"
          @click="handleTemplate"
          >下载模板
        </el-button>
        <el-button
          type="primary"
          icon="el-icon-download"
          plain
          v-if="config.exportUrl && hasPermission(`${config.permission}_export`)"
          @click="handleExport"
          >批量导出
        </el-button>
        <el-button
          type="success"
          icon="el-icon-check"
          plain
          v-if="canBatchComplete"
          @click="handleBatchComplete"
          >批量完成
        </el-button>
        <el-button
          type="primary"
          :plain="!actionButtonLikeSearch"
          v-if="canRoadLoading"
          @click="handleRoadLoading"
          >{{ config.roadLoadingText || '公路配载' }}
        </el-button>
        <el-button
          type="danger"
          icon="el-icon-delete"
          plain
          v-if="
            config.batchDelete !== false &&
            hasPermission(`${config.permission}_delete`) &&
            !readonlyScope
          "
          @click="handleDelete"
          >批量删除
        </el-button>
      </template>

      <template #status="{ row }">
        <el-tag :type="statusTagType(row.status)">
          {{ displayStatus(row, 'status') }}
        </el-tag>
      </template>

      <template #businessStatus="{ row }">
        <el-tag :type="statusTagType(row.businessStatus)">
          {{ displayStatus(row, 'businessStatus') }}
        </el-tag>
      </template>

      <template #approvalStatus="{ row }">
        <el-tag :type="statusTagType(row.approvalStatus)">
          {{ displayStatus(row, 'approvalStatus') }}
        </el-tag>
      </template>

      <template #basicInfoTitle-form>
        <div class="dialog-section-title">基本信息</div>
      </template>

      <template #templateInfoTitle-form>
        <div class="dialog-section-title">模板信息</div>
      </template>

      <template #contractCategory-label>合同类型</template>

      <template v-if="config.enableContractExpiryTags" #search-menu>
        <div v-show="searchExpanded" class="business-crud-page__contract-expiry-search">
          <span class="business-crud-page__contract-expiry-label">签约类型：</span>
          <el-select v-model="contractSignType" clearable placeholder="请选择">
            <el-option
              v-for="item in contractSignTypeOptions"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
          <div class="business-crud-page__contract-expiry-tags">
            <el-check-tag
              v-for="item in contractExpiryTagOptions"
              :key="item.value"
              :checked="contractExpiryScope === item.value"
              @change="handleContractExpiryScopeChange(item.value)"
            >
              {{ item.label }}（{{ contractExpiryStats[item.statKey] || 0 }}）
            </el-check-tag>
          </div>
        </div>
      </template>

      <template #projectName-form>
        <el-select
          v-model="selectedProjectId"
          class="business-crud-page__field"
          :placeholder="config.projectPlaceholder || '请选择项目名称'"
          filterable
          clearable
          :loading="projectLoading"
          :disabled="dialogReadonly"
          @visible-change="visible => visible && loadProjectOptions()"
          @change="handleProjectChange"
        >
          <el-option
            v-for="item in projectOptions"
            :key="item.id"
            :label="item.projectName"
            :value="item.id"
          />
        </el-select>
      </template>

      <template #contractName-form>
        <el-select
          v-if="contractSelectEnabled"
          v-model="form.contractId"
          class="business-crud-page__field"
          placeholder="请选择客户合同"
          filterable
          clearable
          :loading="contractLoading"
          :disabled="dialogReadonly || !form.projectId"
          @visible-change="visible => visible && loadContractOptionsForProject()"
          @change="handleContractChange"
        >
          <el-option
            v-for="item in contractOptions"
            :key="item.id"
            :label="item.contractName"
            :value="item.id"
          />
        </el-select>
        <el-input
          v-else
          v-model="form.contractName"
          class="business-crud-page__field"
          placeholder="请输入合同名称"
          clearable
          maxlength="100"
          show-word-limit
          :disabled="dialogReadonly"
        />
      </template>

      <template #planName-form>
        <el-select
          v-if="shippingPlanSelectEnabled"
          v-model="form.planName"
          class="business-crud-page__field"
          placeholder="请选择发货计划"
          filterable
          clearable
          :loading="shippingPlanLoading"
          :disabled="dialogReadonly"
          @visible-change="visible => visible && loadShippingPlanOptions()"
          @change="handleShippingPlanChange"
        >
          <el-option
            v-for="item in shippingPlanOptions"
            :key="item.id || item.planName"
            :label="item.planName"
            :value="item.planName"
          />
        </el-select>
        <el-input
          v-else
          v-model="form.planName"
          class="business-crud-page__field"
          placeholder="请输入计划名称"
          clearable
          maxlength="50"
          show-word-limit
          :disabled="dialogReadonly"
        />
      </template>

      <template #shippingInfoTitle-form>
        <div
          class="dialog-section-title dialog-section-title--action business-crud-page__shipping-title"
        >
          <span>收发货信息</span>
          <el-link
            v-if="!dialogReadonly"
            type="primary"
            :disabled="transportAddressSelectDisabled"
            @click="openTransportRouteDialog"
          >
            选择线路
          </el-link>
        </div>
      </template>

      <template #departureAddress-form>
        <div class="business-crud-page__route-address-row">
          <el-input
            v-model="form.departureName"
            readonly
            :placeholder="transportStationNamePlaceholder"
            :suffix-icon="transportStationMode ? Search : undefined"
            :disabled="transportAddressSelectDisabled"
            @click="openTransportPrimaryAddressPicker('departure')"
          />
          <el-input
            v-model="form.departureAddress"
            readonly
            :placeholder="transportAddressDetailPlaceholder"
            :suffix-icon="transportStationMode ? undefined : Location"
            :disabled="transportAddressSelectDisabled"
            @click="openTransportSecondaryAddressPicker('departure')"
          />
          <el-tooltip content="选择常用地址" placement="top">
            <el-button
              type="primary"
              :icon="List"
              link
              :disabled="transportAddressSelectDisabled"
              @click="openTransportAddressDialog('departure')"
            />
          </el-tooltip>
          <span class="business-crud-page__route-label">联系人</span>
          <el-input
            v-model="form.departureContact"
            placeholder="请输入"
            :disabled="dialogReadonly"
          />
          <span class="business-crud-page__route-label">联系方式</span>
          <el-input v-model="form.departurePhone" placeholder="请输入" :disabled="dialogReadonly" />
        </div>
      </template>

      <template #arrivalAddress-form>
        <div class="business-crud-page__route-address-row">
          <el-input
            v-model="form.arrivalName"
            readonly
            :placeholder="transportStationNamePlaceholder"
            :suffix-icon="transportStationMode ? Search : undefined"
            :disabled="transportAddressSelectDisabled"
            @click="openTransportPrimaryAddressPicker('arrival')"
          />
          <el-input
            v-model="form.arrivalAddress"
            readonly
            :placeholder="transportAddressDetailPlaceholder"
            :suffix-icon="transportStationMode ? undefined : Location"
            :disabled="transportAddressSelectDisabled"
            @click="openTransportSecondaryAddressPicker('arrival')"
          />
          <el-tooltip content="选择常用地址" placement="top">
            <el-button
              type="primary"
              :icon="List"
              link
              :disabled="transportAddressSelectDisabled"
              @click="openTransportAddressDialog('arrival')"
            />
          </el-tooltip>
          <span class="business-crud-page__route-label">联系人</span>
          <el-input v-model="form.arrivalContact" placeholder="请输入" :disabled="dialogReadonly" />
          <span class="business-crud-page__route-label">联系方式</span>
          <el-input v-model="form.arrivalPhone" placeholder="请输入" :disabled="dialogReadonly" />
        </div>
      </template>

      <template #taskInfoTitle-form>
        <div class="dialog-section-title">任务信息</div>
      </template>

      <template #taskInfoForm-form>
        <div class="business-crud-page__task">
          <div class="business-crud-page__task-toolbar">
            <el-segmented
              v-model="form.taskEntryMode"
              :options="taskEntryModeOptions"
              :disabled="dialogReadonly"
              @change="handleTaskEntryModeChange"
            />
          </div>
          <div v-if="isTaskFullMode" class="business-crud-page__task-full">
            <div class="business-crud-page__subsection">
              <div
                class="dialog-section-title dialog-section-title--action business-crud-page__goods-title"
              >
                <span>货物信息</span>
                <div v-if="!dialogReadonly" class="business-crud-page__section-actions">
                  <el-link type="primary" @click="cargoImportBox = true">导入货物</el-link>
                  <el-link type="primary" @click="openCommonCargoDialog">常用货物</el-link>
                </div>
              </div>
              <div class="business-crud-page__cargo-wrap">
                <el-table
                  :data="transportCargoRows"
                  border
                  class="business-crud-page__cargo-table business-crud-page__cargo-table--task"
                  empty-text="暂无货物信息"
                >
                  <el-table-column
                    type="index"
                    label="序号"
                    width="70"
                    align="center"
                    fixed="left"
                  />
                  <el-table-column min-width="240" align="center">
                    <template #header>
                      <span class="business-crud-page__required-column">货物名称</span>
                    </template>
                    <template #default="{ row }">
                      <el-select
                        v-model="row.cargoName"
                        placeholder="请输入"
                        filterable
                        allow-create
                        default-first-option
                        clearable
                        :disabled="dialogReadonly"
                        :loading="isTaskCargoOptionsLoading(row.cargoTypePath)"
                        @visible-change="
                          visible => visible && loadTaskCargoOptionsByPath(row.cargoTypePath)
                        "
                        @change="value => handleTaskCargoRowNameChange(row, value)"
                      >
                        <el-option
                          v-for="item in getTaskCargoRowOptions(row)"
                          :key="item.id || item.cargoCode || item.cargoName"
                          :label="formatBillingCargoTypeLabel(item)"
                          :value="formatBillingCargoTypeLabel(item)"
                        />
                      </el-select>
                    </template>
                  </el-table-column>
                  <el-table-column min-width="220" align="center">
                    <template #header>
                      <span class="business-crud-page__required-column">货物类型</span>
                    </template>
                    <template #default="{ row }">
                      <el-cascader
                        v-model="row.cargoTypePath"
                        :options="transportCargoTypeOptions"
                        :props="transportCargoTypeCascaderProps"
                        placeholder="从货物类型获取"
                        clearable
                        filterable
                        :disabled="dialogReadonly"
                        :filter-method="filterBillingCargoType"
                        @visible-change="visible => visible && ensureBillingCargoTypeOptions()"
                        @change="value => handleTransportCargoTypeChange(row, value)"
                      />
                    </template>
                  </el-table-column>
                  <el-table-column label="包装" min-width="130" align="center">
                    <template #default="{ row }">
                      <el-select v-model="row.packageType" clearable :disabled="dialogReadonly">
                        <el-option
                          v-for="item in packageOptions"
                          :key="item.value"
                          :label="item.label"
                          :value="item.value"
                        />
                      </el-select>
                    </template>
                  </el-table-column>
                  <el-table-column min-width="130" align="center">
                    <template #header>
                      <span class="business-crud-page__required-column">数量</span>
                    </template>
                    <template #default="{ row }">
                      <el-input
                        v-model="row.quantity"
                        placeholder="请输入"
                        :disabled="dialogReadonly"
                        @input="value => handleTransportCargoQuantityInput(row, value)"
                      />
                    </template>
                  </el-table-column>
                  <el-table-column min-width="140" align="center">
                    <template #header>
                      <span class="business-crud-page__required-column">数量单位</span>
                    </template>
                    <template #default="{ row }">
                      <el-select
                        v-model="row.quantityUnit"
                        clearable
                        :disabled="dialogReadonly"
                        @visible-change="visible => visible && loadTaskQuantityUnitOptions()"
                      >
                        <el-option
                          v-for="item in taskQuantityUnitOptions"
                          :key="item.value"
                          :label="item.label"
                          :value="item.value"
                        />
                      </el-select>
                    </template>
                  </el-table-column>
                  <el-table-column label="品牌" min-width="130" align="center">
                    <template #default="{ row }">
                      <el-input
                        v-model="row.brand"
                        placeholder="请输入"
                        :disabled="dialogReadonly"
                      />
                    </template>
                  </el-table-column>
                  <el-table-column label="规格" min-width="130" align="center">
                    <template #default="{ row }">
                      <el-input
                        v-model="row.specification"
                        placeholder="请输入"
                        :disabled="dialogReadonly"
                      />
                    </template>
                  </el-table-column>
                  <el-table-column label="型号" min-width="130" align="center">
                    <template #default="{ row }">
                      <el-input
                        v-model="row.model"
                        placeholder="请输入"
                        :disabled="dialogReadonly"
                      />
                    </template>
                  </el-table-column>
                  <el-table-column label="物料编码" min-width="150" align="center">
                    <template #default="{ row }">
                      <el-input
                        v-model="row.materialCode"
                        placeholder="请输入"
                        :disabled="dialogReadonly"
                      />
                    </template>
                  </el-table-column>
                  <el-table-column label="设备编码" min-width="150" align="center">
                    <template #default="{ row }">
                      <el-input
                        v-model="row.deviceCode"
                        placeholder="请输入"
                        :disabled="dialogReadonly"
                      />
                    </template>
                  </el-table-column>
                  <el-table-column label="备注" min-width="180" align="center">
                    <template #default="{ row }">
                      <el-input
                        v-model="row.remark"
                        placeholder="请输入"
                        :disabled="dialogReadonly"
                      />
                    </template>
                  </el-table-column>
                  <el-table-column label="操作" width="140" align="center" fixed="right">
                    <template #default="{ $index }">
                      <div class="business-crud-page__cargo-actions">
                        <el-link
                          v-if="!dialogReadonly && $index === transportCargoRows.length - 1"
                          type="primary"
                          @click="addTransportCargoRow($index)"
                        >
                  新增
                </el-link>
                        <el-link
                          v-if="!dialogReadonly"
                          type="primary"
                          @click="removeTransportCargoRow($index)"
                        >
                  删除
                </el-link>
                      </div>
                    </template>
                  </el-table-column>
                </el-table>
              </div>
            </div>
            <el-form
              :model="form"
              label-position="right"
              label-width="auto"
              class="business-crud-page__task-form business-crud-page__task-form--full"
            >
              <div class="business-crud-page__task-row">
                <el-form-item
                  label="承运类型"
                  required
                  class="business-crud-page__task-carrier-type business-crud-page__task-span-4"
                >
                  <el-radio-group
                    v-model="form.carrierType"
                    :disabled="dialogReadonly"
                    @change="handleTaskCarrierTypeChange"
                  >
                    <el-radio-button
                      v-for="item in taskCarrierTypes"
                      :key="item"
                      :label="item"
                      :value="item"
                    />
                  </el-radio-group>
                </el-form-item>
              </div>
              <div
                v-if="isCarrierMode"
                class="business-crud-page__subsection business-crud-page__carrier-section"
              >
                <div class="business-crud-page__subsection-head">
                  <span>承运信息</span>
                </div>
                <div class="business-crud-page__task-row">
                  <el-form-item label="承运商" required class="business-crud-page__task-span-1">
                    <el-select
                      v-model="form.carrierName"
                      placeholder="请输入"
                      filterable
                      clearable
                      :suffix-icon="Search"
                      :loading="taskCarrierLoading"
                      :disabled="dialogReadonly"
                      @visible-change="visible => visible && loadTaskCarrierOptions()"
                      @change="handleTaskCarrierChange"
                    >
                      <el-option
                        v-for="item in taskCarrierOptions"
                        :key="
                          item.id ||
                          item.customerName ||
                          item.carrierName ||
                          item.fullName ||
                          item.name ||
                          item.customerCode
                        "
                        :label="item.customerName || item.carrierName || item.fullName || item.name"
                        :value="item.customerName || item.carrierName || item.fullName || item.name"
                      />
                    </el-select>
                  </el-form-item>
                  <el-form-item label="司机" class="business-crud-page__task-span-1">
                    <el-select
                      v-model="form.driverName"
                      placeholder="请输入"
                      filterable
                      clearable
                      allow-create
                      default-first-option
                      :suffix-icon="Search"
                      :loading="taskDriverLoading"
                      :disabled="dialogReadonly"
                      @visible-change="visible => visible && loadTaskDriverOptions()"
                      @change="handleTaskDriverChange"
                    >
                      <el-option
                        v-for="item in taskDriverOptions"
                        :key="item.id || item.driverName || item.mobile"
                        :label="item.driverName || item.name"
                        :value="item.driverName || item.name"
                      />
                    </el-select>
                  </el-form-item>
                  <el-form-item label="手机号" class="business-crud-page__task-span-1">
                    <el-input
                      v-model="form.driverPhone"
                      placeholder="请输入"
                      clearable
                      :disabled="dialogReadonly"
                    />
                  </el-form-item>
                  <el-form-item label="车牌号" required class="business-crud-page__task-span-1">
                    <el-input
                      v-model="form.vehicleNo"
                      placeholder="请输入"
                      clearable
                      :disabled="dialogReadonly"
                    />
                  </el-form-item>
                </div>
                <div class="business-crud-page__task-row">
                  <el-form-item label="里程(km)" class="business-crud-page__task-span-1">
                    <el-input
                      v-model="form.mileage"
                      placeholder="请输入"
                      clearable
                      :disabled="dialogReadonly"
                      @input="value => handleTaskNumberInput('mileage', value)"
                    />
                  </el-form-item>
                  <el-form-item label="预计发货日期" class="business-crud-page__task-span-1">
                    <el-date-picker
                      v-model="form.estimatedStartTime"
                      type="date"
                      placeholder="默认填充当天"
                      format="YYYY-MM-DD"
                      value-format="YYYY-MM-DD"
                      :disabled="dialogReadonly"
                    />
                  </el-form-item>
                  <el-form-item label="预计完成日期" class="business-crud-page__task-span-1">
                    <el-date-picker
                      v-model="form.estimatedEndTime"
                      type="date"
                      placeholder="请输入"
                      format="YYYY-MM-DD"
                      value-format="YYYY-MM-DD"
                      :disabled="dialogReadonly"
                    />
                  </el-form-item>
                </div>
                <div class="business-crud-page__task-row">
                  <el-form-item
                    label="备注"
                    class="business-crud-page__task-note business-crud-page__task-span-4"
                  >
                    <el-input
                      v-model="form.taskRemark"
                      placeholder="请输入，200字以内"
                      clearable
                      maxlength="200"
                      show-word-limit
                      :disabled="dialogReadonly"
                    />
                  </el-form-item>
                </div>
              </div>
              <template v-else>
                <div class="business-crud-page__task-row">
                  <el-form-item label="司机" class="business-crud-page__task-span-1">
                    <el-select
                      v-model="form.driverName"
                      placeholder="请输入"
                      filterable
                      clearable
                      allow-create
                      default-first-option
                      :suffix-icon="Search"
                      :loading="taskDriverLoading"
                      :disabled="dialogReadonly"
                      @visible-change="visible => visible && loadTaskDriverOptions()"
                      @change="handleTaskDriverChange"
                    >
                      <el-option
                        v-for="item in taskDriverOptions"
                        :key="item.id || item.driverName || item.mobile"
                        :label="item.driverName || item.name"
                        :value="item.driverName || item.name"
                      />
                    </el-select>
                  </el-form-item>
                  <el-form-item label="司机手机号" required class="business-crud-page__task-span-1">
                    <el-input
                      v-model="form.driverPhone"
                      placeholder="请输入"
                      clearable
                      :disabled="dialogReadonly"
                    />
                  </el-form-item>
                  <el-form-item label="车牌号" required class="business-crud-page__task-span-1">
                    <el-input
                      v-model="form.vehicleNo"
                      placeholder="请输入"
                      clearable
                      :disabled="dialogReadonly"
                    />
                  </el-form-item>
                  <el-form-item label="挂车车牌号" class="business-crud-page__task-span-1">
                    <el-input
                      v-model="form.trailerVehicleNo"
                      placeholder="请输入"
                      clearable
                      :disabled="dialogReadonly"
                    />
                  </el-form-item>
                </div>
                <div class="business-crud-page__task-row">
                  <el-form-item label="押运人" class="business-crud-page__task-span-1">
                    <el-input
                      v-model="form.escortName"
                      placeholder="请输入"
                      clearable
                      :disabled="dialogReadonly"
                    />
                  </el-form-item>
                  <el-form-item label="押运人手机号" class="business-crud-page__task-span-1">
                    <el-input
                      v-model="form.escortPhone"
                      placeholder="请输入"
                      clearable
                      :disabled="dialogReadonly"
                    />
                  </el-form-item>
                  <el-form-item label="里程(km)" class="business-crud-page__task-span-1">
                    <el-input
                      v-model="form.mileage"
                      placeholder="请输入"
                      clearable
                      :disabled="dialogReadonly"
                      @input="value => handleTaskNumberInput('mileage', value)"
                    />
                  </el-form-item>
                </div>
                <div class="business-crud-page__task-row">
                  <el-form-item label="预计发货日期" class="business-crud-page__task-span-1">
                    <el-date-picker
                      v-model="form.estimatedStartTime"
                      type="date"
                      placeholder="默认填充当天"
                      format="YYYY-MM-DD"
                      value-format="YYYY-MM-DD"
                      :disabled="dialogReadonly"
                    />
                  </el-form-item>
                  <el-form-item label="预计完成日期" class="business-crud-page__task-span-1">
                    <el-date-picker
                      v-model="form.estimatedEndTime"
                      type="date"
                      placeholder="请输入"
                      format="YYYY-MM-DD"
                      value-format="YYYY-MM-DD"
                      :disabled="dialogReadonly"
                    />
                  </el-form-item>
                </div>
                <div class="business-crud-page__task-row">
                  <el-form-item
                    label="备注"
                    class="business-crud-page__task-note business-crud-page__task-span-4"
                  >
                    <el-input
                      v-model="form.taskRemark"
                      placeholder="请输入，200字以内"
                      clearable
                      maxlength="200"
                      show-word-limit
                      :disabled="dialogReadonly"
                    />
                  </el-form-item>
                </div>
              </template>
            </el-form>
            <div class="business-crud-page__subsection">
              <div class="business-crud-page__subsection-head">
                <span>运费信息</span>
              </div>
              <el-form
                :model="form"
                label-position="right"
                label-width="auto"
                class="business-crud-page__freight-form"
              >
                <el-form-item label="单价">
                  <el-input
                    v-model="form.unitPrice"
                    placeholder="请输入"
                    clearable
                    :disabled="dialogReadonly"
                    @input="value => handleTaskNumberInput('unitPrice', value)"
                  >
                    <template #append>
                      <el-select
                        v-model="form.priceUnit"
                        class="business-crud-page__append-select"
                        placeholder="元/吨"
                        :disabled="dialogReadonly"
                        @visible-change="visible => visible && loadTaskFeeUnitOptions()"
                      >
                        <el-option
                          v-for="item in taskFeeUnitOptions"
                          :key="item.value"
                          :label="item.label"
                          :value="item.value"
                        />
                      </el-select>
                    </template>
                  </el-input>
                </el-form-item>
                <el-form-item label="数量合计">
                  <el-input
                    :model-value="taskCargoQuantityTotal"
                    disabled
                    placeholder="自动汇总货物数量"
                  >
                    <template #append>
                      <el-select
                        v-model="form.quantityUnit"
                        class="business-crud-page__append-select"
                        disabled
                        placeholder="吨"
                      >
                        <el-option
                          v-for="item in taskQuantityUnitOptions"
                          :key="item.value"
                          :label="item.label"
                          :value="item.value"
                        />
                      </el-select>
                    </template>
                  </el-input>
                </el-form-item>
                <el-form-item label="运费">
                  <el-input :model-value="taskFreightAmount" disabled placeholder="自动计算">
                    <template #append>元</template>
                  </el-input>
                </el-form-item>
                <el-form-item label="其他运费合计">
                  <el-input
                    v-model="form.otherFeeTotal"
                    placeholder="请输入"
                    clearable
                    :disabled="dialogReadonly"
                    @input="value => handleTaskNumberInput('otherFeeTotal', value)"
                  >
                    <template #append>元</template>
                  </el-input>
                </el-form-item>
              </el-form>
            </div>
          </div>
          <el-form
            v-else
            :model="form"
            label-position="right"
            label-width="auto"
            class="business-crud-page__task-form"
          >
            <el-form-item
              label="承运类型"
              required
              class="business-crud-page__task-carrier-type business-crud-page__task-span-4"
            >
              <el-radio-group
                v-model="form.carrierType"
                :disabled="dialogReadonly"
                @change="handleTaskCarrierTypeChange"
              >
                <el-radio-button
                  v-for="item in taskCarrierTypes"
                  :key="item"
                  :label="item"
                  :value="item"
                />
              </el-radio-group>
            </el-form-item>
            <el-form-item v-if="isCarrierMode" label="承运商" required>
              <el-select
                v-model="form.carrierName"
                placeholder="请选择承运商"
                filterable
                clearable
                :loading="taskCarrierLoading"
                :disabled="dialogReadonly || form.carrierType !== '承运商'"
                @visible-change="visible => visible && loadTaskCarrierOptions()"
                @change="handleTaskCarrierChange"
              >
                <el-option
                  v-for="item in taskCarrierOptions"
                  :key="
                    item.id ||
                    item.customerName ||
                    item.carrierName ||
                    item.fullName ||
                    item.name ||
                    item.customerCode
                  "
                  :label="item.customerName || item.carrierName || item.fullName || item.name"
                  :value="item.customerName || item.carrierName || item.fullName || item.name"
                />
              </el-select>
            </el-form-item>
            <el-form-item label="司机">
              <el-select
                v-model="form.driverName"
                placeholder="请选择或输入司机"
                filterable
                clearable
                allow-create
                default-first-option
                :loading="taskDriverLoading"
                :disabled="dialogReadonly"
                @visible-change="visible => visible && loadTaskDriverOptions()"
                @change="handleTaskDriverChange"
              >
                <el-option
                  v-for="item in taskDriverOptions"
                  :key="item.id || item.driverName || item.mobile"
                  :label="item.driverName || item.name"
                  :value="item.driverName || item.name"
                />
              </el-select>
            </el-form-item>
            <el-form-item label="手机号" :required="form.carrierType === '自运'">
              <el-input
                v-model="form.driverPhone"
                placeholder="请输入手机号"
                clearable
                :disabled="dialogReadonly"
              />
            </el-form-item>
            <el-form-item label="车牌号" required>
              <el-input
                v-model="form.vehicleNo"
                placeholder="请输入车牌号"
                clearable
                :disabled="dialogReadonly"
              />
            </el-form-item>
            <el-form-item label="货物类型" required>
              <el-cascader
                v-model="form.cargoTypePath"
                :options="transportCargoTypeOptions"
                :props="transportCargoTypeCascaderProps"
                placeholder="请选择到二级"
                clearable
                filterable
                :disabled="dialogReadonly"
                :filter-method="filterBillingCargoType"
                @visible-change="visible => visible && ensureBillingCargoTypeOptions()"
                @change="handleTaskCargoTypeChange"
              />
            </el-form-item>
            <el-form-item label="货物名称">
              <el-select
                v-model="form.cargoName"
                placeholder="请选择货物"
                filterable
                clearable
                :disabled="dialogReadonly || !form.cargoType"
                :loading="taskCargoLoading"
                @visible-change="visible => visible && loadTaskCargoOptionsForForm()"
                @change="handleTaskCargoChange"
              >
                <el-option
                  v-for="item in taskCargoOptions"
                  :key="item.id || item.cargoCode || item.cargoName"
                  :label="formatBillingCargoTypeLabel(item)"
                  :value="formatBillingCargoTypeLabel(item)"
                />
              </el-select>
            </el-form-item>
            <el-form-item label="规格">
              <el-input
                v-model="form.specification"
                placeholder="选择货物自动回填"
                clearable
                :disabled="dialogReadonly"
              />
            </el-form-item>
            <el-form-item label="型号">
              <el-input
                v-model="form.model"
                placeholder="选择货物自动回填"
                clearable
                :disabled="dialogReadonly"
              />
            </el-form-item>
            <el-form-item label="数量">
              <el-input
                v-model="form.quantity"
                placeholder="请输入数量"
                clearable
                :disabled="dialogReadonly"
                @input="value => handleTaskNumberInput('quantity', value)"
              >
                <template #suffix>
                  <el-select
                    v-model="form.quantityUnit"
                    class="business-crud-page__suffix-select"
                    placeholder="单位"
                    :disabled="dialogReadonly"
                    @visible-change="visible => visible && loadTaskQuantityUnitOptions()"
                  >
                    <el-option
                      v-for="item in taskQuantityUnitOptions"
                      :key="item.value"
                      :label="item.label"
                      :value="item.value"
                    />
                  </el-select>
                </template>
              </el-input>
            </el-form-item>
            <el-form-item label="里程（公里）">
              <el-input
                v-model="form.mileage"
                placeholder="请输入里程"
                clearable
                :disabled="dialogReadonly"
                @input="value => handleTaskNumberInput('mileage', value)"
              />
            </el-form-item>
            <el-form-item label="预计发货日期">
              <el-date-picker
                v-model="form.estimatedStartTime"
                type="date"
                placeholder="请选择预计发货日期"
                format="YYYY-MM-DD"
                value-format="YYYY-MM-DD"
                :disabled="dialogReadonly"
              />
            </el-form-item>
            <el-form-item label="预计完成日期">
              <el-date-picker
                v-model="form.estimatedEndTime"
                type="date"
                placeholder="请选择预计完成日期"
                format="YYYY-MM-DD"
                value-format="YYYY-MM-DD"
                :disabled="dialogReadonly"
              />
            </el-form-item>
            <el-form-item label="单价">
              <el-input
                v-model="form.unitPrice"
                placeholder="请输入单价"
                clearable
                :disabled="dialogReadonly"
                @input="value => handleTaskNumberInput('unitPrice', value)"
              >
                <template #suffix>
                  <el-select
                    v-model="form.priceUnit"
                    class="business-crud-page__suffix-select business-crud-page__suffix-select--wide"
                    placeholder="单位"
                    :disabled="dialogReadonly"
                    @visible-change="visible => visible && loadTaskFeeUnitOptions()"
                  >
                    <el-option
                      v-for="item in taskFeeUnitOptions"
                      :key="item.value"
                      :label="item.label"
                      :value="item.value"
                    />
                  </el-select>
                </template>
              </el-input>
            </el-form-item>
            <el-form-item label="备注" class="business-crud-page__task-remark">
              <el-input
                v-model="form.taskRemark"
                type="textarea"
                :rows="2"
                maxlength="200"
                show-word-limit
                placeholder="请输入备注，200字以内"
                :disabled="dialogReadonly"
              />
            </el-form-item>
          </el-form>
        </div>
      </template>

      <template #goodsInfoTitle-form>
        <div
          v-if="config.enableTransportPlanForm"
          class="dialog-section-title dialog-section-title--action business-crud-page__goods-title"
        >
          <span>货物信息</span>
          <div v-if="!dialogReadonly" class="business-crud-page__section-actions">
            <el-link type="primary" @click="cargoImportBox = true">导入货物</el-link>
            <el-link type="primary" @click="openCommonCargoDialog">常用货物</el-link>
          </div>
        </div>
      </template>

      <template #goodsJson-form>
        <div v-if="config.enableTransportPlanForm" class="business-crud-page__cargo-wrap">
          <el-table
            :data="transportCargoRows"
            border
            class="business-crud-page__cargo-table"
            empty-text="暂无货物信息"
          >
            <el-table-column type="index" label="序号" width="70" align="center" fixed="left" />
            <el-table-column label="货物名称" min-width="150" align="center">
              <template #default="{ row }">
                <el-input v-model="row.cargoName" placeholder="请输入" :disabled="dialogReadonly" />
              </template>
            </el-table-column>
            <el-table-column min-width="220" align="center">
              <template #header>
                <span class="business-crud-page__required-column">货物类型</span>
              </template>
              <template #default="{ row }">
                <el-cascader
                  v-model="row.cargoTypePath"
                  :options="transportCargoTypeOptions"
                  :props="transportCargoTypeCascaderProps"
                  :placeholder="row.cargoType || '请选择'"
                  clearable
                  filterable
                  :disabled="dialogReadonly"
                  :filter-method="filterBillingCargoType"
                  @visible-change="visible => visible && ensureBillingCargoTypeOptions()"
                  @change="value => handleTransportCargoTypeChange(row, value)"
                />
              </template>
            </el-table-column>
            <el-table-column label="数量" min-width="120" align="center">
              <template #default="{ row }">
                <el-input
                  v-model="row.quantity"
                  placeholder="请输入"
                  :disabled="dialogReadonly"
                  @input="value => handleTransportCargoQuantityInput(row, value)"
                />
              </template>
            </el-table-column>
            <el-table-column label="数量单位" min-width="120" align="center">
              <template #default="{ row }">
                <el-select v-model="row.quantityUnit" clearable :disabled="dialogReadonly">
                  <el-option
                    v-for="item in quantityUnitOptions"
                    :key="item"
                    :label="item"
                    :value="item"
                  />
                </el-select>
              </template>
            </el-table-column>
            <el-table-column label="包装" min-width="120" align="center">
              <template #default="{ row }">
                <el-select v-model="row.packageType" clearable :disabled="dialogReadonly">
                  <el-option
                    v-for="item in packageOptions"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  />
                </el-select>
              </template>
            </el-table-column>
            <el-table-column label="品牌" min-width="130" align="center">
              <template #default="{ row }">
                <el-input v-model="row.brand" placeholder="请输入" :disabled="dialogReadonly" />
              </template>
            </el-table-column>
            <el-table-column label="规格" min-width="130" align="center">
              <template #default="{ row }">
                <el-input
                  v-model="row.specification"
                  placeholder="请输入"
                  :disabled="dialogReadonly"
                />
              </template>
            </el-table-column>
            <el-table-column label="型号" min-width="130" align="center">
              <template #default="{ row }">
                <el-input v-model="row.model" placeholder="请输入" :disabled="dialogReadonly" />
              </template>
            </el-table-column>
            <el-table-column label="物料编码" min-width="150" align="center">
              <template #default="{ row }">
                <el-input
                  v-model="row.materialCode"
                  placeholder="请输入"
                  :disabled="dialogReadonly"
                />
              </template>
            </el-table-column>
            <el-table-column label="设备编码" min-width="150" align="center">
              <template #default="{ row }">
                <el-input
                  v-model="row.deviceCode"
                  placeholder="请输入"
                  :disabled="dialogReadonly"
                />
              </template>
            </el-table-column>
            <el-table-column label="备注" min-width="180" align="center">
              <template #default="{ row }">
                <el-input v-model="row.remark" placeholder="请输入" :disabled="dialogReadonly" />
              </template>
            </el-table-column>
            <el-table-column label="操作" width="140" align="center" fixed="right">
              <template #default="{ $index }">
                <div class="business-crud-page__cargo-actions">
                  <el-link
                    v-if="!dialogReadonly"
                    type="primary"
                    @click="addTransportCargoRow($index)"
                  >
                  新增
                </el-link>
                  <el-link
                    v-if="!dialogReadonly"
                    type="primary"
                    @click="removeTransportCargoRow($index)"
                  >
                  删除
                </el-link>
                </div>
              </template>
            </el-table-column>
          </el-table>
          <div class="business-crud-page__cargo-total">
            运输数量合计：{{ transportCargoQuantityTotal }}
          </div>
        </div>
      </template>

      <template #organizationName-form>
        <el-cascader
          v-model="selectedOrganizationId"
          class="business-crud-page__field"
          :placeholder="config.organizationPlaceholder || '请选择所属组织'"
          :options="organizationOptions"
          :props="organizationCascaderProps"
          clearable
          filterable
          :show-all-levels="false"
          :disabled="dialogReadonly"
          @visible-change="visible => visible && loadOrganizationOptions()"
          @change="handleOrganizationChange"
        />
      </template>

      <template #applyLimit-form>
        <el-input
          v-model="form.applyLimit"
          class="business-crud-page__field"
          placeholder="请输入申请临时额度"
          :disabled="dialogReadonly"
          @input="value => handleAmountInput('applyLimit', value)"
        />
      </template>

      <template #contractPeriod-form>
        <div class="business-crud-page__date-range">
          <el-date-picker
            v-model="form.startDate"
            class="business-crud-page__date"
            type="date"
            placeholder="YYYY-MM-DD"
            format="YYYY-MM-DD"
            value-format="YYYY-MM-DD"
            :disabled="dialogReadonly"
            @change="handleContractDateChange"
          />
          <span class="business-crud-page__date-separator">至</span>
          <el-date-picker
            v-model="form.endDate"
            class="business-crud-page__date"
            type="date"
            placeholder="YYYY-MM-DD"
            format="YYYY-MM-DD"
            value-format="YYYY-MM-DD"
            :disabled="dialogReadonly"
            @change="handleContractDateChange"
          />
        </div>
      </template>

      <template #copyCount-form>
        <div class="business-crud-page__inline-number">
          <el-input
            v-model="form.copyCount"
            placeholder="请输入"
            :disabled="dialogReadonly"
            @input="value => handleIntegerInput('copyCount', value)"
          />
          <span>份</span>
        </div>
      </template>

      <template #paymentDays-form>
        <div class="business-crud-page__inline-number">
          <el-input
            v-model="form.paymentDays"
            placeholder="请输入"
            :disabled="dialogReadonly"
            @input="value => handleIntegerInput('paymentDays', value)"
          />
          <span>天</span>
        </div>
      </template>

      <template #contractFileTitle-form>
        <div class="dialog-section-title">合同文件</div>
      </template>

      <template #contractFileJson-form>
        <div class="business-crud-page__attachment">
          <div class="business-crud-page__attachment-head">
            <vehicle-attachment-upload
              v-model="contractFileRows"
              :readonly="dialogReadonly"
              :file-types="attachmentFileTypes"
              :max-size="500"
              :show-tip="false"
              :show-file-list="false"
              button-text="上传附件"
              @change="handleContractFileChange"
            />
          </div>
          <el-table :data="contractFileRows" border class="business-crud-page__attachment-table">
            <el-table-column type="index" label="序号" width="70" align="center" />
            <el-table-column label="文件名" min-width="240" align="center" show-overflow-tooltip>
              <template #default="{ row }">{{ row.originalName || row.name }}</template>
            </el-table-column>
            <el-table-column label="文件大小" width="120" align="center">
              <template #default="{ row }">{{ formatFileSize(row.size) }}</template>
            </el-table-column>
            <el-table-column prop="uploadUserName" label="上传人" width="140" align="center" />
            <el-table-column prop="uploadTime" label="上传时间" width="170" align="center"  sortable/>
            <el-table-column label="操作" width="120" align="center">
              <template #default="{ row, $index }">
                <el-link
                  v-if="!dialogReadonly"
                  type="primary"
                  @click="removeContractFile($index)"
                >
                  删除
                </el-link>
                <el-link v-else type="primary" @click="downloadAttachment(row)">
                  下载
                </el-link>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </template>

      <template #billingInfoTitle-form>
        <div class="dialog-section-title">计费信息</div>
      </template>

      <template #billingPlanJson-form>
        <div class="business-crud-page__billing">
          <div class="business-crud-page__billing-head">
            <div class="business-crud-page__billing-switch">
              <span>自动生成费用明细</span>
              <el-radio-group v-model="form.billingEnabled" :disabled="dialogReadonly">
                <el-radio :label="1">开启</el-radio>
                <el-radio :label="0">关闭</el-radio>
              </el-radio-group>
            </div>
            <el-button v-if="!dialogReadonly" type="primary" @click="openBillingPlan()">
              添加
            </el-button>
          </div>
          <el-table :data="billingPlanRows" border class="business-crud-page__billing-table">
            <el-table-column type="index" label="序号" width="80" align="center" />
            <el-table-column prop="planName" label="方案名称" min-width="220" align="center" />
            <el-table-column label="默认方案" width="140" align="center">
              <template #default="{ row }">{{ row.defaultPlan ? '是' : '' }}</template>
            </el-table-column>
            <el-table-column
              prop="remark"
              label="备注"
              min-width="260"
              align="center"
              show-overflow-tooltip
            />
            <el-table-column label="操作" width="180" align="center">
              <template #default="{ row, $index }">
                <el-link type="primary" @click="openBillingPlan(row, $index)">
                  编辑
                </el-link>
                <el-link
                  v-if="!dialogReadonly"
                  type="primary"
                  @click="removeBillingPlan($index)"
                >
                  删除
                </el-link>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </template>

      <template #settlementRuleTitle-form>
        <div class="dialog-section-title">结算生成规则</div>
      </template>

      <template #settlementRuleJson-form>
        <div class="business-crud-page__settlement-rule">
          <div class="business-crud-page__settlement-switch">
            <span>自动生成结算单</span>
            <el-radio-group v-model="settlementRuleForm.autoGenerate" :disabled="dialogReadonly">
              <el-radio :label="1">开启</el-radio>
              <el-radio :label="0">关闭</el-radio>
            </el-radio-group>
          </div>
          <el-form
            v-if="settlementRuleEnabled"
            :model="settlementRuleForm"
            label-position="right"
            label-width="auto"
            class="business-crud-page__module-form business-crud-page__module-form--three"
          >
            <el-form-item
              label="账单起始日期"
              required
              :error="settlementRuleErrors.billStartDate"
            >
              <el-date-picker
                v-model="settlementRuleForm.billStartDate"
                type="date"
                placeholder="请选择账单起始日期"
                format="YYYY-MM-DD"
                value-format="YYYY-MM-DD"
                :disabled="dialogReadonly"
                @change="clearSettlementRuleError('billStartDate')"
              />
            </el-form-item>
            <el-form-item label="结算类型" required :error="settlementRuleErrors.settlementType">
              <el-select
                v-model="settlementRuleForm.settlementType"
                placeholder="请选择结算类型"
                :disabled="dialogReadonly"
                @change="handleSettlementTypeChange"
              >
                <el-option
                  v-for="item in settlementTypeOptions"
                  :key="item"
                  :label="item"
                  :value="item"
                />
              </el-select>
            </el-form-item>
            <el-form-item
              v-if="showSettlementBillCycleType"
              label="账单周期类型"
              required
              :error="settlementRuleErrors.billCycleType"
            >
              <el-select
                v-model="settlementRuleForm.billCycleType"
                placeholder="请选择账单周期类型"
                :disabled="dialogReadonly"
                @change="handleSettlementBillCycleTypeChange"
              >
                <el-option
                  v-for="item in billCycleTypeOptions"
                  :key="item"
                  :label="item"
                  :value="item"
                />
              </el-select>
            </el-form-item>
            <el-form-item
              v-if="showSettlementBillCutoffDay"
              label="账单截单日"
              required
              :error="settlementRuleErrors.billCutoffDay"
            >
              <el-select
                v-model="settlementRuleForm.billCutoffDay"
                placeholder="请选择账单截单日"
                :disabled="dialogReadonly"
                @change="clearSettlementRuleError('billCutoffDay')"
              >
                <el-option
                  v-for="item in billCutoffDayOptions"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
            </el-form-item>
            <el-form-item
              v-if="showSettlementCycleDays"
              label="周期天数"
              required
              :error="settlementRuleErrors.cycleDays"
            >
              <el-select
                v-model="settlementRuleForm.cycleDays"
                placeholder="请选择周期天数"
                :disabled="dialogReadonly"
                @change="clearSettlementRuleError('cycleDays')"
              >
                <el-option
                  v-for="item in cycleDayOptions"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
            </el-form-item>
          </el-form>
        </div>
      </template>

      <template #reconciliationTitle-form>
        <div class="dialog-section-title">对账配置</div>
      </template>

      <template #reconciliationJson-form>
        <el-form
          :model="reconciliationForm"
          label-position="right"
          label-width="auto"
          class="business-crud-page__module-form business-crud-page__module-form--two"
        >
          <el-form-item label="是否跳过对账" required>
            <el-select
              v-model="reconciliationForm.skipReconciliation"
              placeholder="请选择是否跳过对账"
              :disabled="dialogReadonly"
            >
              <el-option label="是" :value="1" />
              <el-option label="否" :value="0" />
            </el-select>
          </el-form-item>
          <el-form-item label="对账模式" required>
            <el-select
              v-model="reconciliationForm.reconciliationMode"
              placeholder="请选择对账模式"
              :disabled="dialogReadonly"
            >
              <el-option
                v-for="item in reconciliationModeOptions"
                :key="item"
                :label="item"
                :value="item"
              />
            </el-select>
          </el-form-item>
        </el-form>
      </template>

      <template #attachmentTitle-form>
        <div class="dialog-section-title">{{ config.attachmentTitle || '其它附件' }}</div>
      </template>

      <template #attachmentsJson-form>
        <div class="business-crud-page__attachment">
          <div class="business-crud-page__attachment-head">
            <vehicle-attachment-upload
              v-model="attachmentRows"
              :readonly="dialogReadonly"
              :file-types="attachmentFileTypes"
              :max-size="500"
              :show-tip="false"
              :show-file-list="false"
              button-text="上传附件"
              @change="handleAttachmentChange"
            />
            <el-button
              type="primary"
              :disabled="!attachmentRows.length"
              @click="handleBatchDownload"
            >
              批量下载
            </el-button>
          </div>
          <el-table :data="attachmentRows" border class="business-crud-page__attachment-table">
            <el-table-column type="index" label="序号" width="70" align="center" />
            <el-table-column label="文件名" min-width="200" align="center" show-overflow-tooltip>
              <template #default="{ row }">{{ row.originalName || row.name }}</template>
            </el-table-column>
            <el-table-column label="附件描述" min-width="220" align="center">
              <template #default="{ row }">
                <el-input
                  v-model="row.description"
                  placeholder="请输入附件描述"
                  :disabled="dialogReadonly"
                />
              </template>
            </el-table-column>
            <el-table-column label="文件大小" width="110" align="center">
              <template #default="{ row }">{{ formatFileSize(row.size) }}</template>
            </el-table-column>
            <el-table-column prop="uploadUserName" label="上传人" width="140" align="center" />
            <el-table-column prop="uploadTime" label="上传时间" width="160" align="center"  sortable/>
            <el-table-column label="操作" width="100" align="center">
              <template #default="{ row, $index }">
                <el-link
                  v-if="!dialogReadonly"
                  type="primary"
                  @click="removeAttachment($index)"
                >
                  删除
                </el-link>
                <el-link v-else type="primary" @click="downloadAttachment(row)">
                  下载
                </el-link>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </template>

      <template #changeRecordTitle-form>
        <div class="dialog-section-title">变更记录</div>
      </template>

      <template #changeRecordJson-form>
        <el-table :data="changeRecordRows" border class="business-crud-page__change-table">
          <el-table-column type="index" label="序号" width="70" align="center" />
          <el-table-column prop="changeDate" label="变更日期" min-width="150" align="center"  sortable/>
          <el-table-column prop="handlerUserName" label="经办人" min-width="140" align="center" />
          <el-table-column prop="changeType" label="变更类型" min-width="160" align="center" />
          <el-table-column
            prop="changeReason"
            label="变更原因"
            min-width="240"
            align="center"
            show-overflow-tooltip
          />
          <el-table-column prop="statusName" label="状态" min-width="140" align="center" />
          <el-table-column label="操作" width="120" align="center">
            <template #default="{ row }">
              <el-link type="primary" @click="handleChangeRecordFlow(row)"> 流程 </el-link>
            </template>
          </el-table-column>
        </el-table>
      </template>

      <template #menu-form-before>
        <el-button
          v-if="config.enableDraftSave && !dialogReadonly"
          type="primary"
          plain
          :loading="draftSaveLoading"
          @click="handleDraftSave"
        >
          保存
        </el-button>
      </template>

      <template #menu-form>
        <div v-if="showTransportPlanCreateActions" class="business-crud-page__create-actions">
          <el-button :disabled="Boolean(transportPlanCreateActionLoading)" @click="closeCrudDialog">
            关闭
          </el-button>
          <el-button
            type="primary"
            plain
            :loading="transportPlanCreateActionLoading === 'draft'"
            :disabled="Boolean(transportPlanCreateActionLoading)"
            @click="handleTransportPlanCreateAction('draft')"
          >
            暂存
          </el-button>
          <el-button
            type="primary"
            :loading="transportPlanCreateActionLoading === 'create'"
            :disabled="Boolean(transportPlanCreateActionLoading)"
            @click="handleTransportPlanCreateAction('create')"
          >
            确认创建
          </el-button>
        </div>
        <div v-if="showContractCreateActions" class="business-crud-page__contract-actions">
          <el-button
            type="primary"
            plain
            :loading="contractCreateActionLoading === 'draft'"
            :disabled="Boolean(contractCreateActionLoading)"
            @click="handleContractCreateAction('draft')"
          >
            保存
          </el-button>
          <el-button
            type="primary"
            :loading="contractCreateActionLoading === 'temporary'"
            :disabled="Boolean(contractCreateActionLoading)"
            @click="handleContractCreateAction('temporary')"
          >
            提交临时合同
          </el-button>
          <el-button
            type="primary"
            :loading="contractCreateActionLoading === 'formal'"
            :disabled="Boolean(contractCreateActionLoading)"
            @click="handleContractCreateAction('formal')"
          >
            提交正式合同
          </el-button>
        </div>
      </template>

      <template #contractStage="{ row }">
        <el-tag :type="stageTagType(row.contractStage)">
          {{ displayStatus(row, 'contractStage') }}
        </el-tag>
      </template>

      <template #menu="{ row, index }">
        <template v-if="config.customMenuActions">
          <el-link
            v-for="action in menuActions(row)"
            :key="action.key"
            :type="action.type || 'primary'"
            @click="handleMenuAction(action, row, index)"
          >
            {{ action.label }}
          </el-link>
        </template>
        <template v-else>
          <el-link type="primary" v-if="showDetailButton(row)" @click="openDetail(row)">详情</el-link>
          <el-link
            type="primary"
            v-if="hasPermission(`${config.permission}_view`) && !showDetailButton(row)"
            @click="$refs.crud.rowView(row, index)"
          >
            查看
          </el-link>
          <el-link
            type="primary"
            v-if="canEdit(row)"
            @click="$refs.crud.rowEdit(row, index)"
          >
            编辑
          </el-link>
          <el-link type="primary" v-if="canCopy(row)" @click="handleCopy(row)">
            复制
          </el-link>
          <el-link type="primary" v-if="canEnable(row)" @click="handleAction('enable', row)">
            启用
          </el-link>
          <el-link
            type="primary"
            v-if="canDisable(row)"
            @click="handleAction('disable', row)"
          >
            停用
          </el-link>
          <el-link
            type="primary"
            v-if="canReassign(row)"
            @click="handleAction('reassign', row)"
          >
            重新派单
          </el-link>
          <el-link type="primary" v-if="canCancel(row)" @click="handleAction('cancel', row)">
            取消
          </el-link>
          <el-link
            type="primary"
            v-if="canComplete(row)"
            @click="handleAction('complete', row)"
          >
            完成
          </el-link>
          <el-link
            v-for="operation in customOperations(row)"
            :key="operation.action"
            :type="operation.type || 'primary'"
            @click="handleCustomOperation(operation, row)"
          >
            {{ operation.label }}
          </el-link>
          <el-link type="primary" v-if="canDelete(row)" @click="rowDel(row)">
            删除
          </el-link>
        </template>
      </template>
    </avue-crud>

    <empty-pagination
      :page="page"
      @size-change="sizeChange"
      @current-change="currentChange"
      @load="onLoad(page, query)"
    />

    <el-dialog
      v-model="detailBox"
      :title="`${config.title}详情`"
      append-to-body
      width="96%"
      class="business-crud-page__detail-dialog"
    >
      <div v-loading="detailLoading" class="business-crud-page__detail-content">
        <section-card
          v-for="section in detailSections"
          :key="section.title"
          :title="section.title"
        >
          <el-descriptions :column="2" border>
            <el-descriptions-item
              v-for="field in section.fields"
              :key="field[0]"
              :label="field[1]"
              :span="field[2] || 1"
            >
              {{ formatDetailValue(detailRow, field[0]) }}
            </el-descriptions-item>
          </el-descriptions>
        </section-card>
        <section-card v-if="config.enableTransportPlanForm" title="货物信息">
          <el-table :data="transportCargoRows" border empty-text="暂无货物信息">
            <el-table-column type="index" label="序号" width="70" align="center" />
            <el-table-column prop="cargoName" label="货物名称" min-width="180" />
            <el-table-column prop="cargoType" label="货物类型" min-width="150" />
            <el-table-column prop="quantity" label="数量" min-width="100" />
            <el-table-column prop="quantityUnit" label="数量单位" min-width="110" />
            <el-table-column prop="packageType" label="包装" min-width="110" />
            <el-table-column prop="brand" label="品牌" min-width="120" />
            <el-table-column prop="specification" label="规格" min-width="120" />
            <el-table-column prop="model" label="型号" min-width="120" />
            <el-table-column prop="materialCode" label="物料编码" min-width="140" />
            <el-table-column prop="deviceCode" label="设备编码" min-width="140" />
            <el-table-column prop="remark" label="备注" min-width="160" />
          </el-table>
        </section-card>
        <section-card v-if="config.enableAttachmentTable" :title="config.attachmentTitle || '附件'">
          <el-table :data="attachmentRows" border empty-text="暂无附件">
            <el-table-column type="index" label="序号" width="70" align="center" />
            <el-table-column label="文件名" min-width="260">
              <template #default="{ row }">{{ row.originalName || row.name || '-' }}</template>
            </el-table-column>
            <el-table-column prop="description" label="附件描述" min-width="220" />
            <el-table-column label="文件大小" width="110">
              <template #default="{ row }">{{ formatFileSize(row.size) }}</template>
            </el-table-column>
            <el-table-column prop="uploadUserName" label="上传人" width="140" />
            <el-table-column prop="uploadTime" label="上传时间" width="180"  sortable/>
          </el-table>
        </section-card>
      </div>
      <template #footer>
        <el-button type="primary" @click="detailBox = false">关闭</el-button>
      </template>
    </el-dialog>

    <el-dialog
      v-if="config.enableTransportPlanImport"
      v-model="transportPlanImportBox"
      :title="config.transportPlanImportTitle || `导入${config.title}`"
      append-to-body
      width="96%"
      class="business-crud-page__transport-plan-import-dialog"
      @closed="resetTransportPlanImport"
    >
      <el-form
        ref="transportPlanImportFormRef"
        :model="transportPlanImportForm"
        :rules="transportPlanImportRules"
        label-position="right"
        label-width="auto"
        class="business-crud-page__transport-plan-import-form"
      >
        <el-row :gutter="24">
          <el-col :span="8">
            <el-form-item label="项目" prop="projectId" required>
              <el-select
                v-model="transportPlanImportForm.projectId"
                placeholder="模糊查询后选择"
                filterable
                clearable
                :loading="projectLoading"
                @visible-change="visible => visible && loadProjectOptions()"
                @change="handleTransportPlanImportProjectChange"
              >
                <el-option
                  v-for="item in projectOptions"
                  :key="item.id"
                  :label="item.projectName"
                  :value="item.id"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="客户" prop="customerName" required>
              <el-input
                v-model="transportPlanImportForm.customerName"
                disabled
                placeholder="选择项目后带出"
              />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="客户合同" prop="contractId" required>
              <el-select
                v-model="transportPlanImportForm.contractId"
                placeholder="选择项目后选择"
                filterable
                clearable
                :loading="transportPlanImportContractLoading"
                :disabled="!transportPlanImportForm.projectId"
                @change="handleTransportPlanImportContractChange"
              >
                <el-option
                  v-for="item in transportPlanImportContractOptions"
                  :key="item.id"
                  :label="item.contractName"
                  :value="item.id"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item
          label="计划明细表"
          prop="file"
          required
          class="business-crud-page__import-file-item"
        >
          <el-upload
            action="#"
            accept=".xls,.xlsx"
            :auto-upload="false"
            :limit="1"
            :file-list="transportPlanImportFiles"
            :on-change="handleTransportPlanImportFileChange"
            :on-remove="handleTransportPlanImportFileRemove"
          >
            <el-button type="primary">添加附件</el-button>
          </el-upload>
          <span class="business-crud-page__import-file-tip"
            >请上传计划明细表，仅支持 Excel 格式</span
          >
          <el-link type="primary" @click="handleTransportPlanTemplate">下载模板</el-link>
        </el-form-item>
      </el-form>
      <section v-if="transportPlanImportRows.length" class="business-crud-page__import-preview">
        <div class="business-crud-page__import-preview-head">
          <div class="dialog-section-title">数据明细</div>
          <el-button
            type="danger"
            plain
            :disabled="!transportPlanImportSelection.length"
            @click="removeSelectedTransportPlanImportRows"
          >
            批量删除
          </el-button>
        </div>
        <el-tabs v-model="transportPlanImportTab" class="business-crud-page__import-preview-tabs">
          <el-tab-pane :label="`计划单数（${transportPlanImportRows.length}）`" name="all" />
          <el-tab-pane
            :label="`疑似重复(${transportPlanImportDuplicateCount})`"
            name="duplicate"
          />
        </el-tabs>
        <div class="business-crud-page__import-preview-table">
          <el-table
            :data="transportPlanImportVisibleRows"
            border
            row-key="_key"
            empty-text="暂无明细数据"
            @selection-change="handleTransportPlanImportSelectionChange"
          >
            <el-table-column type="selection" width="52" fixed="left" />
            <el-table-column type="index" label="序号" width="70" fixed="left" />
            <el-table-column label="计划单号" min-width="150">
              <template #default>待生成</template>
            </el-table-column>
            <el-table-column label="计划名称" min-width="180">
              <template #default="{ row }">
                <el-input v-if="isEditingTransportPlanImportRow(row)" v-model="row.planName" />
                <span v-else>{{ row.planName || '-' }}</span>
              </template>
            </el-table-column>
            <el-table-column label="运输类型" min-width="160">
              <template #default="{ row }">
                <el-input v-if="isEditingTransportPlanImportRow(row)" v-model="row.transportType" />
                <span v-else>{{ row.transportType || '-' }}</span>
              </template>
            </el-table-column>
            <el-table-column label="货物名称" min-width="160">
              <template #default="{ row }">
                <el-input v-if="isEditingTransportPlanImportRow(row)" v-model="row.cargoName" />
                <span v-else>{{ row.cargoName || '-' }}</span>
              </template>
            </el-table-column>
            <el-table-column label="货物类型" min-width="160">
              <template #default="{ row }">
                <el-input v-if="isEditingTransportPlanImportRow(row)" v-model="row.cargoType" />
                <span v-else>{{ row.cargoType || '-' }}</span>
              </template>
            </el-table-column>
            <el-table-column label="数量" min-width="120">
              <template #default="{ row }">
                <el-input v-if="isEditingTransportPlanImportRow(row)" v-model="row.quantity" />
                <span v-else>{{ row.quantity || '-' }}</span>
              </template>
            </el-table-column>
            <el-table-column label="计量单位" min-width="130">
              <template #default="{ row }">
                <el-select v-if="isEditingTransportPlanImportRow(row)" v-model="row.quantityUnit">
                  <el-option
                    v-for="item in quantityUnitOptions"
                    :key="item"
                    :label="item"
                    :value="item"
                  />
                </el-select>
                <span v-else>{{ row.quantityUnit || '-' }}</span>
              </template>
            </el-table-column>
            <el-table-column label="发货地址" min-width="260">
              <template #default="{ row }">
                <el-input
                  v-if="isEditingTransportPlanImportRow(row)"
                  v-model="row.departureAddress"
                />
                <span v-else>{{ row.departureAddress || '-' }}</span>
              </template>
            </el-table-column>
            <el-table-column label="操作" width="150" fixed="right">
              <template #default="{ row }">
                <template v-if="isEditingTransportPlanImportRow(row)">
                  <el-link type="primary" @click="saveTransportPlanImportRow(row)">保存</el-link>
                  <el-link type="primary" @click="cancelTransportPlanImportRow(row)">取消</el-link>
                </template>
                <template v-else>
                  <el-link type="primary" @click="editTransportPlanImportRow(row)">编辑</el-link>
                  <el-link type="danger" @click="removeTransportPlanImportRow(row)">删除</el-link>
                </template>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </section>
      <template #footer>
        <el-button type="primary" :loading="transportPlanImportLoading" @click="submitTransportPlanImport">提交</el-button>
        <el-button @click="transportPlanImportBox = false">返回</el-button>
      </template>
    </el-dialog>

    <waybill-import-dialog v-if="config.enableWaybillImport" v-model="waybillImportBox" />

    <flow-design
      v-if="website.design.designMode"
      is-dialog
      v-model:is-display="flowBox"
      :process-instance-id="processInstanceId"
    />
    <el-dialog v-else title="流程图" append-to-body v-model="flowBox" :fullscreen="true">
      <iframe
        :src="flowUrl"
        width="100%"
        height="700"
        title="流程图"
        frameBorder="no"
        border="0"
        marginWidth="0"
        marginHeight="0"
        scrolling="no"
        allowTransparency="yes"
      />
      <template #footer>
        <el-button @click="flowBox = false">关闭</el-button>
      </template>
    </el-dialog>

    <el-dialog
      :title="dispatchDialogTitle"
      append-to-body
      v-model="dispatchBox"
      width="calc(100vw - 32px)"
      top="16px"
      class="business-crud-page__dispatch-dialog"
      @closed="resetDispatchDialog"
    >
      <div v-loading="dispatchLoading" class="business-crud-page__dispatch-shell">
        <div class="business-crud-page__dispatch-top">
          <div class="business-crud-page__dispatch-heading">
            <div class="business-crud-page__dispatch-heading-title">
              <span class="business-crud-page__dispatch-heading-name">
                {{ dispatchDialogTitle }}
              </span>
              <span class="business-crud-page__dispatch-heading-no">
                {{ dispatchDialogNo || '-' }}
              </span>
              <el-tag :type="statusTagType(dispatchRow.businessStatus)" effect="light">
                {{ dispatchDialogStatusText }}
              </el-tag>
              <el-tag type="primary" effect="light">
                {{ dispatchRow.transportType || '公路整车' }}
              </el-tag>
            </div>
          </div>

          <div class="business-crud-page__dispatch-summary">
            <section-card title="基本信息" class="business-crud-page__dispatch-summary-card">
              <div class="business-crud-page__dispatch-summary-grid">
                <div class="business-crud-page__dispatch-summary-item">
                  <div class="business-crud-page__dispatch-summary-label">客户</div>
                  <div class="business-crud-page__dispatch-summary-value">
                    {{ dispatchRow.customerName || '-' }}
                  </div>
                </div>
                <div class="business-crud-page__dispatch-summary-item">
                  <div class="business-crud-page__dispatch-summary-label">项目</div>
                  <div class="business-crud-page__dispatch-summary-value">
                    {{ dispatchRow.projectName || '-' }}
                  </div>
                </div>
                <div class="business-crud-page__dispatch-summary-item">
                  <div class="business-crud-page__dispatch-summary-label">合同编号</div>
                  <div class="business-crud-page__dispatch-summary-value">
                    {{ dispatchRow.contractName || dispatchRow.contractNo || '-' }}
                  </div>
                </div>
                <div class="business-crud-page__dispatch-summary-item">
                  <div class="business-crud-page__dispatch-summary-label">计划执行时间</div>
                  <div class="business-crud-page__dispatch-summary-value">
                    {{
                      [dispatchRow.planStartDate, dispatchRow.planEndDate]
                        .filter(Boolean)
                        .join(' ~ ') || '-'
                    }}
                  </div>
                </div>
                <div class="business-crud-page__dispatch-summary-item is-wide">
                  <div class="business-crud-page__dispatch-summary-label">货物信息</div>
                  <div class="business-crud-page__dispatch-summary-value">
                    {{ dispatchPlanCargoInfo || '-' }}
                  </div>
                </div>
                <div
                  class="business-crud-page__dispatch-summary-item business-crud-page__dispatch-summary-item--attachments"
                >
                  <div class="business-crud-page__dispatch-summary-label">附件</div>
                  <div class="business-crud-page__dispatch-summary-value">
                    <el-link
                      v-for="(item, index) in dispatchAttachmentRows"
                      :key="dispatchAttachmentLabel(item) + index"
                      type="primary"
                      :href="dispatchAttachmentUrl(item) || undefined"
                      :underline="false"
                      target="_blank"
                      class="business-crud-page__dispatch-attachment-link"
                    >
                      {{ dispatchAttachmentLabel(item) }}
                    </el-link>
                    <span v-if="!dispatchAttachmentRows.length">-</span>
                  </div>
                </div>
              </div>
            </section-card>

            <section-card title="收发货路线" class="business-crud-page__dispatch-route-card">
              <div class="business-crud-page__dispatch-route">
                <div class="business-crud-page__dispatch-route-item">
                  <span class="business-crud-page__dispatch-route-badge is-start">起</span>
                  <div class="business-crud-page__dispatch-route-body">
                    <div class="business-crud-page__dispatch-route-title">
                      {{ parseDispatchRoutePoint(dispatchRow, 'start').name || '-' }}
                    </div>
                    <div class="business-crud-page__dispatch-route-address">
                      {{ parseDispatchRoutePoint(dispatchRow, 'start').address || '-' }}
                    </div>
                    <div class="business-crud-page__dispatch-route-contact">
                      {{ parseDispatchRoutePoint(dispatchRow, 'start').contact || '-' }}
                    </div>
                  </div>
                </div>
                <div class="business-crud-page__dispatch-route-vehicle">
                  <el-icon><Van /></el-icon>
                </div>
                <div class="business-crud-page__dispatch-route-item">
                  <span class="business-crud-page__dispatch-route-badge is-end">终</span>
                  <div class="business-crud-page__dispatch-route-body">
                    <div class="business-crud-page__dispatch-route-title">
                      {{ parseDispatchRoutePoint(dispatchRow, 'end').name || '-' }}
                    </div>
                    <div class="business-crud-page__dispatch-route-address">
                      {{ parseDispatchRoutePoint(dispatchRow, 'end').address || '-' }}
                    </div>
                    <div class="business-crud-page__dispatch-route-contact">
                      {{ parseDispatchRoutePoint(dispatchRow, 'end').contact || '-' }}
                    </div>
                  </div>
                </div>
              </div>
            </section-card>
          </div>
        </div>

        <section-card title="待调度列表">
          <template #extra>
            <el-button
              type="primary"
              :disabled="dispatchSummaryRemainingQuantity <= 0"
              @click="handleDispatchAddRow"
            >
              新增
            </el-button>
            <span class="business-crud-page__dispatch-list-summary">
              {{ dispatchSummaryText }}
            </span>
          </template>
          <el-table
            :data="dispatchRows"
            border
            height="260"
            row-key="_key"
            class="business-crud-page__dispatch-table"
          >
            <el-table-column type="index" label="序号" width="70" align="center" fixed="left" />
            <el-table-column
              prop="transportType"
              label="运输方式"
              min-width="180"
              align="center"
              show-overflow-tooltip
            />
            <el-table-column
              prop="carrierType"
              label="承运类型"
              min-width="150"
              align="center"
              show-overflow-tooltip
            />
            <el-table-column
              prop="carrierName"
              label="承运商"
              min-width="150"
              align="center"
              show-overflow-tooltip
            />
            <el-table-column
              prop="driverName"
              label="司机(联系方式)"
              min-width="220"
              align="center"
              show-overflow-tooltip
            >
              <template #default="{ row }">
                <div>
                  <div>{{ row.driverName || '-' }}</div>
                  <div>{{ row.driverPhone || '-' }}</div>
                </div>
              </template>
            </el-table-column>
            <el-table-column
              prop="vehicleNo"
              label="车牌号/航班号/船号/班列号"
              min-width="200"
              align="center"
              show-overflow-tooltip
            />
            <el-table-column
              prop="cargoType"
              label="货物类型"
              min-width="170"
              align="center"
              show-overflow-tooltip
            />
            <el-table-column
              prop="cargoInfo"
              label="货物信息"
              min-width="280"
              align="center"
              show-overflow-tooltip
            />
            <el-table-column
              prop="departureAddress"
              label="发货地址"
              min-width="220"
              align="center"
              show-overflow-tooltip
            />
            <el-table-column
              prop="arrivalAddress"
              label="收货地址"
              min-width="220"
              align="center"
              show-overflow-tooltip
            />
            <el-table-column
              prop="dispatchUserName"
              label="调度人"
              min-width="140"
              align="center"
              show-overflow-tooltip
            />
            <el-table-column
              prop="dispatchTime"
              label="调度时间"
              min-width="160"
              align="center"
              show-overflow-tooltip
             sortable/>
            <el-table-column label="操作" width="220" align="center" fixed="right">
              <template #default="{ row, $index }">
                <div class="business-crud-page__dispatch-actions">
                  <el-link type="primary" @click="handleDispatchEditRow(row, $index)">编辑</el-link>
                  <el-link type="danger" @click="removeDispatchRow($index)">删除</el-link>
                </div>
              </template>
            </el-table-column>
          </el-table>
        </section-card>
      </div>
      <template #footer>
        <div class="business-crud-page__dispatch-footer">
          <el-button @click="dispatchBox = false">关闭</el-button>
          <el-button
            type="primary"
            :loading="dispatchSaving === 'submit'"
            @click="handleDispatchSubmit('submit')"
          >
            确认生成
          </el-button>
        </div>
      </template>
    </el-dialog>

    <el-dialog
      :title="dispatchItemDialogTitle"
      append-to-body
      v-model="dispatchItemBox"
      width="96%"
      top="8vh"
      class="business-crud-page__dispatch-item-dialog"
      @closed="resetDispatchItemDialog"
    >
      <el-form
        :model="dispatchItemForm"
        label-position="right"
        label-width="auto"
        class="business-crud-page__dispatch-item-form"
      >
        <div class="dialog-section-title">收发货信息</div>
        <div class="business-crud-page__dispatch-shipping-form">
          <el-form-item label="发货地址" required>
            <div class="business-crud-page__route-address-row">
              <el-input
                v-model="dispatchItemForm.departureName"
                :placeholder="dispatchStationNamePlaceholder"
                :suffix-icon="dispatchStationMode ? Search : undefined"
              />
              <el-input
                v-model="dispatchItemForm.departureAddress"
                :placeholder="dispatchAddressDetailPlaceholder"
                :suffix-icon="dispatchStationMode ? undefined : Location"
              />
              <span class="business-crud-page__route-label">联系人</span>
              <el-input v-model="dispatchItemForm.departureContact" placeholder="请输入" />
              <span class="business-crud-page__route-label">联系方式</span>
              <el-input v-model="dispatchItemForm.departurePhone" placeholder="请输入" />
            </div>
          </el-form-item>
          <el-form-item label="收货地址" required>
            <div class="business-crud-page__route-address-row">
              <el-input
                v-model="dispatchItemForm.arrivalName"
                :placeholder="dispatchStationNamePlaceholder"
                :suffix-icon="dispatchStationMode ? Search : undefined"
              />
              <el-input
                v-model="dispatchItemForm.arrivalAddress"
                :placeholder="dispatchAddressDetailPlaceholder"
                :suffix-icon="dispatchStationMode ? undefined : Location"
              />
              <span class="business-crud-page__route-label">联系人</span>
              <el-input v-model="dispatchItemForm.arrivalContact" placeholder="请输入" />
              <span class="business-crud-page__route-label">联系方式</span>
              <el-input v-model="dispatchItemForm.arrivalPhone" placeholder="请输入" />
            </div>
          </el-form-item>
        </div>

        <div
          class="dialog-section-title dialog-section-title--action"
          style="display: flex; width: 100%; justify-content: space-between"
        >
          <span class="business-crud-page__dispatch-task-title">任务信息</span>
          <el-segmented
            v-model="dispatchItemForm.taskEntryMode"
            :options="taskEntryModeOptions"
            class="business-crud-page__dispatch-entry-mode"
          />
        </div>

        <template v-if="dispatchItemForm.taskEntryMode === 'full'">
          <div class="dialog-section-title">货物信息</div>
          <div class="business-crud-page__dispatch-item-grid">
            <el-form-item label="货物名称" required>
              <el-input v-model="dispatchItemForm.cargoName" placeholder="请输入" />
            </el-form-item>
            <el-form-item label="货物类型" required>
              <el-input v-model="dispatchItemForm.cargoType" placeholder="请输入" />
            </el-form-item>
            <el-form-item label="数量" required>
              <el-input v-model="dispatchItemForm.quantity" placeholder="请输入">
                <template #append>
                  <el-select
                    v-model="dispatchItemForm.quantityUnit"
                    class="business-crud-page__dispatch-unit-select"
                  >
                    <el-option
                      v-for="item in quantityUnitOptions"
                      :key="item"
                      :label="item"
                      :value="item"
                    />
                  </el-select>
                </template>
              </el-input>
            </el-form-item>
            <el-form-item label="包装">
              <el-select v-model="dispatchItemForm.packageType" clearable placeholder="请选择">
                <el-option
                  v-for="item in packageOptions"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
            </el-form-item>
            <el-form-item label="规格">
              <el-input v-model="dispatchItemForm.specification" placeholder="请输入" />
            </el-form-item>
            <el-form-item label="型号">
              <el-input v-model="dispatchItemForm.model" placeholder="请输入" />
            </el-form-item>
          </div>

          <div class="dialog-section-title">费用信息</div>
          <div class="business-crud-page__dispatch-item-grid">
            <el-form-item label="单价">
              <el-input v-model="dispatchItemForm.unitPrice" placeholder="请输入">
                <template #append>
                  <el-select
                    v-model="dispatchItemForm.priceUnit"
                    class="business-crud-page__dispatch-unit-select"
                    placeholder="元/吨"
                    :loading="taskFeeUnitLoading"
                    @visible-change="visible => visible && loadTaskFeeUnitOptions()"
                  >
                    <el-option
                      v-if="!taskFeeUnitOptions.some(item => item.value === '元/吨')"
                      label="元/吨"
                      value="元/吨"
                    />
                    <el-option
                      v-for="item in taskFeeUnitOptions"
                      :key="item.value"
                      :label="item.label"
                      :value="item.value"
                    />
                  </el-select>
                </template>
              </el-input>
            </el-form-item>
            <el-form-item label="其他费用合计">
              <el-input v-model="dispatchItemForm.otherFeeTotal" placeholder="请输入" />
            </el-form-item>
          </div>
        </template>
        <template v-else>
          <div
            class="business-crud-page__dispatch-item-grid business-crud-page__dispatch-item-grid--compact"
          >
            <el-form-item
              label="承运类型"
              required
              class="business-crud-page__dispatch-item-carrier-type"
            >
              <el-radio-group
                v-model="dispatchItemForm.carrierType"
                @change="handleDispatchCarrierTypeChange"
              >
                <el-radio-button v-for="item in taskCarrierTypes" :key="item" :label="item" />
              </el-radio-group>
            </el-form-item>
            <el-form-item v-if="dispatchItemForm.carrierType === '承运商'" label="承运商" required>
              <el-select
                v-model="dispatchItemForm.carrierName"
                placeholder="请输入"
                filterable
                clearable
                :suffix-icon="Search"
                :loading="taskCarrierLoading"
                @visible-change="visible => visible && loadDispatchCarrierOptions()"
                @change="handleDispatchCarrierChange"
              >
                <el-option
                  v-for="item in taskCarrierOptions"
                  :key="
                    item.id || item.customerName || item.carrierName || item.fullName || item.name
                  "
                  :label="item.customerName || item.carrierName || item.fullName || item.name"
                  :value="item.customerName || item.carrierName || item.fullName || item.name"
                />
              </el-select>
            </el-form-item>
            <el-form-item label="司机" :required="dispatchItemForm.carrierType !== '承运商'">
              <el-select
                v-model="dispatchItemForm.driverName"
                placeholder="请输入"
                filterable
                clearable
                allow-create
                default-first-option
                :suffix-icon="Search"
                :loading="taskDriverLoading"
                @visible-change="visible => visible && loadDispatchDriverOptions()"
                @change="handleDispatchDriverChange"
              >
                <el-option
                  v-for="item in taskDriverOptions"
                  :key="item.id || item.driverName || item.mobile"
                  :label="item.driverName || item.name"
                  :value="item.driverName || item.name"
                />
              </el-select>
            </el-form-item>
            <el-form-item
              :label="dispatchItemForm.carrierType === '承运商' ? '手机号' : '司机手机号'"
              :required="dispatchItemForm.carrierType !== '承运商'"
            >
              <el-input v-model="dispatchItemForm.driverPhone" placeholder="请输入" />
            </el-form-item>
            <el-form-item label="车牌号" required>
              <el-input v-model="dispatchItemForm.vehicleNo" placeholder="请输入" />
            </el-form-item>
            <el-form-item
              v-if="dispatchItemForm.carrierType !== '承运商'"
              label="挂车车牌号"
              required
            >
              <el-input v-model="dispatchItemForm.trailerVehicleNo" placeholder="请输入" />
            </el-form-item>
            <el-form-item v-if="dispatchItemForm.carrierType !== '承运商'" label="押运人" required>
              <el-input v-model="dispatchItemForm.escortName" placeholder="请输入" />
            </el-form-item>
            <el-form-item
              v-if="dispatchItemForm.carrierType !== '承运商'"
              label="押运人手机号"
              required
            >
              <el-input v-model="dispatchItemForm.escortPhone" placeholder="请输入" />
            </el-form-item>
            <el-form-item label="货物类型" required>
              <el-input v-model="dispatchItemForm.cargoType" placeholder="请输入" />
            </el-form-item>
            <el-form-item label="货物名称" required>
              <el-input v-model="dispatchItemForm.cargoName" placeholder="请输入" />
            </el-form-item>
            <el-form-item label="规格">
              <el-input v-model="dispatchItemForm.specification" placeholder="请输入" />
            </el-form-item>
            <el-form-item label="型号">
              <el-input v-model="dispatchItemForm.model" placeholder="请输入" />
            </el-form-item>
            <el-form-item label="数量" required>
              <el-input v-model="dispatchItemForm.quantity" placeholder="请输入">
                <template #append>
                  <el-select
                    v-model="dispatchItemForm.quantityUnit"
                    class="business-crud-page__dispatch-unit-select"
                  >
                    <el-option
                      v-for="item in quantityUnitOptions"
                      :key="item"
                      :label="item"
                      :value="item"
                    />
                  </el-select>
                </template>
              </el-input>
            </el-form-item>
            <el-form-item label="里程(公里)" :required="dispatchItemForm.carrierType !== '承运商'">
              <el-input v-model="dispatchItemForm.mileage" placeholder="请输入" />
            </el-form-item>
            <el-form-item label="预计发货日期">
              <el-date-picker
                v-model="dispatchItemForm.estimatedStartTime"
                type="date"
                format="YYYY-MM-DD"
                value-format="YYYY-MM-DD"
                placeholder="请选择"
              />
            </el-form-item>
            <el-form-item label="预计完成日期">
              <el-date-picker
                v-model="dispatchItemForm.estimatedEndTime"
                type="date"
                format="YYYY-MM-DD"
                value-format="YYYY-MM-DD"
                placeholder="请选择"
              />
            </el-form-item>
            <el-form-item label="单价">
              <el-input v-model="dispatchItemForm.unitPrice" placeholder="请输入">
                <template #append>
                  <el-select
                    v-model="dispatchItemForm.priceUnit"
                    class="business-crud-page__dispatch-unit-select"
                    placeholder="元/吨"
                    :loading="taskFeeUnitLoading"
                    @visible-change="visible => visible && loadTaskFeeUnitOptions()"
                  >
                    <el-option
                      v-if="!taskFeeUnitOptions.some(item => item.value === '元/吨')"
                      label="元/吨"
                      value="元/吨"
                    />
                    <el-option
                      v-for="item in taskFeeUnitOptions"
                      :key="item.value"
                      :label="item.label"
                      :value="item.value"
                    />
                  </el-select>
                </template>
              </el-input>
            </el-form-item>
            <el-form-item label="备注" class="business-crud-page__dispatch-item-span-3">
              <el-input
                v-model="dispatchItemForm.remark"
                maxlength="200"
                show-word-limit
                placeholder="请输入，200字以内"
              />
            </el-form-item>
          </div>
        </template>

        <div
          v-if="dispatchItemForm.taskEntryMode === 'full'"
          class="dialog-section-title business-crud-page__dispatch-carrier-title"
          style="display: flex; width: 100%; margin-top: 16px"
        >
          承运信息
        </div>
        <div
          v-if="dispatchItemForm.taskEntryMode === 'full'"
          class="business-crud-page__dispatch-item-grid"
        >
          <el-form-item label="承运类型" required class="business-crud-page__dispatch-item-span-2">
            <el-radio-group
              v-model="dispatchItemForm.carrierType"
              @change="handleDispatchCarrierTypeChange"
            >
              <el-radio-button v-for="item in taskCarrierTypes" :key="item" :label="item" />
            </el-radio-group>
          </el-form-item>
          <el-form-item v-if="dispatchItemForm.carrierType === '承运商'" label="承运商" required>
            <el-select
              v-model="dispatchItemForm.carrierName"
              placeholder="请输入"
              filterable
              clearable
              :suffix-icon="Search"
              :loading="taskCarrierLoading"
              @visible-change="visible => visible && loadDispatchCarrierOptions()"
              @change="handleDispatchCarrierChange"
            >
              <el-option
                v-for="item in taskCarrierOptions"
                :key="
                  item.id || item.customerName || item.carrierName || item.fullName || item.name
                "
                :label="item.customerName || item.carrierName || item.fullName || item.name"
                :value="item.customerName || item.carrierName || item.fullName || item.name"
              />
            </el-select>
          </el-form-item>
          <el-form-item
            v-if="dispatchItemForm.carrierType"
            label="司机"
            :required="dispatchItemForm.carrierType !== '承运商'"
          >
            <el-select
              v-model="dispatchItemForm.driverName"
              placeholder="请输入"
              filterable
              clearable
              allow-create
              default-first-option
              :suffix-icon="Search"
              :loading="taskDriverLoading"
              @visible-change="visible => visible && loadDispatchDriverOptions()"
              @change="handleDispatchDriverChange"
            >
              <el-option
                v-for="item in taskDriverOptions"
                :key="item.id || item.driverName || item.mobile"
                :label="item.driverName || item.name"
                :value="item.driverName || item.name"
              />
            </el-select>
          </el-form-item>
          <el-form-item
            v-if="dispatchItemForm.carrierType"
            :label="dispatchItemForm.carrierType === '承运商' ? '手机号' : '司机手机号'"
            :required="dispatchItemForm.carrierType !== '承运商'"
          >
            <el-input v-model="dispatchItemForm.driverPhone" placeholder="请输入" />
          </el-form-item>
          <el-form-item
            v-if="dispatchItemForm.carrierType"
            label="车牌号/航班号/船号/班列号"
            required
          >
            <el-input v-model="dispatchItemForm.vehicleNo" placeholder="请输入" />
          </el-form-item>
          <el-form-item
            v-if="dispatchItemForm.carrierType && dispatchItemForm.carrierType !== '承运商'"
            label="挂车车牌号"
            required
          >
            <el-input v-model="dispatchItemForm.trailerVehicleNo" placeholder="请输入" />
          </el-form-item>
          <el-form-item
            v-if="dispatchItemForm.carrierType && dispatchItemForm.carrierType !== '承运商'"
            label="押运人"
            required
          >
            <el-input v-model="dispatchItemForm.escortName" placeholder="请输入" />
          </el-form-item>
          <el-form-item
            v-if="dispatchItemForm.carrierType && dispatchItemForm.carrierType !== '承运商'"
            label="押运人手机号"
            required
          >
            <el-input v-model="dispatchItemForm.escortPhone" placeholder="请输入" />
          </el-form-item>
          <el-form-item
            v-if="dispatchItemForm.carrierType && dispatchItemForm.carrierType !== '承运商'"
            label="里程(km)"
            required
          >
            <el-input v-model="dispatchItemForm.mileage" placeholder="请输入" />
          </el-form-item>
          <el-form-item label="预计发货日期">
            <el-date-picker
              v-model="dispatchItemForm.estimatedStartTime"
              type="date"
              format="YYYY-MM-DD"
              value-format="YYYY-MM-DD"
              placeholder="请选择"
            />
          </el-form-item>
          <el-form-item label="预计完成日期">
            <el-date-picker
              v-model="dispatchItemForm.estimatedEndTime"
              type="date"
              format="YYYY-MM-DD"
              value-format="YYYY-MM-DD"
              placeholder="请选择"
            />
          </el-form-item>
          <el-form-item label="备注" class="business-crud-page__dispatch-item-span-2">
            <el-input
              v-model="dispatchItemForm.remark"
              type="textarea"
              :rows="2"
              maxlength="200"
              show-word-limit
              placeholder="请输入，200字以内"
            />
          </el-form-item>
        </div>

        <template v-if="dispatchItemForm.taskEntryMode === 'full'">
          <div class="dialog-section-title">附件</div>
          <div class="business-crud-page__attachment">
            <div class="business-crud-page__attachment-head">
              <vehicle-attachment-upload
                v-model="dispatchItemAttachmentRows"
                :file-types="attachmentFileTypes"
                :max-size="500"
                :show-tip="false"
                :show-file-list="false"
                button-text="上传附件"
                @change="handleDispatchItemAttachmentChange"
              />
              <el-button
                type="primary"
                :disabled="!dispatchItemAttachmentRows.length"
                @click="handleDispatchItemBatchDownload"
              >
                批量下载
              </el-button>
            </div>
            <el-table
              :data="dispatchItemAttachmentRows"
              border
              class="business-crud-page__attachment-table"
            >
              <el-table-column type="index" label="序号" width="70" align="center" />
              <el-table-column label="文件名" min-width="200" align="center" show-overflow-tooltip>
                <template #default="{ row }">{{ row.originalName || row.name }}</template>
              </el-table-column>
              <el-table-column label="附件描述" min-width="220" align="center">
                <template #default="{ row }">
                  <el-input v-model="row.description" placeholder="请输入附件描述" />
                </template>
              </el-table-column>
              <el-table-column label="文件大小" width="110" align="center">
                <template #default="{ row }">{{ formatFileSize(row.size) }}</template>
              </el-table-column>
              <el-table-column prop="uploadUserName" label="上传人" width="140" align="center" />
              <el-table-column prop="uploadTime" label="上传时间" width="160" align="center"  sortable/>
              <el-table-column label="操作" width="100" align="center">
                <template #default="{ $index }">
                  <el-link type="danger" @click="removeDispatchItemAttachment($index)">删除</el-link>
                </template>
              </el-table-column>
            </el-table>
          </div>
        </template>
      </el-form>
      <template #footer>
        <el-button @click="dispatchItemBox = false">取消</el-button>
        <el-button type="primary" @click="saveDispatchItem">提交</el-button>
      </template>
    </el-dialog>

    <el-dialog
      :title="billingPlanMode === 'edit' ? '编辑计费方案' : '添加计费方案'"
      append-to-body
      v-model="billingPlanBox"
      width="90%"
      class="business-crud-page__billing-dialog"
    >
      <el-form
        ref="billingPlanFormRef"
        :model="billingPlanForm"
        :rules="billingPlanRules"
        label-position="right"
        label-width="auto"
      >
        <div class="business-crud-page__billing-form-row">
          <el-form-item label="方案名称" prop="planName" required>
            <el-input
              v-model="billingPlanForm.planName"
              maxlength="100"
              :disabled="dialogReadonly"
            />
          </el-form-item>
          <el-form-item label="" class="business-crud-page__billing-default">
            <el-checkbox v-model="billingPlanForm.defaultPlan" :disabled="dialogReadonly">
              默认方案
            </el-checkbox>
          </el-form-item>
        </div>
        <el-form-item label="备注">
          <el-input
            v-model="billingPlanForm.remark"
            class="business-crud-page__billing-plan-remark"
            maxlength="100"
            show-word-limit
            :disabled="dialogReadonly"
          />
        </el-form-item>
      </el-form>

      <div class="business-crud-page__billing-rule-head">
        <el-link v-if="!dialogReadonly" type="primary" @click="addBillingRule">
          +添加规则
        </el-link>
      </div>
      <div class="business-crud-page__billing-rule-wrap">
        <el-table
          :data="billingPlanForm.rules"
          border
          class="business-crud-page__billing-rule-table"
        >
          <el-table-column type="index" label="序号" width="70" align="center" fixed="left" />
          <el-table-column width="180" align="center">
            <template #header>
              <span class="business-crud-page__required-column">费用类型</span>
            </template>
            <template #default="{ row }">
              <el-select
                v-model="row.feeType"
                class="business-crud-page__billing-field"
                clearable
                filterable
                :disabled="dialogReadonly"
                :loading="billingFeeCategoryLoading"
                placeholder="请选择"
                @change="value => handleBillingFeeTypeChange(row, value)"
              >
                <el-option
                  v-for="item in billingFeeCategoryOptions"
                  :key="item.id || item.dictKey"
                  :label="item.dictValue"
                  :value="item.dictKey"
                />
              </el-select>
            </template>
          </el-table-column>
          <el-table-column width="210" align="center">
            <template #header>
              <span class="business-crud-page__required-column">费用项</span>
            </template>
            <template #default="{ row }">
              <el-select
                v-model="row.feeItem"
                class="business-crud-page__billing-field"
                clearable
                filterable
                :disabled="dialogReadonly || !row.feeType"
                :loading="billingFeeItemLoadingMap[resolveBillingFeeType(row.feeType)]"
                :placeholder="row.feeType ? '请选择' : '请先选择费用类型'"
              >
                <el-option
                  v-for="item in getBillingFeeItemOptions(row.feeType)"
                  :key="item.id || item.name || item.englishName"
                  :label="item.name || item.englishName"
                  :value="item.name || item.englishName"
                />
              </el-select>
            </template>
          </el-table-column>
          <el-table-column width="170" align="center">
            <template #header>
              <span class="business-crud-page__required-column">计费要素</span>
            </template>
            <template #default="{ row }">
              <el-select
                v-model="row.billingElement"
                :disabled="dialogReadonly"
                placeholder="请选择"
                @change="handleBillingElementChange(row)"
              >
                <el-option
                  v-for="item in billingElementOptions"
                  :key="item"
                  :label="item"
                  :value="item"
                />
              </el-select>
            </template>
          </el-table-column>
          <el-table-column width="180" align="center">
            <template #header>
              <span class="business-crud-page__required-column">计费类型</span>
            </template>
            <template #default="{ row }">
              <el-select
                v-model="row.billingType"
                :disabled="dialogReadonly || !row.billingElement"
                :placeholder="row.billingElement ? '请选择' : '请先选择计费要素'"
                @change="handleBillingTypeChange(row)"
              >
                <el-option
                  v-for="item in getBillingTypeOptions(row)"
                  :key="item"
                  :label="item"
                  :value="item"
                />
              </el-select>
            </template>
          </el-table-column>
          <el-table-column width="150" align="center">
            <template #header>
              <span class="business-crud-page__required-column">计费单位</span>
            </template>
            <template #default="{ row }">
              <el-select
                v-model="row.billingUnit"
                clearable
                filterable
                :disabled="dialogReadonly"
                :loading="billingUnitLoading"
                placeholder="请选择"
              >
                <el-option
                  v-for="item in billingUnitOptions"
                  :key="item.id || item.dictKey || item.dictValue"
                  :label="item.dictValue"
                  :value="item.dictValue"
                />
              </el-select>
            </template>
          </el-table-column>
          <el-table-column width="140" align="center">
            <template #header>
              <span class="business-crud-page__required-column">单价（元）</span>
            </template>
            <template #default="{ row }">
              <el-input
                v-model="row.unitPrice"
                :disabled="dialogReadonly"
                placeholder="请输入"
                @input="value => handleBillingDecimalInput(row, 'unitPrice', value)"
              />
            </template>
          </el-table-column>
          <el-table-column label="计费要素下限" width="260" align="center">
            <template #default="{ row }">
              <div class="business-crud-page__limit-list">
                <el-input
                  v-for="(item, rangeIndex) in getBillingLimitRanges(row)"
                  :key="rangeIndex"
                  v-model="item.lowerLimit"
                  :disabled="!canEditBillingLimit(row)"
                  :placeholder="canEditBillingLimit(row) ? '请输入下限' : '无需配置'"
                  @input="value => handleBillingLimitInput(row, rangeIndex, 'lowerLimit', value)"
                />
              </div>
            </template>
          </el-table-column>
          <el-table-column label="计费要素上限" width="260" align="center">
            <template #default="{ row }">
              <div class="business-crud-page__limit-list">
                <div
                  v-for="(item, rangeIndex) in getBillingLimitRanges(row)"
                  :key="rangeIndex"
                  class="business-crud-page__limit-row"
                >
                  <el-input
                    v-model="item.upperLimit"
                    :disabled="!canEditBillingLimit(row)"
                    :placeholder="canEditBillingLimit(row) ? '请输入上限' : '无需配置'"
                    @input="value => handleBillingLimitInput(row, rangeIndex, 'upperLimit', value)"
                  />
                  <el-link
                    v-if="canEditBillingLimit(row)"
                    type="primary"
                    @click="addBillingLimitRange(row, rangeIndex)"
                  >
                    添加
                  </el-link>
                  <el-button
                    v-if="canEditBillingLimit(row) && getBillingLimitRanges(row).length > 1"
                    type="primary"
                    text
                    @click="removeBillingLimitRange(row, rangeIndex)"
                  >
                    删除
                  </el-button>
                </div>
              </div>
            </template>
          </el-table-column>
          <el-table-column label="保底计费重量" width="240" align="center">
            <template #default="{ row }">
              <el-input
                v-model="row.minimumBillingWeight"
                :disabled="!canEditMinimumBillingWeight(row)"
                :placeholder="
                  canEditMinimumBillingWeight(row)
                    ? '实际计量值小于保底数值时按保底计费'
                    : '仅按重量、按吨·公里可填写'
                "
                @input="value => handleBillingDecimalInput(row, 'minimumBillingWeight', value)"
              />
            </template>
          </el-table-column>
          <el-table-column label="备注" width="180" align="center">
            <template #default="{ row }">
              <el-input
                v-model="row.remark"
                maxlength="100"
                show-word-limit
                :disabled="dialogReadonly"
                placeholder="请输入"
              />
            </template>
          </el-table-column>
          <el-table-column label="操作" width="230" align="center" fixed="right">
            <template #default="{ row, $index }">
              <el-link type="primary" @click="openBillingMatch(row, $index)">
                设置匹配条件
              </el-link>
              <el-link v-if="!dialogReadonly" type="primary" @click="copyBillingRule(row)">
                复制
              </el-link>
              <el-link
                v-if="!dialogReadonly"
                type="primary"
                @click="removeBillingRule($index)"
              >
                删除
              </el-link>
            </template>
          </el-table-column>
        </el-table>
      </div>

      <template #footer>
        <div class="business-crud-page__dialog-footer">
          <el-button type="primary" :disabled="dialogReadonly" @click="submitBillingPlan">
            提交
          </el-button>
          <el-button @click="billingPlanBox = false">取消</el-button>
        </div>
      </template>
    </el-dialog>

    <el-dialog title="设置匹配条件" append-to-body v-model="billingMatchBox" width="720px">
      <el-form
        :model="billingMatchForm"
        label-position="right"
        label-width="90px"
        class="business-crud-page__match-form"
      >
        <el-row :gutter="48">
          <el-col :span="12">
            <el-form-item label="起运地">
              <el-cascader
                v-model="billingMatchForm.originPath"
                :options="billingMatchRegionOptions"
                :props="billingMatchRegionCascaderProps"
                :placeholder="billingMatchForm.origin || '请选择起运地'"
                :loading="billingMatchRegionLoading"
                :disabled="dialogReadonly"
                clearable
                filterable
                @visible-change="visible => visible && ensureBillingMatchRegionOptions()"
                @change="value => handleBillingMatchRegionChange('origin', value)"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="目的地">
              <el-cascader
                v-model="billingMatchForm.destinationPath"
                :options="billingMatchRegionOptions"
                :props="billingMatchRegionCascaderProps"
                :placeholder="billingMatchForm.destination || '请选择目的地'"
                :loading="billingMatchRegionLoading"
                :disabled="dialogReadonly"
                clearable
                filterable
                @visible-change="visible => visible && ensureBillingMatchRegionOptions()"
                @change="value => handleBillingMatchRegionChange('destination', value)"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="运输方式">
              <el-select
                v-model="billingMatchForm.transportMode"
                :disabled="dialogReadonly"
                clearable
              >
                <el-option label="公路" value="公路" />
                <el-option label="铁路" value="铁路" />
                <el-option label="水路" value="水路" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="货物类型">
              <el-cascader
                v-model="billingMatchForm.cargoTypePath"
                :options="billingCargoTypeOptions"
                :props="billingCargoTypeCascaderProps"
                :placeholder="billingMatchForm.cargoType || '请选择货物类型'"
                :loading="billingCargoTypeLoading"
                :disabled="dialogReadonly"
                clearable
                filterable
                :filter-method="filterBillingCargoType"
                @visible-change="visible => visible && ensureBillingCargoTypeOptions()"
                @change="handleBillingMatchCargoTypeChange"
              />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <el-button @click="billingMatchBox = false">取消</el-button>
        <el-button type="primary" :disabled="dialogReadonly" @click="saveBillingMatch">
          保存
        </el-button>
      </template>
    </el-dialog>

    <el-dialog
      title="选择线路"
      append-to-body
      v-model="transportRouteBox"
      width="1280px"
      @opened="loadTransportRouteList"
    >
      <div class="business-crud-page__dialog-search">
        <el-form :model="transportRouteQuery" label-position="right" label-width="86px">
          <el-row :gutter="16">
            <el-col :span="6">
              <el-form-item label="线路名称">
                <el-input v-model="transportRouteQuery.routeName" clearable placeholder="请输入" />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="发货地址">
                <el-input
                  v-model="transportRouteQuery.departureAddress"
                  clearable
                  placeholder="请输入"
                />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="收货地址">
                <el-input
                  v-model="transportRouteQuery.arrivalAddress"
                  clearable
                  placeholder="请输入"
                />
              </el-form-item>
            </el-col>
            <el-col :span="6" class="business-crud-page__dialog-search-actions">
              <el-button type="primary" @click="handleTransportRouteSearch">查询</el-button>
              <el-button @click="handleTransportRouteReset">重置</el-button>
            </el-col>
          </el-row>
        </el-form>
      </div>
      <el-table border height="320" :data="transportRouteRows" v-loading="transportRouteLoading">
        <el-table-column type="index" label="序号" width="70" align="center" />
        <el-table-column prop="routeName" label="线路名称" min-width="150" />
        <el-table-column prop="routeCode" label="线路编号" min-width="140" />
        <el-table-column prop="departureName" label="发货地" min-width="140" />
        <el-table-column
          prop="departureAddress"
          label="发货地址"
          min-width="220"
          show-overflow-tooltip
        />
        <el-table-column prop="departureContact" label="发货联系人" min-width="120" />
        <el-table-column prop="departurePhone" label="发货联系方式" min-width="140" />
        <el-table-column prop="arrivalName" label="收货地" min-width="140" />
        <el-table-column
          prop="arrivalAddress"
          label="收货地址"
          min-width="220"
          show-overflow-tooltip
        />
        <el-table-column prop="arrivalContact" label="收货联系人" min-width="120" />
        <el-table-column prop="arrivalPhone" label="收货联系方式" min-width="140" />
        <el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip />
        <el-table-column prop="deptName" label="组织" min-width="150" show-overflow-tooltip />
        <el-table-column
          prop="updateUserName"
          label="更新人"
          min-width="120"
          :formatter="formatTransportRouteUpdateUserName"
        />
        <el-table-column prop="updateTime" label="更新时间" min-width="160"  sortable/>
        <el-table-column prop="createTime" label="创建时间" min-width="160"  sortable/>
        <el-table-column label="操作" width="110" fixed="right">
          <template #default="{ row }">
            <el-link type="primary" @click.stop="selectTransportRouteRow(row)">
              选择
            </el-link>
          </template>
        </el-table-column>
      </el-table>
      <div class="business-crud-page__dialog-pagination">
        <el-pagination
          v-model:current-page="transportRoutePage.currentPage"
          v-model:page-size="transportRoutePage.pageSize"
          :page-sizes="[10, 20, 50, 100]"
          layout="total, prev, pager, next, sizes"
          :total="transportRoutePage.total"
          @size-change="handleTransportRouteSizeChange"
          @current-change="handleTransportRouteCurrentChange"
        />
      </div>
      <template #footer>
        <el-button @click="transportRouteBox = false">关闭</el-button>
      </template>
    </el-dialog>

    <el-dialog
      title="选择常用地址"
      append-to-body
      v-model="transportAddressBox"
      width="1280px"
      @opened="loadTransportAddressList"
    >
      <div class="business-crud-page__dialog-search">
        <el-form :model="transportAddressQuery" label-position="right" label-width="86px">
          <el-row :gutter="16">
            <el-col :span="6">
              <el-form-item label="地址名称">
                <el-input
                  v-model="transportAddressQuery.addressName"
                  clearable
                  placeholder="请输入"
                />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="地址类型">
                <el-select
                  v-model="transportAddressQuery.addressType"
                  clearable
                  filterable
                  placeholder="请选择"
                >
                  <el-option
                    v-for="item in addressTypeOptions"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  />
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="详细地址">
                <el-input
                  v-model="transportAddressQuery.detailAddress"
                  clearable
                  placeholder="请输入"
                />
              </el-form-item>
            </el-col>
            <el-col :span="6" class="business-crud-page__dialog-search-actions">
              <el-button type="primary" @click="handleTransportAddressSearch">查询</el-button>
              <el-button @click="handleTransportAddressReset">重置</el-button>
            </el-col>
          </el-row>
        </el-form>
      </div>
      <el-table
        border
        height="320"
        :data="transportAddressRows"
        v-loading="transportAddressLoading"
      >
        <el-table-column type="index" label="序号" width="70" align="center" />
        <el-table-column prop="addressName" label="地址名称" min-width="150" />
        <el-table-column prop="addressCode" label="地址编号" min-width="130" />
        <el-table-column prop="addressType" label="类型" min-width="120" />
        <el-table-column
          prop="siteCodeDisplay"
          label="站点编码"
          min-width="120"
          :formatter="formatTransportAddressSiteCode"
        />
        <el-table-column
          prop="detailAddress"
          label="详细地址"
          min-width="260"
          show-overflow-tooltip
        />
        <el-table-column prop="longitude" label="经度" min-width="120" />
        <el-table-column prop="latitude" label="纬度" min-width="120" />
        <el-table-column prop="regionName" label="行政区划" min-width="150" show-overflow-tooltip />
        <el-table-column prop="contactName" label="联系人" min-width="120" />
        <el-table-column prop="contactPhone" label="联系方式" min-width="140" />
        <el-table-column prop="deptName" label="组织" min-width="150" show-overflow-tooltip />
        <el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip />
        <el-table-column prop="updateTime" label="更新时间" min-width="160"  sortable/>
        <el-table-column prop="createTime" label="创建时间" min-width="160"  sortable/>
        <el-table-column label="操作" width="110" fixed="right">
          <template #default="{ row }">
            <el-link type="primary" @click.stop="selectTransportAddressRow(row)">
              选择
            </el-link>
          </template>
        </el-table-column>
      </el-table>
      <div class="business-crud-page__dialog-pagination">
        <el-pagination
          v-model:current-page="transportAddressPage.currentPage"
          v-model:page-size="transportAddressPage.pageSize"
          :page-sizes="[10, 20, 50, 100]"
          layout="total, prev, pager, next, sizes"
          :total="transportAddressPage.total"
          @size-change="handleTransportAddressSizeChange"
          @current-change="handleTransportAddressCurrentChange"
        />
      </div>
      <template #footer>
        <el-button @click="transportAddressBox = false">关闭</el-button>
      </template>
    </el-dialog>

    <el-dialog
      title="选择站点"
      append-to-body
      v-model="transportStationBox"
      width="1180px"
      @opened="loadTransportStationList"
    >
      <div class="business-crud-page__dialog-search">
        <el-form :model="transportStationQuery" label-position="right" label-width="86px">
          <el-row :gutter="16">
            <el-col :span="6">
              <el-form-item label="站点名称">
                <el-input v-model="transportStationQuery.name" clearable placeholder="请输入" />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="站点类型">
                <el-input :model-value="transportStationTypeLabel" disabled />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="详细地址">
                <el-input
                  v-model="transportStationQuery.detailAddress"
                  clearable
                  placeholder="请输入"
                />
              </el-form-item>
            </el-col>
            <el-col :span="6" class="business-crud-page__dialog-search-actions">
              <el-button type="primary" @click="handleTransportStationSearch">查询</el-button>
              <el-button @click="handleTransportStationReset">重置</el-button>
            </el-col>
          </el-row>
        </el-form>
      </div>
      <el-table
        border
        height="320"
        :data="transportStationRows"
        v-loading="transportStationLoading"
      >
        <el-table-column type="index" label="序号" width="70" align="center" />
        <el-table-column prop="stationName" label="站点名" min-width="160" />
        <el-table-column prop="stationType" label="类型" min-width="120" />
        <el-table-column prop="stationCode" label="站点编码" min-width="130" />
        <el-table-column
          prop="detailAddress"
          label="详细地址"
          min-width="260"
          show-overflow-tooltip
        />
        <el-table-column prop="longitude" label="经度" min-width="120" />
        <el-table-column prop="latitude" label="纬度" min-width="120" />
        <el-table-column prop="regionName" label="行政区划" min-width="160" show-overflow-tooltip />
        <el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip />
        <el-table-column prop="updateTime" label="更新时间" min-width="160"  sortable/>
        <el-table-column prop="createTime" label="创建时间" min-width="160"  sortable/>
        <el-table-column label="操作" width="110" fixed="right">
          <template #default="{ row }">
            <el-link type="primary" @click.stop="selectTransportStationRow(row)">
              选择
            </el-link>
          </template>
        </el-table-column>
      </el-table>
      <div class="business-crud-page__dialog-pagination">
        <el-pagination
          v-model:current-page="transportStationPage.currentPage"
          v-model:page-size="transportStationPage.pageSize"
          :page-sizes="[10, 20, 50, 100]"
          layout="total, prev, pager, next, sizes"
          :total="transportStationPage.total"
          @size-change="handleTransportStationSizeChange"
          @current-change="handleTransportStationCurrentChange"
        />
      </div>
      <template #footer>
        <el-button @click="transportStationBox = false">关闭</el-button>
      </template>
    </el-dialog>

    <el-dialog
      title="地图选择地址"
      append-to-body
      v-model="transportMapBox"
      width="920px"
      @opened="initTransportAmap"
    >
      <div class="business-crud-page__map-toolbar">
        <el-input
          v-model="transportMapKeyword"
          clearable
          placeholder="输入地址关键词"
          @keyup.enter="searchTransportMapKeyword"
        />
        <el-button
          type="primary"
          :icon="Search"
          :loading="transportMapLoading"
          @click="searchTransportMapKeyword"
        >
          搜索
        </el-button>
      </div>
      <div ref="transportAmap" class="business-crud-page__map"></div>
      <div class="business-crud-page__map-info">
        <span>{{ transportMapStatus }}</span>
        <span v-if="transportMapSelected.regionName"
          >行政区划：{{ transportMapSelected.regionName }}</span
        >
      </div>
      <template #footer>
        <el-button @click="transportMapBox = false">取消</el-button>
        <el-button
          type="primary"
          :disabled="!transportMapSelected.longitude"
          @click="confirmTransportMapPick"
        >
          确定
        </el-button>
      </template>
    </el-dialog>

    <el-dialog
      title="选择常用货物"
      append-to-body
      v-model="commonCargoBox"
      width="1280px"
      @opened="loadCommonCargoList"
    >
      <div class="business-crud-page__dialog-search">
        <el-form :model="commonCargoQuery" label-position="right" label-width="100px">
          <el-row :gutter="16">
            <el-col :span="6">
              <el-form-item label="货物名称">
                <el-input v-model="commonCargoQuery.cargoName" clearable placeholder="请输入" />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="货物类型">
                <el-cascader
                  v-model="commonCargoQuery.cargoTypePath"
                  :options="billingCargoTypeOptions"
                  :props="billingCargoTypeCascaderProps"
                  placeholder="请选择"
                  clearable
                  filterable
                  :filter-method="filterBillingCargoType"
                  @visible-change="visible => visible && ensureBillingCargoTypeOptions()"
                  @change="handleCommonCargoTypeChange"
                />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="规格型号">
                <el-input
                  v-model="commonCargoQuery.specificationModel"
                  clearable
                  placeholder="请输入"
                />
              </el-form-item>
            </el-col>
            <el-col :span="6" class="business-crud-page__dialog-search-actions">
              <el-button type="primary" @click="handleCommonCargoSearch">查询</el-button>
              <el-button @click="handleCommonCargoReset">重置</el-button>
            </el-col>
          </el-row>
        </el-form>
      </div>
      <el-table border height="320" :data="commonCargoRows" v-loading="commonCargoLoading">
        <el-table-column type="index" label="序号" width="70" align="center" />
        <el-table-column prop="cargoName" label="货物名称" min-width="150" />
        <el-table-column prop="cargoCode" label="货物编号" min-width="130" />
        <el-table-column prop="firstCargoTypeName" label="一级货物类型" min-width="150" />
        <el-table-column prop="secondCargoTypeName" label="二级货物类型" min-width="150" />
        <el-table-column prop="secondCargoTypeCode" label="二级货物编码" min-width="140" />
        <el-table-column prop="packageType" label="包装" min-width="100" />
        <el-table-column prop="brand" label="品牌" min-width="120" />
        <el-table-column prop="specification" label="规格" min-width="120" />
        <el-table-column prop="model" label="型号" min-width="120" />
        <el-table-column prop="cargoValue" label="单价" min-width="120" />
        <el-table-column prop="priceUnit" label="计价单位" min-width="120" />
        <el-table-column
          prop="cargoSize"
          label="尺寸"
          min-width="120"
          :formatter="formatCommonCargoSize"
        />
        <el-table-column
          prop="descriptionOne"
          label="其他说明1"
          min-width="160"
          show-overflow-tooltip
        />
        <el-table-column
          prop="descriptionTwo"
          label="其他说明2"
          min-width="160"
          show-overflow-tooltip
        />
        <el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip />
        <el-table-column prop="deptName" label="组织" min-width="150" show-overflow-tooltip />
        <el-table-column prop="updateTime" label="更新时间" min-width="160"  sortable/>
        <el-table-column prop="createTime" label="创建时间" min-width="160"  sortable/>
        <el-table-column label="操作" width="110" fixed="right">
          <template #default="{ row }">
            <el-link type="primary" @click.stop="selectCommonCargoRow(row)">
              选择
            </el-link>
          </template>
        </el-table-column>
      </el-table>
      <div class="business-crud-page__dialog-pagination">
        <el-pagination
          v-model:current-page="commonCargoPage.currentPage"
          v-model:page-size="commonCargoPage.pageSize"
          :page-sizes="[10, 20, 50, 100]"
          layout="total, prev, pager, next, sizes"
          :total="commonCargoPage.total"
          @size-change="handleCommonCargoSizeChange"
          @current-change="handleCommonCargoCurrentChange"
        />
      </div>
      <template #footer>
        <el-button @click="commonCargoBox = false">关闭</el-button>
      </template>
    </el-dialog>

    <el-dialog title="导入货物" append-to-body v-model="cargoImportBox" width="560px">
      <el-upload
        drag
        action="#"
        accept=".xls,.xlsx"
        :auto-upload="false"
        :show-file-list="false"
        :disabled="cargoImportLoading"
        :on-change="handleCargoImportFileChange"
      >
        <i class="el-icon-upload"></i>
        <div class="el-upload__text">将 Excel 文件拖到此处，或点击上传</div>
        <template #tip>
          <div class="el-upload__tip">
            按货物信息表头读取：货物名称、货物类型、数量、数量单位、包装、品牌、规格、型号、物料编码、设备编码、备注
          </div>
        </template>
      </el-upload>
      <template #footer>
        <el-button @click="cargoImportBox = false">关闭</el-button>
      </template>
    </el-dialog>

    <el-dialog :title="`${config.title}导入`" append-to-body v-model="excelBox" width="555px">
      <avue-form :option="excelOptionConfig" v-model="excelForm">
        <template #excelTemplate>
          <el-button type="primary" @click="handleTemplate">
            点击下载<i class="el-icon-download el-icon--right"></i>
          </el-button>
        </template>
      </avue-form>
    </el-dialog>
  </basic-container>
</template>

<script>
import { exportBlob } from '@/api/common';
import { getList as getCommonAddressList } from '@/api/base/common-address';
import { getList as getAirportMasterList } from '@/api/base/airport-master';
import { getList as getCargoTypeList } from '@/api/base/cargo-type';
import { getList as getFeeItemList } from '@/api/base/fee-item';
import { getList as getPortTerminalList } from '@/api/base/port-terminal';
import { getList as getRailwayStationList } from '@/api/base/railway-station';
import { getLazyTree as getRegionLazyTree } from '@/api/base/region';
import { getList as getCommonCargoList } from '@/api/business/common-cargo';
import { getList as getContractList } from '@/api/business/contract-manage';
import { getList as getProjectList } from '@/api/business/project-apply';
import { getList as getCommonRouteList } from '@/api/business/common-route';
import { getList as getTransportPlanList } from '@/api/business/transport-plan';
import { getList as getCarrierCustomerList } from '@/api/vehicle/customer-archive';
import { getList as getDriverList } from '@/api/transportCapacity/driver';
import { getDictionary } from '@/api/system/dictbiz';
import { getDeptTree } from '@/api/system/dept';
import { addressTypeOptions } from '@/option/base/common-address';
import { packageOptions } from '@/option/business/common';
import { formatUpdateUserName } from '@/utils/audit';
import { getToken } from '@/utils/auth';
import { openImportDialog } from '@/utils/import-excel';
import { applyTableMenuWidth } from '@/utils/table-menu';
import { downloadFileByUrl, downloadXls } from '@/utils/util';
import { List, Location, Search } from '@element-plus/icons-vue';
import { mapGetters } from 'vuex';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
import WaybillImportDialog from './waybill-import-dialog.vue';

const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
let amapLoader;

const estimateMenuButtonCount = config => {
  const rowActions = (config.actions || []).filter(action => action !== 'batchComplete');
  return 3 + rowActions.length + (config.operations || []).length;
};

const extractRecords = res => {
  const data = res?.data?.data || res?.data || res;
  if (Array.isArray(data)) return data;
  if (Array.isArray(data?.records)) return data.records;
  if (Array.isArray(data?.data)) return data.data;
  if (Array.isArray(data?.data?.records)) return data.data.records;
  return [];
};

const transportPlanImportColumns = [
  ['计划名称', 'planName'],
  ['运输方式', 'transportType'],
  ['计划开始日期', 'planStartDate'],
  ['计划结束日期', 'planEndDate'],
  ['货物名称', 'cargoName'],
  ['货物类型', 'cargoType'],
  ['数量', 'quantity'],
  ['数量单位', 'quantityUnit'],
  ['包装', 'packageType'],
  ['品牌', 'brand'],
  ['规格', 'specification'],
  ['型号', 'model'],
  ['物料编码', 'materialCode'],
  ['设备编码', 'deviceCode'],
  ['发货地址', 'departureAddress'],
  ['发货联系人', 'departureContact'],
  ['发货联系方式', 'departurePhone'],
  ['收货地址', 'arrivalAddress'],
  ['收货联系人', 'arrivalContact'],
  ['收货联系方式', 'arrivalPhone'],
  ['备注', 'remark'],
];

const defaultBillingRule = () => ({
  feeType: '',
  feeItem: '',
  billingElement: '',
  billingType: '',
  billingUnit: '',
  unitPrice: '',
  lowerLimit: '',
  upperLimit: '',
  limitRanges: [{ lowerLimit: '', upperLimit: '' }],
  minimumBillingWeight: '',
  remark: '',
  matchCondition: {
    origin: '',
    originCode: '',
    originPath: [],
    destination: '',
    destinationCode: '',
    destinationPath: [],
    transportMode: '',
    cargoType: '',
    cargoTypeCode: '',
    cargoTypePath: [],
  },
});

const defaultBillingPlan = () => ({
  planName: '',
  defaultPlan: false,
  remark: '',
  rules: [defaultBillingRule()],
});

const defaultSettlementRule = () => ({
  autoGenerate: 1,
  billStartDate: '',
  settlementType: '月结',
  billCycleType: '固定截单日',
  billCutoffDay: 25,
  cycleDays: '',
});

const defaultReconciliation = () => ({
  skipReconciliation: 1,
  reconciliationMode: '明细逐行核对',
});

const defaultTransportCargo = () => ({
  cargoName: '',
  cargoType: '',
  cargoTypeCode: '',
  cargoTypePath: [],
  quantity: '',
  quantityUnit: '',
  packageType: '',
  brand: '',
  specification: '',
  model: '',
  materialCode: '',
  deviceCode: '',
  remark: '',
});

const defaultDispatchRow = () => ({
  taskEntryMode: 'simple',
  transportType: '',
  carrierType: '承运商',
  carrierId: '',
  carrierName: '',
  driverId: '',
  driverName: '',
  driverPhone: '',
  vehicleNo: '',
  trailerVehicleNo: '',
  escortName: '',
  escortPhone: '',
  mileage: '',
  cargoType: '',
  cargoName: '',
  cargoInfo: '',
  quantity: '',
  quantityUnit: '吨',
  specification: '',
  model: '',
  packageType: '',
  brand: '',
  materialCode: '',
  unitPrice: '',
  priceUnit: '元/吨',
  otherFeeTotal: '',
  estimatedStartTime: '',
  estimatedEndTime: '',
  departureAddress: '',
  departureName: '',
  departureContact: '',
  departurePhone: '',
  arrivalAddress: '',
  arrivalName: '',
  arrivalContact: '',
  arrivalPhone: '',
  dispatchUserName: '',
  dispatchTime: '',
  attachmentsJson: '',
  remark: '',
});

const taskCarrierTypes = ['承运商', '自运', '网货平台'];

export default {
  components: { WaybillImportDialog },
  name: 'BusinessCrudPage',
  props: {
    api: {
      type: Object,
      required: true,
    },
    config: {
      type: Object,
      required: true,
    },
    crudOption: {
      type: Object,
      required: true,
    },
    crudExcelOption: {
      type: Object,
      default: () => ({ column: [] }),
    },
    // 可选：固定操作列宽度；不传则按按钮数量自动估算（沿用既有逻辑）
    menuWidth: {
      type: Number,
      default: null,
    },
  },
  data() {
    return {
      List,
      Location,
      Search,
      form: {},
      suppressTransportTypeClear: false,
      query: {},
      loading: true,
      data: [],
      allDept: 0,
      dialogReadonly: false,
      detailBox: false,
      detailLoading: false,
      detailRow: {},
      waybillImportBox: false,
      transportPlanImportBox: false,
      transportPlanImportLoading: false,
      transportPlanImportFiles: [],
      transportPlanImportSourceFile: null,
      transportPlanImportRows: [],
      transportPlanImportSelection: [],
      transportPlanImportTab: 'all',
      transportPlanImportEditingKey: '',
      transportPlanImportEditSnapshot: null,
      transportPlanImportContractOptions: [],
      transportPlanImportContractLoading: false,
      transportPlanImportForm: {
        projectId: '',
        projectName: '',
        customerName: '',
        contractId: '',
        contractName: '',
        file: null,
      },
      transportPlanImportRules: {
        projectId: [{ required: true, message: '请选择项目', trigger: 'change' }],
        customerName: [{ required: true, message: '请选择项目后带出客户', trigger: 'change' }],
        contractId: [{ required: true, message: '请选择项目后带出客户合同', trigger: 'change' }],
        file: [{ required: true, message: '请上传计划明细表', trigger: 'change' }],
      },
      draftSaveLoading: false,
      crudDialogType: '',
      defaultDialogButtons: null,
      contractCreateActionLoading: '',
      transportPlanCreateActionLoading: '',
      option: (() => {
        const opt = this.cloneOption(this.crudOption, estimateMenuButtonCount(this.config));
        if (this.menuWidth != null) opt.menuWidth = this.menuWidth;
        return opt;
      })(),
      selectedProjectId: '',
      projectOptions: [],
      projectLoading: false,
      contractOptions: [],
      contractLoading: false,
      shippingPlanOptions: [],
      shippingPlanLoading: false,
      selectedOrganizationId: '',
      organizationOptions: [],
      organizationFlatOptions: [],
      organizationLoading: false,
      addressTypeOptions,
      flowBox: false,
      flowUrl: '',
      processInstanceId: '',
      dispatchBox: false,
      dispatchLoading: false,
      dispatchRow: {},
      dispatchRows: [],
      dispatchSaving: '',
      dispatchItemBox: false,
      dispatchItemIndex: -1,
      dispatchItemForm: defaultDispatchRow(),
      dispatchItemAttachmentRows: [],
      attachmentRows: [],
      contractFileRows: [],
      transportCargoRows: [],
      transportRouteBox: false,
      transportRouteLoading: false,
      transportRouteQuery: {},
      transportRouteRows: [],
      transportRouteSelected: null,
      transportRoutePage: {
        pageSize: 10,
        currentPage: 1,
        total: 0,
      },
      transportAddressBox: false,
      transportAddressTarget: '',
      transportAddressLoading: false,
      transportAddressQuery: {},
      transportAddressRows: [],
      transportAddressSelected: null,
      transportAddressPage: {
        pageSize: 10,
        currentPage: 1,
        total: 0,
      },
      transportStationBox: false,
      transportStationTarget: '',
      transportStationLoading: false,
      transportStationQuery: {},
      transportStationRows: [],
      transportStationPage: {
        pageSize: 10,
        currentPage: 1,
        total: 0,
      },
      transportMapBox: false,
      transportMapTarget: '',
      transportMapLoading: false,
      transportMapKeyword: '',
      transportMapStatus: '可搜索地址或点击地图选点',
      transportMapSelected: {},
      transportAmap: null,
      transportAmapMarker: null,
      transportAmapGeocoder: null,
      commonCargoBox: false,
      commonCargoLoading: false,
      commonCargoQuery: {},
      commonCargoRows: [],
      commonCargoSelected: [],
      commonCargoPage: {
        pageSize: 10,
        currentPage: 1,
        total: 0,
      },
      taskCarrierTypes,
      taskCarrierOptions: [],
      taskCarrierLoading: false,
      taskDriverOptions: [],
      taskDriverLoading: false,
      taskCargoOptionsMap: {},
      taskCargoLoadingMap: {},
      taskQuantityUnitOptions: [],
      taskQuantityUnitLoading: false,
      taskFeeUnitOptions: [],
      taskFeeUnitLoading: false,
      cargoImportBox: false,
      cargoImportLoading: false,
      billingPlanRows: [],
      settlementRuleForm: defaultSettlementRule(),
      settlementRuleErrors: {},
      reconciliationForm: defaultReconciliation(),
      changeRecordRows: [],
      billingPlanBox: false,
      billingPlanMode: 'add',
      billingPlanIndex: -1,
      billingPlanForm: defaultBillingPlan(),
      billingFeeCategoryOptions: [],
      billingFeeCategoryLoading: false,
      billingFeeCategoryRequest: null,
      billingFeeItemOptionsMap: {},
      billingFeeItemLoadingMap: {},
      billingFeeItemRequestMap: {},
      billingUnitOptions: [],
      billingUnitLoading: false,
      billingUnitRequest: null,
      billingMatchBox: false,
      billingMatchRuleIndex: -1,
      billingMatchForm: defaultBillingRule().matchCondition,
      billingMatchRegionOptions: [],
      billingMatchRegionLoading: false,
      billingMatchRegionRequest: null,
      billingCargoTypeOptions: [],
      billingCargoTypeFlatOptions: [],
      billingCargoTypeLoading: false,
      billingCargoTypeRequest: null,
      billingPlanRules: {
        planName: [{ required: true, message: '请输入方案名称', trigger: 'blur' }],
      },
      billingElementOptions: [
        '按重量',
        '按体积',
        '按车辆',
        '按里程',
        '按吨·公里',
        '固定金额（整单一口价）',
        '按数量',
      ],
      billingElementTypeMap: {
        按重量: ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
        按体积: ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
        按车辆: ['固定单价'],
        按里程: ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
        '按吨·公里': ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
        '固定金额（整单一口价）': ['固定一口价'],
        按数量: ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
      },
      settlementTypeOptions: ['月结', '日结', '周结', '半月结', '固定天数周期结算'],
      billCycleTypeOptions: ['固定截单日', '自然月'],
      reconciliationModeOptions: ['明细逐行核对', '按账单汇总核对', '跳过自动核对'],
      packageOptions,
      quantityUnitOptions: ['吨', '千克', '立方米', '件', '车', '箱', '托盘'],
      attachmentFileTypes: [
        'pdf',
        'bmp',
        'jpeg',
        'png',
        'jpg',
        'doc',
        'docx',
        'ppt',
        'pptx',
        'xlsx',
        'xls',
        'eml',
        'msg',
        'zip',
      ],
      excelBox: false,
      excelForm: {},
      excelOptionConfig: this.cloneOption(this.crudExcelOption),
      page: {
        pageSize: 10,
        pageSizes: [10, 20, 50, 100],
        currentPage: 1,
        total: 0,
      },
      selectionList: [],
      contractExpiryScope: '',
      contractSignType: '',
      searchExpanded: false,
      attachmentUploadMode: false,
      contractExpiryStats: {
        all: 0,
        within30: 0,
        within90: 0,
        over90: 0,
        expired: 0,
      },
    };
  },
  created() {
    if (this.config.enableAllDept && this.isAdmin) {
      this.allDept = 1;
    }
    if (this.config.enableProjectSelect) {
      this.loadProjectOptions();
    }
    if (this.config.enableOrganizationSelect) {
      this.loadOrganizationOptions();
    }
    if (this.config.enableBillingPlan) {
      this.ensureBillingFeeCategoryOptions();
      this.ensureBillingUnitOptions();
    }
    if (this.config.enableTaskInfoForm) {
      this.loadTaskQuantityUnitOptions();
      this.loadTaskFeeUnitOptions();
      this.ensureBillingCargoTypeOptions();
    }
    this.consumeTemplateCreatePayload();
  },
  computed: {
    ...mapGetters(['permission', 'userInfo']),
    permissionList() {
      return {
        addBtn: this.canCreate,
      };
    },
    detailSections() {
      return this.config.detailSections || [];
    },
    transportPlanImportDuplicateCount() {
      return this.transportPlanImportRows.filter(row => row._duplicate).length;
    },
    transportPlanImportVisibleRows() {
      return this.transportPlanImportTab === 'duplicate'
        ? this.transportPlanImportRows.filter(row => row._duplicate)
        : this.transportPlanImportRows;
    },
    actionButtonLikeSearch() {
      return this.config.actionButtonLikeSearch === true;
    },
    contractSignTypeOptions() {
      return this.option.column?.find(column => column.prop === 'signType')?.dicData || [];
    },
    contractExpiryTagOptions() {
      return [
        { label: '全部', value: '', statKey: 'all' },
        { label: '30天内到期', value: 'within30', statKey: 'within30' },
        { label: '90天内到期', value: 'within90', statKey: 'within90' },
        { label: '90天以上', value: 'over90', statKey: 'over90' },
        { label: '已到期', value: 'expired', statKey: 'expired' },
      ];
    },
    contractSelectEnabled() {
      return this.config.enableContractSelect || this.config.enableTransportPlanForm;
    },
    shippingPlanSelectEnabled() {
      return this.config.enableShippingPlanSelect === true;
    },
    shippingInfoFormEnabled() {
      return this.config.enableShippingInfoForm || this.config.enableTransportPlanForm;
    },
    taskInfoFormEnabled() {
      return this.config.enableTaskInfoForm === true;
    },
    taskEntryModeOptions() {
      return [
        { label: '精简录入', value: 'simple' },
        { label: '完整录入', value: 'full' },
      ];
    },
    isTaskFullMode() {
      return this.form.taskEntryMode === 'full';
    },
    isCarrierMode() {
      return this.form.carrierType === '承运商';
    },
    taskCargoOptions() {
      return this.getTaskCargoOptionsByPath(this.form.cargoTypePath);
    },
    taskCargoLoading() {
      return this.isTaskCargoOptionsLoading(this.form.cargoTypePath);
    },
    taskCargoQuantityTotal() {
      const total = this.transportCargoRows.reduce((sum, row) => {
        const value = Number(row.quantity);
        return Number.isFinite(value) ? sum + value : sum;
      }, 0);
      if (!total) return '';
      return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(3)));
    },
    dispatchPlanGoodsRows() {
      return this.parseJsonArray(this.dispatchRow.goodsJson);
    },
    dispatchPlanCargoInfo() {
      const cargoInfo = this.dispatchPlanGoodsRows
        .map(row => this.formatDispatchCargoInfo({}, row))
        .filter(Boolean)
        .join('; ');
      return cargoInfo || this.dispatchRow.goodsInfo || '';
    },
    dispatchSummaryTotalQuantity() {
      const goodsQuantity = this.dispatchPlanGoodsRows.reduce(
        (total, row) =>
          total +
          this.parseDispatchQuantity(row.quantity || row.cargoQuantity || row.goodsQuantity),
        0
      );
      if (goodsQuantity > 0) return goodsQuantity;
      return this.parseDispatchQuantity(
        this.dispatchRow.totalQuantity ||
          this.dispatchRow.totalCargoQuantity ||
          this.dispatchRow.quantity
      );
    },
    dispatchSummaryAssignedQuantity() {
      return this.dispatchRows.reduce(
        (total, row) => total + this.parseDispatchQuantity(row.quantity),
        0
      );
    },
    dispatchSummaryRemainingQuantity() {
      const total = this.dispatchSummaryTotalQuantity;
      const assigned = this.dispatchSummaryAssignedQuantity;
      const remain = total - assigned;
      return remain > 0 ? remain : 0;
    },
    dispatchSummaryUnit() {
      const unitRow = this.dispatchPlanGoodsRows.find(
        row => row.quantityUnit || row.goodsQuantityUnit || row.unit
      );
      return (
        unitRow?.quantityUnit ||
        unitRow?.goodsQuantityUnit ||
        unitRow?.unit ||
        this.dispatchRow.quantityUnit ||
        this.dispatchRows.find(row => row.quantityUnit)?.quantityUnit ||
        '吨'
      );
    },
    dispatchSummaryText() {
      const total = this.formatDispatchQuantity(this.dispatchSummaryTotalQuantity);
      const assigned = this.formatDispatchQuantity(this.dispatchSummaryAssignedQuantity);
      const remain = this.formatDispatchQuantity(this.dispatchSummaryRemainingQuantity);
      return `共${total}${this.dispatchSummaryUnit} | 已调度 ${assigned}${this.dispatchSummaryUnit}，剩余${remain}${this.dispatchSummaryUnit}`;
    },
    dispatchAttachmentRows() {
      return this.parseJsonArray(this.dispatchRow.attachmentsJson);
    },
    dispatchRouteStart() {
      const address = this.dispatchRow.departureAddress || this.dispatchRow.departureName || '';
      return {
        label: this.dispatchRow.departureName || '起',
        address,
        detail: this.dispatchRow.departureDetailAddress || address,
        contact: [this.dispatchRow.departureContact, this.dispatchRow.departurePhone]
          .filter(Boolean)
          .join(' - '),
      };
    },
    dispatchRouteEnd() {
      const address = this.dispatchRow.arrivalAddress || this.dispatchRow.arrivalName || '';
      return {
        label: this.dispatchRow.arrivalName || '终',
        address,
        detail: this.dispatchRow.arrivalDetailAddress || address,
        contact: [this.dispatchRow.arrivalContact, this.dispatchRow.arrivalPhone]
          .filter(Boolean)
          .join(' - '),
      };
    },
    dispatchDialogTitle() {
      return '计划调度';
    },
    dispatchItemDialogTitle() {
      const mode = this.dispatchItemForm.taskEntryMode === 'full' ? '完整录入' : '精简录入';
      return this.dispatchItemIndex < 0 ? `新增/编辑（${mode}）` : `编辑调度信息（${mode}）`;
    },
    dispatchTransportMode() {
      return this.resolveTransportMode(this.dispatchItemForm.transportType);
    },
    dispatchStationMode() {
      return ['water', 'rail', 'air'].includes(this.dispatchTransportMode);
    },
    dispatchStationNamePlaceholder() {
      if (!this.dispatchStationMode) return '行政区划自动带出';
      const labelMap = {
        water: '请选择港口/码头',
        rail: '请选择车站',
        air: '请选择空港',
      };
      return labelMap[this.dispatchTransportMode] || '请选择站点';
    },
    dispatchAddressDetailPlaceholder() {
      return this.dispatchStationMode ? '选择站点后带出详细地址' : '请输入';
    },
    dispatchDialogNo() {
      return this.dispatchRow.planNo || this.dispatchRow.loadingNo || '';
    },
    dispatchDialogStatusText() {
      return this.displayStatus(this.dispatchRow, this.config.statusProp || 'businessStatus');
    },
    taskFreightAmount() {
      const quantity = Number(this.taskCargoQuantityTotal || this.form.quantity || 0);
      const unitPrice = Number(this.form.unitPrice || 0);
      if (!quantity || !unitPrice) return '';
      const amount = quantity * unitPrice;
      return Number.isInteger(amount) ? String(amount) : String(Number(amount.toFixed(2)));
    },
    isAdmin() {
      const authority = this.userInfo && this.userInfo.authority;
      return Array.isArray(authority)
        ? authority.includes('admin')
        : String(authority || '').includes('admin');
    },
    canViewAllDept() {
      return this.isAdmin;
    },
    readonlyScope() {
      return (
        this.config.enableAllDept && this.config.allDeptReadonly !== false && this.allDept === 1
      );
    },
    canCreate() {
      return this.hasPermission(`${this.config.permission}_add`) && !this.readonlyScope;
    },
    showContractCreateActions() {
      return (
        this.config.enableContractCreateActions &&
        this.crudDialogType === 'add' &&
        !this.dialogReadonly
      );
    },
    showTransportPlanCreateActions() {
      return (
        this.config.enableTransportPlanCreateActions &&
        this.crudDialogType === 'add' &&
        !this.dialogReadonly
      );
    },
    organizationCascaderProps() {
      return {
        label: 'label',
        value: 'id',
        children: 'children',
        checkStrictly: true,
        emitPath: false,
      };
    },
    billingMatchRegionCascaderProps() {
      return {
        label: 'title',
        value: 'id',
        children: 'children',
        leaf: 'leaf',
        emitPath: true,
      };
    },
    billingCargoTypeCascaderProps() {
      return {
        label: 'cargoName',
        value: 'id',
        children: 'children',
        disabled: (data, node) => node.level === 1,
        leaf: 'leaf',
        checkStrictly: true,
        emitPath: true,
      };
    },
    transportCargoTypeOptions() {
      return this.billingCargoTypeOptions.filter(item => item.children?.length);
    },
    transportCargoTypeCascaderProps() {
      return {
        label: 'cargoName',
        value: 'id',
        children: 'children',
        emitPath: true,
      };
    },
    settlementRuleEnabled() {
      return Number(this.settlementRuleForm.autoGenerate) === 1;
    },
    showSettlementBillCycleType() {
      return this.settlementRuleForm.settlementType === '月结';
    },
    showSettlementBillCutoffDay() {
      return (
        this.showSettlementBillCycleType && this.settlementRuleForm.billCycleType === '固定截单日'
      );
    },
    showSettlementCycleDays() {
      return this.settlementRuleForm.settlementType === '固定天数周期结算';
    },
    canBatchComplete() {
      return (
        this.hasAction('batchComplete') &&
        this.hasPermission(`${this.config.permission}_batch_complete`) &&
        !this.readonlyScope
      );
    },
    canRoadLoading() {
      const permission =
        this.config.roadLoadingPermission || `${this.config.permission}_road_loading`;
      return this.config.roadLoading && this.hasPermission(permission) && !this.readonlyScope;
    },
    ids() {
      return this.selectionList.map(item => item.id).join(',');
    },
    billCutoffDayOptions() {
      return Array.from({ length: 31 }, (_, index) => {
        const value = index + 1;
        return {
          label: `${value}日`,
          value,
        };
      });
    },
    cycleDayOptions() {
      return Array.from({ length: 365 }, (_, index) => {
        const value = index + 1;
        return {
          label: `${value}天`,
          value,
        };
      });
    },
    transportCargoQuantityTotal() {
      if (!this.transportCargoRows.length) return '系统自动计算';
      const total = this.transportCargoRows.reduce((sum, row) => {
        const value = Number(row.quantity);
        return Number.isFinite(value) ? sum + value : sum;
      }, 0);
      return Number.isInteger(total) ? String(total) : total.toFixed(2);
    },
    transportAddressSelectDisabled() {
      return this.dialogReadonly || this.isBillingFieldEmpty(this.form.transportType);
    },
    transportMode() {
      return this.resolveTransportMode(this.form.transportType);
    },
    transportStationMode() {
      return ['water', 'rail', 'air'].includes(this.transportMode);
    },
    transportStationTypeLabel() {
      const labelMap = {
        water: '港口/码头',
        rail: '铁路车站',
        air: '空港机场',
      };
      return labelMap[this.transportMode] || '';
    },
    transportStationNamePlaceholder() {
      if (!this.transportStationMode) return '行政区划自动带出';
      const labelMap = {
        water: '请选择港口/码头',
        rail: '请选择车站',
        air: '请选择空港',
      };
      return labelMap[this.transportMode] || '请选择站点';
    },
    transportAddressDetailPlaceholder() {
      return this.transportStationMode ? '选择站点后带出详细地址' : '请输入';
    },
  },
  watch: {
    'form.transportType'(value, oldValue) {
      if (!this.shippingInfoFormEnabled || this.suppressTransportTypeClear) return;
      if (String(value ?? '') === String(oldValue ?? '')) return;
      this.handleTransportTypeChange(value);
    },
  },
  methods: {
    cloneOption(option, menuButtonCount) {
      const nextOption = {
        ...option,
        column: (option.column || []).map(column => ({ ...column })),
      };
      return applyTableMenuWidth(nextOption, menuButtonCount);
    },
    hasPermission(code) {
      return this.isAdmin || this.validData(this.permission && this.permission[code], false);
    },
    hasAction(action) {
      return (this.config.actions || []).includes(action);
    },
    statusValue(row) {
      return row[this.config.statusProp || 'status'];
    },
    inStatus(row, statusList) {
      if (!statusList || !statusList.length) {
        return true;
      }
      const status = this.statusValue(row);
      const statusText = this.config.statusTextProp ? row[this.config.statusTextProp] : '';
      return statusList.includes(status) || statusList.includes(statusText);
    },
    isReadonlyEditAllowed(row) {
      return this.inStatus(row, this.config.ignoreReadonlyEditStatus);
    },
    isReadonlyDeleteAllowed(row) {
      return this.inStatus(row, this.config.ignoreReadonlyDeleteStatus);
    },
    canEdit(row) {
      return (
        this.hasPermission(`${this.config.permission}_edit`) &&
        !this.readonlyScope &&
        (!row.readonly || this.isReadonlyEditAllowed(row)) &&
        this.inStatus(row, this.config.editStatus)
      );
    },
    canDelete(row) {
      return (
        this.hasPermission(`${this.config.permission}_delete`) &&
        !this.readonlyScope &&
        (!row.readonly || this.isReadonlyDeleteAllowed(row)) &&
        this.inStatus(row, this.config.deleteStatus) &&
        (!this.config.deleteStage || this.config.deleteStage.includes(row.contractStage))
      );
    },
    canCopy(row) {
      return (
        this.hasAction('copy') &&
        this.hasPermission(`${this.config.permission}_copy`) &&
        !this.readonlyScope &&
        !row.readonly
      );
    },
    canEnable(row) {
      return (
        this.hasAction('enable') &&
        this.hasPermission(`${this.config.permission}_enable`) &&
        !this.readonlyScope &&
        !row.readonly &&
        [0, 2].includes(row.status)
      );
    },
    canDisable(row) {
      return (
        this.hasAction('disable') &&
        this.hasPermission(`${this.config.permission}_disable`) &&
        !this.readonlyScope &&
        !row.readonly &&
        row.status === 1
      );
    },
    canCancel(row) {
      if (
        !this.hasAction('cancel') ||
        !this.hasPermission(`${this.config.permission}_cancel`) ||
        this.readonlyScope ||
        row.readonly
      ) {
        return false;
      }
      const status = this.statusValue(row);
      return this.config.permission === 'transport_plan'
        ? ['waiting_dispatch', 'dispatching'].includes(status)
        : ['pending', 'processing'].includes(status);
    },
    canComplete(row) {
      if (
        !this.hasAction('complete') ||
        !this.hasPermission(`${this.config.permission}_complete`) ||
        this.readonlyScope ||
        row.readonly
      ) {
        return false;
      }
      const status = this.statusValue(row);
      return this.config.permission === 'transport_plan'
        ? status === 'dispatching'
        : status === 'processing';
    },
    canReassign(row) {
      return (
        this.hasAction('reassign') &&
        this.hasPermission(`${this.config.permission}_reassign`) &&
        !this.readonlyScope &&
        !row.readonly &&
        this.statusValue(row) === 'pending'
      );
    },
    customOperations(row) {
      return (this.config.operations || []).filter(operation => this.canOperation(operation, row));
    },
    menuActions(row) {
      const actions = [];
      if (this.canCopy(row)) {
        actions.push({ key: 'copy', label: '复制', action: 'copy' });
      }
      if (this.canEdit(row)) {
        actions.push({
          key: 'edit',
          label: this.editLabel(row),
          action: 'edit',
        });
      }
      this.customOperations(row).forEach(operation => {
        actions.push({
          ...operation,
          key: `${operation.action}-${operation.label}`,
        });
      });
      if (this.canDelete(row)) {
        actions.push({ key: 'delete', label: '删除', action: 'delete', type: 'danger' });
      }
      return actions;
    },
    editLabel(row) {
      const status = this.statusValue(row);
      return this.config.editLabelStatusMap?.[status] || '编辑';
    },
    handleMenuAction(action, row, index) {
      if (action.action === 'copy') {
        this.handleCopy(row);
        return;
      }
      if (action.action === 'edit' || action.action === 'attachmentUpload') {
        this.attachmentUploadMode = action.action === 'attachmentUpload';
        this.$refs.crud.rowEdit(row, index);
        return;
      }
      if (action.action === 'delete') {
        this.rowDel(row);
        return;
      }
      this.handleCustomOperation(action, row);
    },
    consumeTemplateCreatePayload() {
      const target = this.config.templateCreateTarget;
      if (!target) return;
      const storageKey = 'business-template-create';
      const raw = sessionStorage.getItem(storageKey);
      if (!raw) return;
      let payload;
      try {
        payload = JSON.parse(raw);
      } catch (error) {
        sessionStorage.removeItem(storageKey);
        return;
      }
      if (payload.target !== target) return;
      sessionStorage.removeItem(storageKey);
      this.$nextTick(() => {
        this.$refs.crud.rowAdd();
        this.$nextTick(() => {
          const row = payload.data || {};
          this.suppressTransportTypeClear = true;
          Object.assign(this.form, row);
          this.$nextTick(() => {
            this.suppressTransportTypeClear = false;
          });
          this.form.planName = this.form.planName || this.form.templateName || '';
          this.selectedProjectId = this.form.projectId || '';
          this.attachmentRows = this.parseJsonArray(this.form.attachmentsJson);
          if (this.contractSelectEnabled) {
            this.syncCurrentContractOption();
            this.loadContractOptionsForProject();
          }
          if (this.config.enableProjectSelect) this.syncCurrentProjectOption();
          if (this.config.enableTransportPlanForm) this.initTransportPlanFormRows();
          if (this.taskInfoFormEnabled) this.initTaskInfoForm();
        });
      });
    },
    canOperation(operation, row) {
      const permission = operation.permission || `${this.config.permission}_${operation.action}`;
      if (!this.hasPermission(permission) || this.readonlyScope || row.readonly) {
        return false;
      }
      const prop = operation.statusProp || this.config.statusProp || 'status';
      const statusList = operation.status || [];
      if (statusList.length && !statusList.includes(row[prop])) {
        return false;
      }
      if (operation.stage && !operation.stage.includes(row.contractStage)) {
        return false;
      }
      return true;
    },
    showDetailButton(row) {
      if (!this.config.detailButton || !this.hasPermission(`${this.config.permission}_view`))
        return false;
      const status = this.statusValue(row);
      return status !== 'draft';
    },
    formatDetailValue(row, prop) {
      if (!row) return '-';
      if (prop === 'businessStatus') return this.displayStatus(row, prop);
      const column = this.findColumn(this.option.column, prop);
      if (column?.formatter) return column.formatter(row, {}, row[prop]);
      if (column?.dicData) {
        const item = column.dicData.find(dic => String(dic.value) === String(row[prop]));
        if (item) return item.label;
      }
      const value = row[prop];
      return value === undefined || value === null || value === '' ? '-' : value;
    },
    openDetail(row) {
      this.detailBox = true;
      this.detailLoading = true;
      this.detailRow = { ...row };
      const request =
        typeof this.api.getDetail === 'function'
          ? this.api.getDetail(row.id)
          : Promise.resolve({ data: { data: row } });
      request
        .then(res => {
          const detail = res?.data?.data || res?.data || row;
          this.detailRow = detail;
          this.applyFormDetail(detail);
        })
        .finally(() => {
          this.detailLoading = false;
        });
    },
    displayStatus(row, prop) {
      const textProp = prop === this.config.statusProp ? this.config.statusTextProp : '';
      if (textProp && row[textProp]) {
        return row[textProp];
      }
      const column = this.findColumn(this.option.column, prop);
      const item = column && (column.dicData || []).find(dic => dic.value === row[prop]);
      return item ? item.label : row[prop] || '未知';
    },
    syncContractNoDisabled(type) {
      if (!this.config.enableContractForm) return;
      const column = this.findColumn?.(this.option.column, 'contractNo');
      if (column) {
        column.disabled = type !== 'add';
      }
    },
    syncCustomCreateDialogButtons(type) {
      if (
        !this.config.enableContractCreateActions &&
        !this.config.enableTransportPlanCreateActions
      ) {
        return;
      }
      if (!this.defaultDialogButtons) {
        this.defaultDialogButtons = {
          saveBtn: this.option.saveBtn,
          cancelBtn: this.option.cancelBtn,
        };
      }
      const customCreate = type === 'add';
      this.option.saveBtn = customCreate ? false : this.defaultDialogButtons.saveBtn;
      this.option.cancelBtn = customCreate ? false : this.defaultDialogButtons.cancelBtn;
    },
    statusTagType(status) {
      if (
        [
          1,
          'pending',
          'waiting_dispatch',
          'processing',
          'dispatching',
          'approved',
          'change_approved',
        ].includes(status)
      ) {
        return 'success';
      }
      if ([2, 'cancelled', 'withdrawn', 'draft'].includes(status)) {
        return 'info';
      }
      if (['rejected', 'change_rejected'].includes(status)) {
        return 'danger';
      }
      if (status === 'completed') {
        return 'primary';
      }
      return 'warning';
    },
    stageTagType(stage) {
      if (stage === 'formal') {
        return 'success';
      }
      if (stage === 'temporary') {
        return 'warning';
      }
      if (stage === 'terminated') {
        return 'danger';
      }
      return 'info';
    },
    isMonthlySettlement(rule = this.settlementRuleForm) {
      return rule.settlementType === '月结';
    },
    isFixedBillCutoffSettlement(rule = this.settlementRuleForm) {
      return this.isMonthlySettlement(rule) && rule.billCycleType === '固定截单日';
    },
    isFixedCycleSettlement(rule = this.settlementRuleForm) {
      return rule.settlementType === '固定天数周期结算';
    },
    handleSettlementTypeChange(value) {
      this.clearSettlementRuleError('settlementType');
      if (value === '月结') {
        if (!this.settlementRuleForm.billCycleType) {
          this.settlementRuleForm.billCycleType = '固定截单日';
        }
        if (this.settlementRuleForm.billCycleType === '固定截单日') {
          this.settlementRuleForm.billCutoffDay = this.settlementRuleForm.billCutoffDay || 25;
        }
        this.settlementRuleForm.cycleDays = '';
        return;
      }
      this.settlementRuleForm.billCycleType = '';
      this.settlementRuleForm.billCutoffDay = '';
      if (value !== '固定天数周期结算') {
        this.settlementRuleForm.cycleDays = '';
      }
    },
    handleSettlementBillCycleTypeChange(value) {
      this.clearSettlementRuleError('billCycleType');
      if (value === '固定截单日') {
        this.settlementRuleForm.billCutoffDay = this.settlementRuleForm.billCutoffDay || 25;
        return;
      }
      this.settlementRuleForm.billCutoffDay = '';
    },
    normalizeSettlementRule(rule = this.settlementRuleForm) {
      const nextRule = {
        ...defaultSettlementRule(),
        ...(rule || {}),
      };
      if (!this.isMonthlySettlement(nextRule)) {
        nextRule.billCycleType = '';
        nextRule.billCutoffDay = '';
      } else if (!this.isFixedBillCutoffSettlement(nextRule)) {
        nextRule.billCutoffDay = '';
      }
      if (!this.isFixedCycleSettlement(nextRule)) {
        nextRule.cycleDays = '';
      }
      return nextRule;
    },
    clearSettlementRuleError(field) {
      if (!this.settlementRuleErrors[field]) return;
      this.settlementRuleErrors = {
        ...this.settlementRuleErrors,
        [field]: '',
      };
    },
    validateSettlementRule() {
      const rule = this.normalizeSettlementRule(this.settlementRuleForm);
      this.settlementRuleErrors = {};
      if (this.isBillingFieldEmpty(rule.billStartDate)) {
        this.settlementRuleErrors = { billStartDate: '请选择账单起始日期' };
        this.$message.warning('请选择账单起始日期');
        return false;
      }
      if (this.isBillingFieldEmpty(rule.settlementType)) {
        this.settlementRuleErrors = { settlementType: '请选择结算类型' };
        this.$message.warning('请选择结算类型');
        return false;
      }
      if (this.isMonthlySettlement(rule) && this.isBillingFieldEmpty(rule.billCycleType)) {
        this.settlementRuleErrors = { billCycleType: '请选择账单周期类型' };
        this.$message.warning('请选择账单周期类型');
        return false;
      }
      if (this.isFixedBillCutoffSettlement(rule)) {
        if (this.isBillingFieldEmpty(rule.billCutoffDay)) {
          this.settlementRuleErrors = { billCutoffDay: '请选择账单截单日' };
          this.$message.warning('请选择账单截单日');
          return false;
        }
        const billCutoffDay = Number(rule.billCutoffDay);
        if (!Number.isInteger(billCutoffDay) || billCutoffDay < 1 || billCutoffDay > 31) {
          this.settlementRuleErrors = { billCutoffDay: '账单截单日必须为1~31的整数' };
          this.$message.warning('账单截单日必须为1~31的整数');
          return false;
        }
      }
      if (this.isFixedCycleSettlement(rule)) {
        const cycleDays = Number(rule.cycleDays);
        if (!Number.isInteger(cycleDays) || cycleDays < 1 || cycleDays > 365) {
          this.settlementRuleErrors = { cycleDays: '周期天数必须为1~365的整数' };
          this.$message.warning('周期天数必须为1~365的整数');
          return false;
        }
      }
      return true;
    },
    normalizeRow(row, saveAsDraft = false) {
      const submitRow = { ...row };
      if (this.config.enableTransportPlanForm) {
        if (!this.validateTransportPlanForm(submitRow)) return false;
        submitRow.goodsJson = JSON.stringify(this.transportCargoRows);
      }
      if (this.taskInfoFormEnabled) {
        if (!this.validateTaskInfoForm(submitRow)) return false;
        this.syncTaskInfoToRow(submitRow);
      }
      if (this.config.enableAttachmentTable) {
        submitRow.attachmentsJson = JSON.stringify(this.attachmentRows);
      }
      if (this.config.enableContractFileUpload) {
        submitRow.contractFileJson = JSON.stringify(this.contractFileRows);
      }
      if (this.config.enableBillingPlan) {
        submitRow.billingPlanJson = JSON.stringify(this.billingPlanRows);
      }
      if (this.config.enableSettlementRule) {
        if (this.settlementRuleEnabled && !this.validateSettlementRule()) return false;
        submitRow.settlementRuleJson = JSON.stringify(this.normalizeSettlementRule());
      }
      if (this.config.enableReconciliation) {
        submitRow.reconciliationJson = JSON.stringify(this.reconciliationForm);
      }
      if (this.config.enableChangeRecord) {
        submitRow.changeRecordJson = JSON.stringify(this.changeRecordRows);
      }
      if (this.config.saveAsDraft && saveAsDraft) {
        submitRow.approvalStatus = 'draft';
      }
      delete submitRow.basicInfoTitle;
      delete submitRow.templateInfoTitle;
      delete submitRow.shippingInfoTitle;
      delete submitRow.taskInfoTitle;
      delete submitRow.taskInfoForm;
      delete submitRow.goodsInfoTitle;
      delete submitRow.contractFileTitle;
      delete submitRow.billingInfoTitle;
      delete submitRow.settlementRuleTitle;
      delete submitRow.reconciliationTitle;
      delete submitRow.attachmentTitle;
      delete submitRow.changeRecordTitle;
      delete submitRow.contractPeriod;
      Object.keys(submitRow).forEach(key => {
        if (typeof submitRow[key] === 'string') {
          submitRow[key] = submitRow[key].trim();
        }
      });
      return submitRow;
    },
    validateTransportPlanForm(row) {
      const requiredFields = this.config.transportFormRequiredFields || [
        ['projectName', '项目'],
        ['contractName', '客户合同'],
        ['planName', '计划名称'],
        ['transportType', '运输方式'],
        ['planStartDate', '计划开始日期'],
        ['planEndDate', '计划结束日期'],
        ['departureAddress', '发货地址'],
        ['arrivalAddress', '收货地址'],
      ];
      const emptyField = requiredFields.find(([prop]) => this.isBillingFieldEmpty(row[prop]));
      if (emptyField) {
        this.$message.warning(`请选择或输入${emptyField[1]}`);
        return false;
      }
      if (row.planStartDate && row.planEndDate && row.planEndDate < row.planStartDate) {
        this.$message.warning('计划结束日期不能早于计划开始日期');
        return false;
      }
      if (row.remark && row.remark.length > 200) {
        this.$message.warning('备注不能超过200个字符');
        return false;
      }
      for (const [index, cargo] of this.transportCargoRows.entries()) {
        if (!cargo.cargoType) {
          this.$message.warning(`第${index + 1}行货物类型不能为空`);
          return false;
        }
      }
      return true;
    },
    stopSubmitLoading(loading) {
      if (typeof loading === 'function') {
        loading();
      }
    },
    rowSave(row, done, loading) {
      const submitRow = this.normalizeRow(row);
      if (!submitRow) {
        this.stopSubmitLoading(loading);
        return;
      }
      this.getSaveRequest()(submitRow).then(
        () => {
          this.onLoad(this.page);
          this.$message({ type: 'success', message: '操作成功!' });
          done();
        },
        error => {
          window.console.log(error);
          this.stopSubmitLoading(loading);
        }
      );
    },
    rowUpdate(row, index, done, loading) {
      const submitRow = this.normalizeRow(row);
      if (!submitRow) {
        this.stopSubmitLoading(loading);
        return;
      }
      const request =
        this.attachmentUploadMode && typeof this.api.updateAttachments === 'function'
          ? this.api.updateAttachments
          : this.getSaveRequest();
      request(submitRow).then(
        () => {
          this.attachmentUploadMode = false;
          this.onLoad(this.page);
          this.$message({ type: 'success', message: '操作成功!' });
          done();
        },
        error => {
          this.attachmentUploadMode = false;
          window.console.log(error);
          this.stopSubmitLoading(loading);
        }
      );
    },
    getSaveRequest() {
      return this.api.submit;
    },
    buildContractCreateSubmitRow(action) {
      const contractStage = action === 'draft' ? 'draft' : 'temporary';
      const submitRow = this.normalizeRow({
        ...this.form,
        contractStage,
        approvalStatus: 'draft',
      });
      return submitRow;
    },
    validateContractCreateAction(action, row) {
      if (!row) return false;
      if (this.isBillingFieldEmpty(row.contractName)) {
        this.$message.warning('请输入合同名称');
        return false;
      }
      if (this.isBillingFieldEmpty(row.contractCategory)) {
        this.$message.warning('请选择合同类别');
        return false;
      }
      if (['temporary', 'formal'].includes(action) && this.isBillingFieldEmpty(row.signType)) {
        this.$message.warning('请选择签约类型');
        return false;
      }
      if (action !== 'formal') return true;
      const formalFields = [
        ['partyA', '甲方'],
        ['partyB', '乙方'],
        ['startDate', '合同期限'],
        ['signDate', '签订日期'],
      ];
      const emptyField = formalFields.find(([prop]) => this.isBillingFieldEmpty(row[prop]));
      if (emptyField) {
        this.$message.warning(`请选择或输入${emptyField[1]}`);
        return false;
      }
      return true;
    },
    extractSavedContract(res, fallback = {}) {
      const data = res?.data?.data || res?.data || {};
      if (data && typeof data === 'object' && !Array.isArray(data)) {
        return {
          ...fallback,
          ...data,
        };
      }
      return fallback;
    },
    getSavedContractId(savedContract) {
      const id = savedContract?.id || this.form.id;
      if (!id) {
        this.$message.warning('合同保存成功但未返回主键，无法继续流转');
        return '';
      }
      return id;
    },
    handleContractCreateAction(action) {
      if (!this.config.enableContractCreateActions || this.contractCreateActionLoading) return;
      const submitRow = this.buildContractCreateSubmitRow(action);
      if (!this.validateContractCreateAction(action, submitRow)) return;
      this.contractCreateActionLoading = action;
      this.api
        .saveDraft(submitRow)
        .then(res => {
          const savedContract = this.extractSavedContract(res, submitRow);
          this.form.id = savedContract.id || this.form.id;
          if (action === 'draft') {
            return savedContract;
          }
          const id = this.getSavedContractId(savedContract);
          if (!id) return Promise.reject(new Error('合同主键为空'));
          if (action === 'formal') {
            return this.api.submitFormal(id).then(() => savedContract);
          }
          return savedContract;
        })
        .then(() => {
          const messageMap = {
            draft: '保存成功',
            temporary: '临时合同提交成功',
            formal: '正式合同提交成功',
          };
          this.$message.success(messageMap[action]);
          this.onLoad(this.page, this.query);
          this.closeCrudDialog();
        })
        .catch(error => {
          if (error?.message !== '合同主键为空') {
            window.console.log(error);
          }
        })
        .finally(() => {
          this.contractCreateActionLoading = '';
        });
    },
    handleTransportPlanCreateAction(action) {
      if (!this.config.enableTransportPlanCreateActions || this.transportPlanCreateActionLoading) {
        return;
      }
      const statusProp = this.config.statusProp || 'businessStatus';
      const submitRow = this.normalizeRow({
        ...this.form,
        [statusProp]: action === 'draft' ? 'draft' : 'waiting_dispatch',
      });
      if (!submitRow) return;
      this.transportPlanCreateActionLoading = action;
      this.api
        .submit(submitRow)
        .then(() => {
          this.$message.success(action === 'draft' ? '暂存成功' : '创建成功');
          this.onLoad(this.page, this.query);
          this.closeCrudDialog();
        })
        .finally(() => {
          this.transportPlanCreateActionLoading = '';
        });
    },
    handleDraftSave() {
      const request =
        typeof this.api.saveDraft === 'function' ? this.api.saveDraft : this.api.submit;
      const submitRow = this.normalizeRow(this.form, true);
      if (!submitRow) return;
      this.draftSaveLoading = true;
      request(submitRow)
        .then(() => {
          this.$message.success('保存成功');
          this.onLoad(this.page, this.query);
          this.closeCrudDialog();
        })
        .finally(() => {
          this.draftSaveLoading = false;
        });
    },
    closeCrudDialog() {
      const crud = this.$refs.crud;
      if (crud && typeof crud.closeDialog === 'function') {
        crud.closeDialog();
      }
    },
    rowDel(row) {
      this.$confirm('确定将选择数据删除?', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      })
        .then(() => this.api.remove(row.id))
        .then(res => {
          this.afterRemove(res.data.data);
        });
    },
    handleDelete() {
      if (this.selectionList.length === 0) {
        this.$message.warning('请选择至少一条数据');
        return;
      }
      const selection = this.selectionList.filter(item => this.canDelete(item));
      if (!selection.length) {
        this.$message.warning('所选数据当前状态不可删除');
        return;
      }
      this.$confirm('确定将选择数据删除?', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      })
        .then(() => this.api.remove(selection.map(item => item.id).join(',')))
        .then(res => {
          this.afterRemove(res.data.data);
        });
    },
    afterRemove(result = {}) {
      this.onLoad(this.page);
      const successCount = result.successCount || 0;
      const skippedCount = result.skippedCount || 0;
      if (skippedCount) {
        this.$message.warning(`成功删除${successCount}条，因业务引用跳过${skippedCount}条`);
      } else {
        this.$message({ type: 'success', message: '删除成功!' });
      }
      this.selectionClear();
    },
    beforeOpen(done, type) {
      this.crudDialogType = type;
      this.settlementRuleErrors = {};
      this.syncCustomCreateDialogButtons(type);
      this.dialogReadonly = type === 'view';
      this.syncContractNoDisabled(type);
      if (type === 'add') {
        this.form = {
          ...(this.config.defaultForm || {}),
        };
        this.form.taskEntryMode = 'simple';
        this.selectedProjectId = '';
        this.selectedOrganizationId = '';
        this.attachmentRows = [];
        this.contractFileRows = [];
        this.shippingPlanOptions = [];
        if (this.contractSelectEnabled) {
          this.contractOptions = [];
          this.form.contractId = '';
          this.form.contractName = '';
        }
        if (this.shippingPlanSelectEnabled) {
          this.clearShippingPlan();
        }
        this.transportCargoRows = [];
        this.billingPlanRows = [];
        this.settlementRuleForm = defaultSettlementRule();
        this.reconciliationForm = defaultReconciliation();
        this.changeRecordRows = [];
        this.applyCurrentUserHandler();
        this.initTaskInfoForm();
        this.initTransportPlanFormRows();
        this.syncContractPeriod(false);
        this.syncBillingPlanJson();
        if (this.config.enableTemplateCodePreview && typeof this.api.nextCode === 'function') {
          this.api
            .nextCode()
            .then(res => {
              this.form.templateCode = res.data?.data || '';
            })
            .finally(done);
          return;
        }
        done();
        return;
      }
      if (['edit', 'view'].includes(type)) {
        this.api.getDetail(this.form.id).then(res => {
          this.applyFormDetail(res.data.data || {});
          done();
        });
        return;
      }
      done();
    },
    applyFormDetail(row) {
      this.suppressTransportTypeClear = true;
      this.form = row || {};
      this.$nextTick(() => {
        this.suppressTransportTypeClear = false;
      });
      this.selectedProjectId = this.form.projectId || '';
      this.selectedOrganizationId = this.form.organizationId || '';
      this.attachmentRows = this.parseJsonArray(this.form.attachmentsJson);
      this.contractFileRows = this.parseJsonArray(this.form.contractFileJson);
      this.initTransportPlanFormRows();
      this.billingPlanRows = this.parseJsonArray(this.form.billingPlanJson);
      this.settlementRuleForm = this.normalizeSettlementRule({
        ...defaultSettlementRule(),
        ...this.parseJsonObject(this.form.settlementRuleJson),
      });
      this.reconciliationForm = {
        ...defaultReconciliation(),
        ...this.parseJsonObject(this.form.reconciliationJson),
      };
      this.changeRecordRows = this.parseJsonArray(this.form.changeRecordJson);
      this.applyCurrentUserHandler();
      this.initTaskInfoForm();
      this.syncContractPeriod(false);
      this.syncBillingPlanJson();
      if (this.config.enableOrganizationSelect) {
        this.syncCurrentOrganizationOption();
      }
      if (this.config.enableProjectSelect) {
        this.form.remainingFundLimit = this.calculateRemainingFundLimit();
        this.syncCurrentProjectOption();
      }
      if (this.contractSelectEnabled) {
        this.syncCurrentContractOption();
      }
      if (this.shippingPlanSelectEnabled) {
        this.syncCurrentShippingPlanOption();
      }
    },
    parseJsonArray(value) {
      if (!value) return [];
      if (Array.isArray(value)) return value;
      try {
        const list = JSON.parse(value);
        return Array.isArray(list) ? list : [];
      } catch (error) {
        return [];
      }
    },
    parseJsonObject(value) {
      if (!value) return {};
      if (typeof value === 'object' && !Array.isArray(value)) return value;
      try {
        const data = JSON.parse(value);
        return data && typeof data === 'object' && !Array.isArray(data) ? data : {};
      } catch (error) {
        return {};
      }
    },
    parseDispatchQuantity(value) {
      if (value === undefined || value === null || value === '') return 0;
      const text = String(value).replace(/,/g, '');
      const match = text.match(/-?\d+(\.\d+)?/);
      if (!match) return 0;
      const number = Number(match[0]);
      return Number.isFinite(number) ? number : 0;
    },
    formatDispatchQuantity(value) {
      const number = Number(value || 0);
      if (!Number.isFinite(number)) return '0';
      const fixed = Math.round((number + Number.EPSILON) * 1000) / 1000;
      return Number.isInteger(fixed) ? String(fixed) : String(fixed).replace(/\.?0+$/, '');
    },
    loadProjectOptions() {
      if (!this.config.enableProjectSelect || this.projectLoading) return;
      this.projectLoading = true;
      getProjectList(1, 9999, this.config.projectQueryParams || {})
        .then(res => {
          this.projectOptions = extractRecords(res);
          this.syncCurrentProjectOption();
        })
        .finally(() => {
          this.projectLoading = false;
        });
    },
    syncCurrentProjectOption() {
      if (!this.form.projectId || !this.form.projectName) return;
      const exists = this.projectOptions.some(
        item => String(item.id) === String(this.form.projectId)
      );
      if (!exists) {
        this.projectOptions.unshift({
          id: this.form.projectId,
          projectName: this.form.projectName,
          projectCode: this.form.projectCode,
          undertakeDeptName: this.form.undertakeDeptName,
          fundLimit: this.form.projectFundLimit,
        });
      }
    },
    calculateRemainingFundLimit() {
      const projectFundLimit = Number(this.form.projectFundLimit);
      const usedFundLimit = Number(this.form.usedFundLimit);
      if (!Number.isFinite(projectFundLimit) || !Number.isFinite(usedFundLimit)) return '';
      return Math.round((projectFundLimit - usedFundLimit + Number.EPSILON) * 100) / 100;
    },
    handleProjectChange(projectId) {
      const project = this.projectOptions.find(item => String(item.id) === String(projectId));
      if (!project) {
        this.form.projectId = '';
        this.form.projectName = '';
        this.form.projectCode = '';
        this.form.undertakeDeptName = '';
        this.form.projectFundLimit = '';
        this.form.usedFundLimit = 0;
        this.form.remainingFundLimit = 0;
        if (this.contractSelectEnabled) {
          this.form.contractId = '';
          this.form.contractName = '';
          this.contractOptions = [];
        }
        if (this.shippingPlanSelectEnabled) {
          this.clearShippingPlan();
          this.shippingPlanOptions = [];
        }
        return;
      }
      this.form.projectId = project.id;
      this.form.projectName = project.projectName || '';
      this.form.projectCode = project.projectCode || '';
      this.form.undertakeDeptName = project.undertakeDeptName || '';
      this.form.projectFundLimit = project.fundLimit || project.projectFundLimit || 0;
      this.form.usedFundLimit = 0;
      this.form.remainingFundLimit = this.calculateRemainingFundLimit();
      if (this.contractSelectEnabled || this.config.enableTransportPlanForm) {
        this.form.customerName =
          project.customerName ||
          project.customerNames ||
          project.customer ||
          this.form.customerName ||
          '';
      }
      if (this.config.enableTransportPlanForm) {
        this.form.transportType =
          project.transportType ||
          project.transportMode ||
          project.transportWay ||
          this.form.transportType ||
          '';
      }
      if (this.contractSelectEnabled) {
        this.form.contractId = '';
        this.form.contractName = '';
        this.loadContractOptionsForProject(true);
      }
      if (this.shippingPlanSelectEnabled) {
        this.clearShippingPlan();
        this.loadShippingPlanOptions();
      }
      this.$refs.crud?.validateField?.('projectName');
    },
    loadContractOptionsForProject(autoPick = false) {
      if (!this.contractSelectEnabled || !this.form.projectId || this.contractLoading) {
        return Promise.resolve([]);
      }
      this.contractLoading = true;
      return getContractList(1, 9999, {
        projectId: this.form.projectId,
        projectName: this.form.projectName,
      })
        .then(res => {
          this.contractOptions = extractRecords(res);
          this.syncCurrentContractOption();
          if (autoPick && this.contractOptions.length) {
            this.applyContract(this.contractOptions[0]);
          }
          return this.contractOptions;
        })
        .finally(() => {
          this.contractLoading = false;
        });
    },
    syncCurrentContractOption() {
      if (!this.form.contractId || !this.form.contractName) return;
      const exists = this.contractOptions.some(
        item => String(item.id) === String(this.form.contractId)
      );
      if (!exists) {
        this.contractOptions.unshift({
          id: this.form.contractId,
          contractName: this.form.contractName,
        });
      }
    },
    handleContractChange(contractId) {
      const contract = this.contractOptions.find(item => String(item.id) === String(contractId));
      if (!contract) {
        this.form.contractId = '';
        this.form.contractName = '';
        return;
      }
      this.applyContract(contract);
    },
    applyContract(contract = {}) {
      this.form.contractId = contract.id || '';
      this.form.contractName = contract.contractName || '';
      this.form.customerName =
        contract.customerName || contract.customerNames || this.form.customerName || '';
      this.$refs.crud?.validateField?.('contractName');
    },
    loadShippingPlanOptions() {
      if (!this.shippingPlanSelectEnabled || this.shippingPlanLoading) {
        return Promise.resolve([]);
      }
      this.shippingPlanLoading = true;
      return getTransportPlanList(1, 9999, {
        projectId: this.form.projectId,
        projectName: this.form.projectName,
      })
        .then(res => {
          this.shippingPlanOptions = extractRecords(res);
          this.syncCurrentShippingPlanOption();
          return this.shippingPlanOptions;
        })
        .finally(() => {
          this.shippingPlanLoading = false;
        });
    },
    syncCurrentShippingPlanOption() {
      if (!this.form.planName) return;
      const exists = this.shippingPlanOptions.some(
        item =>
          String(item.id) === String(this.form.planId) ||
          String(item.planName) === String(this.form.planName)
      );
      if (!exists) {
        this.shippingPlanOptions.unshift({
          id: this.form.planId || this.form.planName,
          planName: this.form.planName,
        });
      }
    },
    handleShippingPlanChange(planName) {
      const plan = this.shippingPlanOptions.find(
        item =>
          String(item.planName) === String(planName) || String(item.id) === String(this.form.planId)
      );
      if (!plan) {
        this.clearShippingPlan();
        return;
      }
      this.applyShippingPlan(plan);
    },
    applyShippingPlan(plan = {}) {
      this.form.planId = plan.id || '';
      this.form.planName = plan.planName || '';
      this.$refs.crud?.validateField?.('planName');
    },
    clearShippingPlan() {
      this.form.planId = '';
      this.form.planName = '';
    },
    initTaskInfoForm() {
      if (!this.taskInfoFormEnabled) return;
      const taskInfo = this.parseJsonObject(this.form.taskInfoJson);
      const carrierRows = this.parseJsonArray(this.form.carrierJson);
      const carrierInfo = carrierRows[0] || this.parseJsonObject(this.form.carrierJson);
      const goodsRows = this.parseJsonArray(this.form.goodsJson);
      const goodsInfo = goodsRows[0] || {};
      this.form.taskEntryMode =
        this.form.taskEntryMode || taskInfo.taskEntryMode || carrierInfo.taskEntryMode || 'simple';
      this.form.carrierType =
        this.form.carrierType ||
        taskInfo.carrierType ||
        carrierInfo.carrierType ||
        carrierInfo.type ||
        '承运商';
      this.form.carrierName =
        this.form.carrierName ||
        taskInfo.carrierName ||
        carrierInfo.carrierName ||
        carrierInfo.customerName ||
        carrierInfo.name ||
        '';
      this.form.driverName =
        this.form.driverName || taskInfo.driverName || carrierInfo.driverName || '';
      this.form.driverPhone =
        this.form.driverPhone ||
        taskInfo.driverPhone ||
        carrierInfo.driverPhone ||
        carrierInfo.mobile ||
        '';
      this.form.vehicleNo =
        this.form.vehicleNo || taskInfo.vehicleNo || carrierInfo.vehicleNo || '';
      this.form.trailerVehicleNo =
        this.form.trailerVehicleNo ||
        taskInfo.trailerVehicleNo ||
        carrierInfo.trailerVehicleNo ||
        '';
      this.form.escortName =
        this.form.escortName || taskInfo.escortName || carrierInfo.escortName || '';
      this.form.escortPhone =
        this.form.escortPhone || taskInfo.escortPhone || carrierInfo.escortPhone || '';
      this.form.cargoType =
        this.form.cargoType ||
        taskInfo.cargoType ||
        goodsInfo.cargoType ||
        goodsInfo.goodsType ||
        '';
      this.form.cargoTypeCode =
        this.form.cargoTypeCode ||
        taskInfo.cargoTypeCode ||
        goodsInfo.cargoTypeCode ||
        goodsInfo.goodsTypeCode ||
        '';
      this.form.cargoName =
        this.form.cargoName ||
        taskInfo.cargoName ||
        goodsInfo.cargoName ||
        goodsInfo.goodsName ||
        '';
      this.form.specification =
        this.form.specification || taskInfo.specification || goodsInfo.specification || '';
      this.form.model = this.form.model || taskInfo.model || goodsInfo.model || '';
      this.form.quantity = this.form.quantity || taskInfo.quantity || goodsInfo.quantity || '';
      this.form.quantityUnit =
        this.form.quantityUnit || taskInfo.quantityUnit || goodsInfo.quantityUnit || '';
      this.form.mileage = this.form.mileage || taskInfo.mileage || goodsInfo.mileage || '';
      this.form.unitPrice = this.form.unitPrice || taskInfo.unitPrice || goodsInfo.unitPrice || '';
      this.form.priceUnit = this.form.priceUnit || taskInfo.priceUnit || goodsInfo.priceUnit || '';
      this.form.otherFeeTotal =
        this.form.otherFeeTotal || taskInfo.otherFeeTotal || goodsInfo.otherFeeTotal || '';
      this.form.estimatedStartTime =
        this.form.estimatedStartTime ||
        taskInfo.estimatedStartTime ||
        goodsInfo.estimatedStartTime ||
        '';
      this.form.estimatedEndTime =
        this.form.estimatedEndTime || taskInfo.estimatedEndTime || goodsInfo.estimatedEndTime || '';
      this.form.taskRemark = this.form.taskRemark || taskInfo.taskRemark || goodsInfo.remark || '';
      this.form.cargoTypePath = this.normalizeBillingCargoTypePath(this.form.cargoTypePath);
      if (!this.form.cargoTypePath.length) {
        this.ensureBillingCargoTypeOptions().then(() => {
          const cargoType = this.findBillingCargoTypeByCodeOrName({
            cargoTypeCode: this.form.cargoTypeCode,
            cargoType: this.form.cargoType,
          });
          if (cargoType?.path) {
            this.form.cargoTypePath = cargoType.path;
          }
        });
      }
      if (this.isTaskFullMode) {
        this.initTaskFullCargoRows(goodsRows);
      }
    },
    handleTaskEntryModeChange(value) {
      this.form.taskEntryMode = value || 'simple';
      if (this.form.taskEntryMode === 'full') {
        this.initTaskFullCargoRows(this.parseJsonArray(this.form.goodsJson));
      }
    },
    handleTaskCarrierTypeChange(value) {
      const nextValue = value || '承运商';
      if (nextValue === '承运商') {
        this.form.trailerVehicleNo = '';
        this.form.escortName = '';
        this.form.escortPhone = '';
      } else {
        this.form.carrierName = '';
        this.form.carrierId = '';
      }
      this.form.carrierType = nextValue;
    },
    loadTaskCarrierOptions() {
      if (!this.taskInfoFormEnabled || this.taskCarrierLoading) return Promise.resolve([]);
      this.taskCarrierLoading = true;
      return getCarrierCustomerList(1, 9999, { customerType: '承运商' })
        .then(res => {
          this.taskCarrierOptions = extractRecords(res);
          return this.taskCarrierOptions;
        })
        .finally(() => {
          this.taskCarrierLoading = false;
        });
    },
    handleTaskCarrierChange(value) {
      const carrier = this.taskCarrierOptions.find(item =>
        [item.customerName, item.carrierName, item.fullName, item.name].some(
          name => String(name || '') === value
        )
      );
      this.form.carrierId = carrier?.id || '';
      this.form.carrierName = value || '';
    },
    loadTaskDriverOptions() {
      if (!this.taskInfoFormEnabled || this.taskDriverLoading) return Promise.resolve([]);
      this.taskDriverLoading = true;
      return getDriverList(1, 9999, {})
        .then(res => {
          this.taskDriverOptions = extractRecords(res);
          return this.taskDriverOptions;
        })
        .finally(() => {
          this.taskDriverLoading = false;
        });
    },
    handleTaskDriverChange(value) {
      const driver = this.taskDriverOptions.find(item =>
        [item.driverName, item.name].some(name => String(name || '') === String(value || ''))
      );
      this.form.driverId = driver?.id || '';
      this.form.driverName = value || '';
      if (driver) {
        this.form.driverPhone =
          driver.mobile || driver.phone || driver.driverPhone || this.form.driverPhone || '';
      }
    },
    getTaskCargoTypeOptionsKey(path = []) {
      const normalizedPath = this.normalizeBillingCargoTypePath(path);
      if (!normalizedPath.length) return '';
      const cargoType = this.findBillingCargoTypeByPath(normalizedPath);
      return String(cargoType?.cargoCode || cargoType?.code || normalizedPath.join('/'));
    },
    getTaskCargoOptionsByPath(path = []) {
      const key = this.getTaskCargoTypeOptionsKey(path);
      return key ? this.taskCargoOptionsMap[key] || [] : [];
    },
    isTaskCargoOptionsLoading(path = []) {
      const key = this.getTaskCargoTypeOptionsKey(path);
      return key ? !!this.taskCargoLoadingMap[key] : false;
    },
    setTaskCargoOptionsByKey(key, options = []) {
      if (!key) return;
      this.taskCargoOptionsMap = {
        ...this.taskCargoOptionsMap,
        [key]: options,
      };
    },
    setTaskCargoLoadingByKey(key, loading) {
      if (!key) return;
      this.taskCargoLoadingMap = {
        ...this.taskCargoLoadingMap,
        [key]: loading,
      };
    },
    buildTaskCargoListQueryParams(path = []) {
      const normalizedPath = this.normalizeBillingCargoTypePath(path);
      const labels = this.getBillingCargoTypePathLabels(normalizedPath);
      const secondCargoType = this.findBillingCargoTypeByPath(normalizedPath);
      return {
        secondCargoTypeName: labels[1] || '',
        secondCargoTypeCode: secondCargoType?.cargoCode || secondCargoType?.code || '',
      };
    },
    loadTaskCargoOptionsByPath(path = []) {
      const normalizedPath = this.normalizeBillingCargoTypePath(path);
      if (!normalizedPath.length) return Promise.resolve([]);
      return this.ensureBillingCargoTypeOptions().then(() => {
        const key = this.getTaskCargoTypeOptionsKey(normalizedPath);
        const params = this.buildTaskCargoListQueryParams(normalizedPath);
        if (!params.secondCargoTypeName && !params.secondCargoTypeCode) {
          this.setTaskCargoOptionsByKey(key, []);
          return [];
        }
        this.setTaskCargoLoadingByKey(key, true);
        return getCommonCargoList(1, 500, {
          ...params,
          allDept: 0,
        })
          .then(res => {
            const records = extractRecords(res);
            this.setTaskCargoOptionsByKey(key, records);
            return records;
          })
          .catch(error => {
            window.console.log(error);
            this.setTaskCargoOptionsByKey(key, []);
            return [];
          })
          .finally(() => {
            this.setTaskCargoLoadingByKey(key, false);
          });
      });
    },
    loadTaskCargoOptionsForForm() {
      return this.loadTaskCargoOptionsByPath(this.form.cargoTypePath);
    },
    handleTaskCargoTypeChange(value) {
      const path = this.normalizeBillingCargoTypePath(value);
      const cargoType = this.findBillingCargoTypeByPath(path);
      const labels = this.getBillingCargoTypePathLabels(path);
      this.form.cargoTypePath = path;
      this.form.cargoType = labels.length ? labels[labels.length - 1] : '';
      this.form.cargoTypeCode = cargoType?.cargoCode || cargoType?.code || cargoType?.id || '';
      this.form.cargoName = '';
      this.form.specification = '';
      this.form.model = '';
      this.loadTaskCargoOptionsForForm();
    },
    handleTaskCargoChange(value) {
      const cargo = this.taskCargoOptions.find(
        item => this.formatBillingCargoTypeLabel(item) === value
      );
      this.form.cargoName = value || '';
      if (cargo) {
        this.form.specification =
          cargo.specification || cargo.spec || this.form.specification || '';
        this.form.model = cargo.model || cargo.modelName || this.form.model || '';
      }
    },
    getTaskCargoRowOptions(row = {}) {
      return this.getTaskCargoOptionsByPath(row.cargoTypePath);
    },
    handleTaskCargoRowNameChange(row, value) {
      const cargo = this.getTaskCargoRowOptions(row).find(
        item => this.formatBillingCargoTypeLabel(item) === value
      );
      row.cargoName = value || '';
      if (cargo) {
        row.specification = cargo.specification || cargo.spec || row.specification || '';
        row.model = cargo.model || cargo.modelName || row.model || '';
      }
      this.syncTransportCargoJson();
    },
    initTaskFullCargoRows(goodsRows = []) {
      if (!this.taskInfoFormEnabled) return;
      const rows = (goodsRows.length ? goodsRows : this.transportCargoRows).map(row =>
        this.normalizeTransportCargoRow(row)
      );
      while (rows.length < 1) {
        rows.push(this.normalizeTransportCargoRow());
      }
      this.transportCargoRows = rows;
      this.syncTransportCargoJson();
    },
    loadTaskQuantityUnitOptions() {
      if (this.taskQuantityUnitOptions.length || this.taskQuantityUnitLoading) {
        return Promise.resolve(this.taskQuantityUnitOptions);
      }
      this.taskQuantityUnitLoading = true;
      return getDictionary({ code: 'quantity_unit' })
        .then(res => {
          this.taskQuantityUnitOptions = this.normalizeDictOptions(res.data.data || []);
          return this.taskQuantityUnitOptions;
        })
        .finally(() => {
          this.taskQuantityUnitLoading = false;
        });
    },
    loadTaskFeeUnitOptions() {
      if (this.taskFeeUnitOptions.length || this.taskFeeUnitLoading) {
        return Promise.resolve(this.taskFeeUnitOptions);
      }
      this.taskFeeUnitLoading = true;
      return getDictionary({ code: 'unit_fee' })
        .then(res => {
          this.taskFeeUnitOptions = this.normalizeDictOptions(res.data.data || []);
          return this.taskFeeUnitOptions;
        })
        .finally(() => {
          this.taskFeeUnitLoading = false;
        });
    },
    normalizeDictOptions(list = []) {
      return (list || []).map(item => ({
        label: item.dictValue || item.label || item.value || '',
        value: item.dictKey || item.value || item.dictValue || '',
      }));
    },
    handleTaskNumberInput(prop, value) {
      this.form[prop] = this.normalizeTaskNumber(value);
    },
    normalizeTaskNumber(value) {
      const text = String(value || '').replace(/[^\d.]/g, '');
      const parts = text.split('.');
      return parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('')}` : parts[0];
    },
    validateTaskCargoRows() {
      if (!this.transportCargoRows.length) {
        this.$message.warning('请至少添加一条货物信息');
        return false;
      }
      for (const [index, cargo] of this.transportCargoRows.entries()) {
        const requiredFields = [
          ['cargoName', '货物名称'],
          ['cargoType', '货物类型'],
          ['quantity', '数量'],
          ['quantityUnit', '数量单位'],
        ];
        const emptyField = requiredFields.find(([prop]) => this.isBillingFieldEmpty(cargo[prop]));
        if (emptyField) {
          this.$message.warning(`第${index + 1}行${emptyField[1]}不能为空`);
          return false;
        }
        const invalidField = [['quantity', '数量']].find(([prop]) => {
          const value = cargo[prop];
          return value !== undefined && value !== null && value !== '' && Number(value) < 0;
        });
        if (invalidField) {
          this.$message.warning(`第${index + 1}行${invalidField[1]}不能小于0`);
          return false;
        }
      }
      return true;
    },
    validateTaskInfoForm(row = {}) {
      if (this.isTaskFullMode && !this.validateTaskCargoRows()) {
        return false;
      }
      const requiredFields = this.config.transportFormRequiredFields || [
        ['carrierType', '承运类型'],
        ['vehicleNo', '车牌号'],
      ];
      if (row.carrierType === '承运商') {
        requiredFields.push(['carrierName', '承运商']);
      }
      if (row.carrierType === '自运') {
        requiredFields.push(['driverPhone', '手机号']);
      }
      const emptyField = requiredFields.find(([prop]) => this.isBillingFieldEmpty(row[prop]));
      if (emptyField) {
        this.$message.warning(`请选择或输入${emptyField[1]}`);
        return false;
      }
      const numberFields = [
        ['quantity', '数量'],
        ['mileage', '里程'],
        ['unitPrice', '单价'],
        ['otherFeeTotal', '其他运费合计'],
      ];
      const invalidField = numberFields.find(([prop]) => {
        const value = row[prop];
        return value !== undefined && value !== null && value !== '' && Number(value) < 0;
      });
      if (invalidField) {
        this.$message.warning(`${invalidField[1]}不能小于0`);
        return false;
      }
      if (
        row.estimatedStartTime &&
        row.estimatedEndTime &&
        row.estimatedEndTime < row.estimatedStartTime
      ) {
        this.$message.warning('预计完成日期不能早于预计发货日期');
        return false;
      }
      if (row.taskRemark && row.taskRemark.length > 200) {
        this.$message.warning('任务备注不能超过200个字符');
        return false;
      }
      return true;
    },
    syncTaskInfoToRow(row) {
      row.taskEntryMode = row.taskEntryMode || 'simple';
      const taskInfo = {
        taskEntryMode: row.taskEntryMode,
        carrierType: row.carrierType,
        carrierId: row.carrierId,
        carrierName: row.carrierName,
        driverId: row.driverId,
        driverName: row.driverName,
        driverPhone: row.driverPhone,
        vehicleNo: row.vehicleNo,
        trailerVehicleNo: row.trailerVehicleNo,
        escortName: row.escortName,
        escortPhone: row.escortPhone,
        mileage: row.mileage,
        estimatedStartTime: row.estimatedStartTime,
        estimatedEndTime: row.estimatedEndTime,
        unitPrice: row.unitPrice,
        priceUnit: row.priceUnit,
        otherFeeTotal: row.otherFeeTotal,
        taskRemark: row.taskRemark,
      };
      row.taskInfoJson = JSON.stringify(taskInfo);
      row.carrierJson = JSON.stringify([
        {
          carrierType: row.carrierType,
          carrierId: row.carrierId,
          carrierName: row.carrierName,
          driverId: row.driverId,
          driverName: row.driverName,
          driverPhone: row.driverPhone,
          vehicleNo: row.vehicleNo,
          trailerVehicleNo: row.trailerVehicleNo,
          escortName: row.escortName,
          escortPhone: row.escortPhone,
          mileage: row.mileage,
          taskEntryMode: row.taskEntryMode,
        },
      ]);
      const goodsRows = this.isTaskFullMode
        ? this.transportCargoRows.map(item => this.normalizeTransportCargoRow(item))
        : [
            {
              cargoType: row.cargoType,
              cargoTypeCode: row.cargoTypeCode,
              cargoTypePath: row.cargoTypePath,
              cargoName: row.cargoName,
              specification: row.specification,
              model: row.model,
              quantity: row.quantity,
              quantityUnit: row.quantityUnit,
              mileage: row.mileage,
              unitPrice: row.unitPrice,
              priceUnit: row.priceUnit,
              remark: row.taskRemark,
            },
          ];
      row.goodsJson = JSON.stringify(goodsRows);
      const firstGoodsRow =
        goodsRows.find(
          item =>
            !this.isBillingFieldEmpty(item.cargoName) || !this.isBillingFieldEmpty(item.cargoType)
        ) ||
        goodsRows[0] ||
        {};
      row.cargoType = firstGoodsRow.cargoType || row.cargoType || '';
      row.cargoTypeCode = firstGoodsRow.cargoTypeCode || row.cargoTypeCode || '';
      row.cargoTypePath = firstGoodsRow.cargoTypePath || row.cargoTypePath || [];
      row.cargoName = firstGoodsRow.cargoName || row.cargoName || '';
      row.specification = firstGoodsRow.specification || row.specification || '';
      row.model = firstGoodsRow.model || row.model || '';
      row.quantity = firstGoodsRow.quantity || row.quantity || '';
      row.quantityUnit = firstGoodsRow.quantityUnit || row.quantityUnit || '';
      row.mileage = firstGoodsRow.mileage || row.mileage || '';
      row.unitPrice = firstGoodsRow.unitPrice || row.unitPrice || '';
      row.priceUnit = firstGoodsRow.priceUnit || row.priceUnit || '';
      row.taskRemark = firstGoodsRow.remark || row.taskRemark || '';
    },
    initTransportPlanFormRows() {
      if (!this.config.enableTransportPlanForm) return;
      this.transportCargoRows = this.parseJsonArray(this.form.goodsJson).map(row =>
        this.normalizeTransportCargoRow(row)
      );
      this.syncTransportCargoJson();
      this.syncCurrentContractOption();
      this.restoreTransportCargoTypePaths();
    },
    restoreTransportCargoTypePaths() {
      if (!this.transportCargoRows.length) return Promise.resolve();
      return this.ensureBillingCargoTypeOptions().then(() => {
        this.transportCargoRows.forEach(row => {
          if (this.normalizeBillingCargoTypePath(row.cargoTypePath).length) return;
          const cargoType = this.findBillingCargoTypeByCodeOrName({
            cargoTypeCode: row.cargoTypeCode,
            cargoType: row.cargoType,
          });
          if (!cargoType?.path) return;
          row.cargoTypePath = cargoType.path;
          row.cargoType =
            this.getBillingCargoTypePathLabels(cargoType.path).at(-1) || row.cargoType;
          row.cargoTypeCode = cargoType.cargoCode || cargoType.code || row.cargoTypeCode;
        });
        this.syncTransportCargoJson();
      });
    },
    normalizeTransportCargoRow(row = {}) {
      return {
        ...defaultTransportCargo(),
        ...row,
        cargoTypePath: this.normalizeBillingCargoTypePath(row.cargoTypePath),
        cargoType:
          row.cargoType || row.goodsType || row.secondCargoTypeName || row.firstCargoTypeName || '',
        cargoTypeCode:
          row.cargoTypeCode ||
          row.goodsTypeCode ||
          row.secondCargoTypeCode ||
          row.firstCargoTypeCode ||
          '',
        cargoName: row.cargoName || row.goodsName || '',
        packageType: row.packageType || row.package || '',
        specification: row.specification || row.spec || '',
      };
    },
    syncTransportCargoJson() {
      if (!this.config.enableTransportPlanForm && !this.isTaskFullMode) return;
      this.form.goodsJson = JSON.stringify(
        this.transportCargoRows.map(row => this.normalizeTransportCargoRow(row))
      );
    },
    addTransportCargoRow(index = -1, row = {}) {
      const nextRow = this.normalizeTransportCargoRow(row);
      if (index > -1) {
        this.transportCargoRows.splice(index + 1, 0, nextRow);
      } else {
        this.transportCargoRows.push(nextRow);
      }
      this.syncTransportCargoJson();
    },
    removeTransportCargoRow(index) {
      this.transportCargoRows.splice(index, 1);
      if (
        !this.transportCargoRows.length &&
        (this.isTaskFullMode || this.config.enableTransportPlanForm)
      ) {
        this.transportCargoRows.push(this.normalizeTransportCargoRow());
      }
      this.syncTransportCargoJson();
    },
    handleTransportCargoQuantityInput(row, value) {
      const text = String(value || '').replace(/[^\d.]/g, '');
      const parts = text.split('.');
      row.quantity =
        parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 3)}` : parts[0];
      this.syncTransportCargoJson();
    },
    handleTransportCargoTypeChange(row, value) {
      const path = this.normalizeBillingCargoTypePath(value);
      const cargoType = this.findBillingCargoTypeByPath(path);
      const labels = this.getBillingCargoTypePathLabels(path);
      row.cargoTypePath = path;
      row.cargoType = labels.length ? labels[labels.length - 1] : '';
      row.cargoTypeCode = cargoType?.cargoCode || cargoType?.code || cargoType?.id || '';
      row.cargoName = '';
      row.specification = '';
      row.model = '';
      this.syncTransportCargoJson();
      this.loadTaskCargoOptionsByPath(path);
    },
    handleTransportTypeChange(value) {
      this.form.transportTypeName = this.getTransportTypeLabel(value);
      this.clearTransportShippingInfo();
    },
    clearTransportShippingInfo() {
      [
        'departureName',
        'departureAddress',
        'departureAddressCode',
        'departureSiteCode',
        'departureLongitude',
        'departureLatitude',
        'departureContact',
        'departurePhone',
        'arrivalName',
        'arrivalAddress',
        'arrivalAddressCode',
        'arrivalSiteCode',
        'arrivalLongitude',
        'arrivalLatitude',
        'arrivalContact',
        'arrivalPhone',
      ].forEach(prop => {
        this.form[prop] = '';
      });
      this.transportRouteSelected = null;
      this.transportAddressSelected = null;
      this.transportMapSelected = {};
      this.transportRouteBox = false;
      this.transportAddressBox = false;
      this.transportStationBox = false;
      this.transportMapBox = false;
      this.transportAddressTarget = '';
      this.transportStationTarget = '';
      this.transportMapTarget = '';
      this.transportMapKeyword = '';
      this.transportMapStatus = '可搜索地址或点击地图选点';
      if (this.transportAmapMarker) {
        this.transportAmapMarker.setMap(null);
        this.transportAmapMarker = null;
      }
      this.$refs.crud?.clearValidate?.([
        'departureAddress',
        'arrivalAddress',
        'departureName',
        'arrivalName',
        'departureContact',
        'departurePhone',
        'arrivalContact',
        'arrivalPhone',
      ]);
    },
    getTransportTypeLabel(value) {
      const column = (this.option.column || []).find(item => item.prop === 'transportType');
      const dictionary = column?.dicData || [];
      const item = dictionary.find(
        dic =>
          String(dic.value ?? dic.dictKey ?? '') === String(value ?? '') ||
          String(dic.dictKey ?? '') === String(value ?? '')
      );
      return item?.label || item?.dictValue || '';
    },
    resolveTransportMode(value) {
      const text = [value, this.form.transportTypeName, this.getTransportTypeLabel(value)]
        .filter(Boolean)
        .join(' ')
        .toLowerCase();
      if (!text) return '';
      if (
        text.includes('公路') ||
        text.includes('道路') ||
        text.includes('road') ||
        text.includes('highway') ||
        text === 'gl'
      ) {
        return 'road';
      }
      if (
        text.includes('铁路') ||
        text.includes('rail') ||
        text.includes('train') ||
        text === 'tl'
      ) {
        return 'rail';
      }
      if (
        text.includes('水路') ||
        text.includes('水运') ||
        text.includes('海运') ||
        text.includes('港') ||
        text.includes('water') ||
        text.includes('ship') ||
        text.includes('sea') ||
        text === 'sl'
      ) {
        return 'water';
      }
      if (
        text.includes('航空') ||
        text.includes('空运') ||
        text.includes('空港') ||
        text.includes('air') ||
        text.includes('airport') ||
        text === 'hk'
      ) {
        return 'air';
      }
      return '';
    },
    openTransportPrimaryAddressPicker(target) {
      if (this.transportStationMode) {
        this.openTransportStationDialog(target);
      }
    },
    openTransportSecondaryAddressPicker(target) {
      if (this.transportStationMode) {
        this.openTransportStationDialog(target);
        return;
      }
      this.openTransportMapDialog(target);
    },
    ensureTransportTypeBeforeAddress() {
      if (!this.shippingInfoFormEnabled || !this.isBillingFieldEmpty(this.form.transportType)) {
        return true;
      }
      this.$message.warning('请先选择运输方式');
      return false;
    },
    openTransportRouteDialog() {
      if (!this.ensureTransportTypeBeforeAddress()) return;
      this.transportRouteSelected = null;
      this.transportRoutePage.currentPage = 1;
      this.transportRouteQuery = {};
      this.transportRouteBox = true;
    },
    loadTransportRouteList() {
      this.transportRouteLoading = true;
      getCommonRouteList(this.transportRoutePage.currentPage, this.transportRoutePage.pageSize, {
        ...this.transportRouteQuery,
        allDept: 0,
      })
        .then(res => {
          const data = res.data.data || {};
          this.transportRouteRows = data.records || [];
          this.transportRoutePage.total = data.total || 0;
        })
        .finally(() => {
          this.transportRouteLoading = false;
        });
    },
    handleTransportRouteSearch() {
      this.transportRoutePage.currentPage = 1;
      this.loadTransportRouteList();
    },
    handleTransportRouteReset() {
      this.transportRouteQuery = {};
      this.transportRoutePage.currentPage = 1;
      this.loadTransportRouteList();
    },
    handleTransportRouteSizeChange(pageSize) {
      this.transportRoutePage.pageSize = pageSize;
      this.transportRoutePage.currentPage = 1;
      this.loadTransportRouteList();
    },
    handleTransportRouteCurrentChange(currentPage) {
      this.transportRoutePage.currentPage = currentPage;
      this.loadTransportRouteList();
    },
    formatTransportRouteUpdateUserName(row) {
      return formatUpdateUserName(row);
    },
    selectTransportRouteRow(row) {
      this.transportRouteSelected = row;
      this.applyTransportRoute(row);
      this.transportRouteBox = false;
    },
    applyTransportRoute(route = {}) {
      this.applyTransportAddress('departure', {
        addressName: route.departureName,
        detailAddress: route.departureAddress,
        longitude: route.departureLongitude,
        latitude: route.departureLatitude,
        contactName: route.departureContact,
        contactPhone: route.departurePhone,
        addressCode: route.departureAddressCode,
        siteCode: route.departureSiteCode,
      });
      this.applyTransportAddress('arrival', {
        addressName: route.arrivalName,
        detailAddress: route.arrivalAddress,
        longitude: route.arrivalLongitude,
        latitude: route.arrivalLatitude,
        contactName: route.arrivalContact,
        contactPhone: route.arrivalPhone,
        addressCode: route.arrivalAddressCode,
        siteCode: route.arrivalSiteCode,
      });
    },
    openTransportStationDialog(target) {
      if (!this.ensureTransportTypeBeforeAddress()) return;
      if (!this.transportStationMode) return;
      this.transportStationTarget = target;
      this.transportStationQuery = {};
      this.transportStationPage.currentPage = 1;
      this.transportStationBox = true;
    },
    getTransportStationListRequest() {
      const requestMap = {
        water: getPortTerminalList,
        rail: getRailwayStationList,
        air: getAirportMasterList,
      };
      return requestMap[this.transportMode];
    },
    loadTransportStationList() {
      const request = this.getTransportStationListRequest();
      if (!request) return;
      this.transportStationLoading = true;
      request(
        this.transportStationPage.currentPage,
        this.transportStationPage.pageSize,
        this.buildTransportStationQueryParams()
      )
        .then(res => {
          const data = res.data.data || {};
          this.transportStationRows = (data.records || []).map(row =>
            this.normalizeTransportStationRow(row)
          );
          this.transportStationPage.total = data.total || 0;
        })
        .finally(() => {
          this.transportStationLoading = false;
        });
    },
    buildTransportStationQueryParams() {
      const query = this.transportStationQuery || {};
      return Object.keys(query).reduce((params, key) => {
        const value = query[key];
        if (value !== undefined && value !== null && value !== '') {
          params[key] = value;
        }
        return params;
      }, {});
    },
    handleTransportStationSearch() {
      this.transportStationPage.currentPage = 1;
      this.loadTransportStationList();
    },
    handleTransportStationReset() {
      this.transportStationQuery = {};
      this.transportStationPage.currentPage = 1;
      this.loadTransportStationList();
    },
    handleTransportStationSizeChange(pageSize) {
      this.transportStationPage.pageSize = pageSize;
      this.transportStationPage.currentPage = 1;
      this.loadTransportStationList();
    },
    handleTransportStationCurrentChange(currentPage) {
      this.transportStationPage.currentPage = currentPage;
      this.loadTransportStationList();
    },
    normalizeTransportStationRow(row = {}) {
      const regionName =
        row.regionName ||
        [row.provinceName, row.cityName, row.districtName].filter(Boolean).join('');
      return {
        ...row,
        stationName: row.name || row.stationName || row.addressName || '',
        stationType: row.category || this.transportStationTypeLabel,
        stationCode: row.code || row.tmisCode || row.iataCode || row.icaoCode || row.siteCode || '',
        detailAddress: row.detailAddress || row.address || '',
        regionName,
      };
    },
    selectTransportStationRow(row) {
      const station = this.normalizeTransportStationRow(row);
      this.applyTransportAddress(this.transportStationTarget, {
        addressName: station.stationName,
        addressType: this.transportStationTypeLabel,
        detailAddress: station.detailAddress,
        longitude: station.longitude,
        latitude: station.latitude,
        regionName: station.regionName,
        regionCode: station.regionCode,
        siteCode: station.stationCode,
        siteCodeDisplay: station.stationCode,
        remark: station.remark,
      });
      this.transportStationBox = false;
    },
    openTransportAddressDialog(target) {
      if (!this.ensureTransportTypeBeforeAddress()) return;
      this.transportAddressTarget = target;
      this.transportAddressSelected = null;
      this.transportAddressQuery = {};
      this.transportAddressPage.currentPage = 1;
      this.transportAddressBox = true;
    },
    loadTransportAddressList() {
      this.transportAddressLoading = true;
      getCommonAddressList(
        this.transportAddressPage.currentPage,
        this.transportAddressPage.pageSize,
        {
          ...this.transportAddressQuery,
          allDept: 0,
        }
      )
        .then(res => {
          const data = res.data.data || {};
          this.transportAddressRows = data.records || [];
          this.transportAddressPage.total = data.total || 0;
        })
        .finally(() => {
          this.transportAddressLoading = false;
        });
    },
    handleTransportAddressSearch() {
      this.transportAddressPage.currentPage = 1;
      this.loadTransportAddressList();
    },
    handleTransportAddressReset() {
      this.transportAddressQuery = {};
      this.transportAddressPage.currentPage = 1;
      this.loadTransportAddressList();
    },
    handleTransportAddressSizeChange(pageSize) {
      this.transportAddressPage.pageSize = pageSize;
      this.transportAddressPage.currentPage = 1;
      this.loadTransportAddressList();
    },
    handleTransportAddressCurrentChange(currentPage) {
      this.transportAddressPage.currentPage = currentPage;
      this.loadTransportAddressList();
    },
    formatTransportAddressSiteCode(row) {
      return row.addressType === '常规地址' ? '/' : row.siteCodeDisplay || row.siteCode || '';
    },
    selectTransportAddressRow(row) {
      this.transportAddressSelected = row;
      this.applyTransportAddress(this.transportAddressTarget, {
        ...row,
        preferRegionName: true,
      });
      this.transportAddressBox = false;
    },
    applyTransportAddress(target, address = {}) {
      const prefix = target === 'departure' ? 'departure' : 'arrival';
      this.form[`${prefix}Name`] = address.preferRegionName
        ? address.regionName || address.addressName || ''
        : address.addressName || address.regionName || '';
      this.form[`${prefix}Address`] = address.detailAddress || address.address || '';
      this.form[`${prefix}AddressCode`] = address.addressCode || '';
      this.form[`${prefix}SiteCode`] =
        address.addressType === '常规地址'
          ? '/'
          : address.siteCodeDisplay || address.siteCode || '';
      this.form[`${prefix}Longitude`] = address.longitude || '';
      this.form[`${prefix}Latitude`] = address.latitude || '';
      this.form[`${prefix}Contact`] = address.contactName || this.form[`${prefix}Contact`] || '';
      this.form[`${prefix}Phone`] = address.contactPhone || this.form[`${prefix}Phone`] || '';
      this.$refs.crud?.validateField?.(`${prefix}Address`);
    },
    openTransportMapDialog(target) {
      if (this.dialogReadonly) return;
      if (!this.ensureTransportTypeBeforeAddress()) return;
      const prefix = target === 'departure' ? 'departure' : 'arrival';
      this.transportMapTarget = prefix;
      this.transportMapKeyword = this.form[`${prefix}Address`] || this.form[`${prefix}Name`] || '';
      this.transportMapSelected = this.buildTransportMapSelection({
        lng: this.form[`${prefix}Longitude`],
        lat: this.form[`${prefix}Latitude`],
        address: this.form[`${prefix}Address`],
        regionName: this.form[`${prefix}Name`],
      });
      this.transportMapStatus = this.transportMapSelected.longitude
        ? '已加载当前选点，可重新选点'
        : '可搜索地址或点击地图选点';
      this.transportMapBox = true;
    },
    loadAmap() {
      if (window.AMap && window.AMap.Map) {
        return Promise.resolve();
      }
      if (!amapLoader) {
        amapLoader = new Promise((resolve, reject) => {
          window._AMapSecurityConfig = {
            securityJsCode: AMAP_SECURITY_CODE,
          };
          const script = document.createElement('script');
          script.src = `https://webapi.amap.com/maps?v=2.0&key=${AMAP_KEY}`;
          script.async = true;
          script.onload = resolve;
          script.onerror = () => reject(new Error('高德地图组件加载失败'));
          document.body.appendChild(script);
        });
      }
      return amapLoader;
    },
    initTransportAmap() {
      this.loadAmap()
        .then(() => {
          this.$nextTick(() => {
            if (!this.transportAmap) {
              const center = this.toLngLat(
                this.transportMapSelected.longitude || 116.40769,
                this.transportMapSelected.latitude || 39.89945
              );
              this.transportAmap = new window.AMap.Map(this.$refs.transportAmap, {
                center,
                zoom: this.transportMapSelected.longitude ? 14 : 11,
              });
              window.AMap.plugin(['AMap.ToolBar', 'AMap.Geocoder'], () => {
                this.transportAmap.addControl(new window.AMap.ToolBar());
                if (!this.transportAmapGeocoder) {
                  this.transportAmapGeocoder = new window.AMap.Geocoder();
                }
              });
              this.transportAmap.on('click', event => this.pickTransportMapPoint(event.lnglat));
            } else if (typeof this.transportAmap.resize === 'function') {
              this.transportAmap.resize();
            }
            if (this.transportMapSelected.longitude) {
              this.renderTransportMapMarker(
                this.toLngLat(
                  this.transportMapSelected.longitude,
                  this.transportMapSelected.latitude
                )
              );
            }
          });
        })
        .catch(() => {
          this.$message.error('高德地图组件加载失败，请稍后重试或重新打开弹窗');
          this.transportMapBox = false;
        });
    },
    searchTransportMapKeyword() {
      const keyword = String(this.transportMapKeyword || '').trim();
      if (!keyword) {
        this.$message.warning('请输入地址关键词');
        return;
      }
      this.loadAmap()
        .then(() => {
          this.transportMapLoading = true;
          this.ensureTransportAmapGeocoder()
            .then(() => this.runAmapGeocode('location', keyword))
            .then(result => {
              const point = this.resolveMapPoint(result);
              if (!point) {
                this.transportMapStatus = '未找到匹配地址';
                this.$message.warning('地图搜索无匹配地址');
                return;
              }
              this.pickTransportMapPoint(point, keyword);
            })
            .catch(() => {
              this.transportMapStatus = '地图搜索失败';
              this.$message.error('地图搜索失败，请稍后重试');
            })
            .finally(() => {
              this.transportMapLoading = false;
            });
        })
        .catch(() => {
          this.$message.error('高德地图组件加载失败，请稍后重试或重新打开弹窗');
        });
    },
    pickTransportMapPoint(lnglat, keyword) {
      const longitude = this.getPointLng(lnglat);
      const latitude = this.getPointLat(lnglat);
      if (longitude === undefined || latitude === undefined) {
        this.$message.warning('选点坐标无效');
        return;
      }
      const point = this.toLngLat(longitude, latitude);
      this.renderTransportMapMarker(point);
      this.transportMapSelected = this.buildTransportMapSelection({
        lng: longitude,
        lat: latitude,
        address: keyword,
      });
      this.transportMapStatus = '正在反查地址...';
      this.ensureTransportAmapGeocoder()
        .then(() => this.runAmapGeocode('address', point))
        .then(result => {
          const address = this.resolveMapAddress(result);
          this.transportMapSelected = {
            ...this.transportMapSelected,
            detailAddress: address.detailAddress || this.transportMapSelected.detailAddress,
            regionName: address.regionName || this.transportMapSelected.regionName,
            regionCode: address.regionCode || this.transportMapSelected.regionCode,
          };
          this.transportMapKeyword =
            this.transportMapSelected.detailAddress || this.transportMapKeyword;
          this.transportMapStatus = this.transportMapSelected.detailAddress || '已选点，可确认回填';
        })
        .catch(() => {
          this.transportMapStatus = '反查地址失败';
          this.$message.error('反查地址失败，请重新选点');
        });
    },
    renderTransportMapMarker(point) {
      if (!this.transportAmap || !window.AMap) return;
      if (this.transportAmapMarker) {
        this.transportAmapMarker.setMap(null);
      }
      this.transportAmapMarker = new window.AMap.Marker({
        position: point,
      });
      this.transportAmapMarker.setMap(this.transportAmap);
      this.transportAmap.setCenter(point);
    },
    confirmTransportMapPick() {
      if (!this.transportMapSelected.longitude) {
        this.$message.warning('请先搜索或点击地图完成选点');
        return;
      }
      const prefix = this.transportMapTarget;
      this.form[`${prefix}Address`] =
        this.transportMapSelected.detailAddress || this.form[`${prefix}Address`];
      this.form[`${prefix}Name`] =
        this.transportMapSelected.regionName || this.form[`${prefix}Name`];
      this.form[`${prefix}Longitude`] = this.transportMapSelected.longitude;
      this.form[`${prefix}Latitude`] = this.transportMapSelected.latitude;
      this.transportMapBox = false;
      this.$refs.crud?.validateField?.(`${prefix}Address`);
    },
    buildTransportMapSelection({ lng, lat, address, regionName, regionCode }) {
      const longitude = this.formatCoordinate(lng);
      const latitude = this.formatCoordinate(lat);
      return {
        longitude,
        latitude,
        detailAddress: address || '',
        regionName: regionName || '',
        regionCode: regionCode || '',
      };
    },
    formatCoordinate(value) {
      if (value === undefined || value === null || value === '') return '';
      const numberValue = Number(value);
      return Number.isFinite(numberValue) ? numberValue.toFixed(6) : '';
    },
    toLngLat(lng, lat) {
      return new window.AMap.LngLat(Number(lng), Number(lat));
    },
    getPointLng(point) {
      if (!point) return undefined;
      if (typeof point.getLng === 'function') return point.getLng();
      return point.lng ?? point.lon;
    },
    getPointLat(point) {
      if (!point) return undefined;
      if (typeof point.getLat === 'function') return point.getLat();
      return point.lat;
    },
    ensureTransportAmapGeocoder() {
      if (this.transportAmapGeocoder) {
        return Promise.resolve(this.transportAmapGeocoder);
      }
      return new Promise((resolve, reject) => {
        if (!window.AMap || !window.AMap.plugin) {
          reject(new Error('高德地图组件未就绪'));
          return;
        }
        window.AMap.plugin(['AMap.Geocoder'], () => {
          try {
            this.transportAmapGeocoder = new window.AMap.Geocoder();
            resolve(this.transportAmapGeocoder);
          } catch (error) {
            reject(error);
          }
        });
      });
    },
    runAmapGeocode(action, input) {
      return new Promise((resolve, reject) => {
        const timer = window.setTimeout(() => {
          reject(new Error('高德地图请求超时'));
        }, 10000);
        const done = (status, result) => {
          window.clearTimeout(timer);
          if (status === 'complete' && result) {
            resolve(result);
            return;
          }
          reject(new Error('高德地图请求失败'));
        };
        try {
          if (action === 'location') {
            this.transportAmapGeocoder.getLocation(input, done);
          } else {
            this.transportAmapGeocoder.getAddress(input, done);
          }
        } catch (error) {
          window.clearTimeout(timer);
          reject(error);
        }
      });
    },
    resolveMapPoint(result) {
      if (!result) return null;
      const geocode = Array.isArray(result.geocodes) ? result.geocodes[0] : null;
      if (geocode && geocode.location) return geocode.location;
      if (result.location) return result.location;
      if (result.lnglat) return result.lnglat;
      if (result.getLng || result.lng || result.lon) return result;
      return null;
    },
    resolveMapAddress(result = {}) {
      const regeocode = result.regeocode || {};
      const component = regeocode.addressComponent || {};
      const city = Array.isArray(component.city) ? '' : component.city;
      const regionName = [component.province, city, component.district].filter(Boolean).join('');
      return {
        detailAddress: regeocode.formattedAddress || '',
        regionName,
        regionCode: component.adcode || '',
      };
    },
    openCommonCargoDialog() {
      this.commonCargoSelected = [];
      this.commonCargoQuery = {};
      this.commonCargoPage.currentPage = 1;
      this.commonCargoBox = true;
    },
    loadCommonCargoList() {
      this.commonCargoLoading = true;
      getCommonCargoList(this.commonCargoPage.currentPage, this.commonCargoPage.pageSize, {
        ...this.buildCommonCargoQueryParams(),
        allDept: 0,
      })
        .then(res => {
          const data = res.data.data || {};
          this.commonCargoRows = data.records || [];
          this.commonCargoPage.total = data.total || 0;
        })
        .finally(() => {
          this.commonCargoLoading = false;
        });
    },
    buildCommonCargoQueryParams() {
      const { cargoTypePath, ...query } = this.commonCargoQuery || {};
      return Object.keys(query).reduce((params, key) => {
        const value = query[key];
        if (value !== undefined && value !== null && value !== '') {
          params[key] = value;
        }
        return params;
      }, {});
    },
    handleCommonCargoSearch() {
      this.commonCargoPage.currentPage = 1;
      this.loadCommonCargoList();
    },
    handleCommonCargoReset() {
      this.commonCargoQuery = {};
      this.commonCargoPage.currentPage = 1;
      this.loadCommonCargoList();
    },
    handleCommonCargoSizeChange(pageSize) {
      this.commonCargoPage.pageSize = pageSize;
      this.commonCargoPage.currentPage = 1;
      this.loadCommonCargoList();
    },
    handleCommonCargoCurrentChange(currentPage) {
      this.commonCargoPage.currentPage = currentPage;
      this.loadCommonCargoList();
    },
    handleCommonCargoTypeChange(value) {
      const path = this.normalizeBillingCargoTypePath(value);
      const labels = this.getBillingCargoTypePathLabels(path);
      const firstCargoType = this.billingCargoTypeFlatOptions.find(
        item => String(item.id) === String(path[0] || '') && item.path?.length === 1
      );
      const secondCargoType = this.findBillingCargoTypeByPath(path);
      this.commonCargoQuery = {
        ...this.commonCargoQuery,
        cargoTypePath: path,
        firstCargoTypeName: labels[0] || '',
        firstCargoTypeCode: firstCargoType?.cargoCode || firstCargoType?.code || '',
        secondCargoTypeName: labels[1] || '',
        secondCargoTypeCode: secondCargoType?.cargoCode || secondCargoType?.code || '',
      };
      if (!path.length) {
        delete this.commonCargoQuery.firstCargoTypeName;
        delete this.commonCargoQuery.firstCargoTypeCode;
        delete this.commonCargoQuery.secondCargoTypeName;
        delete this.commonCargoQuery.secondCargoTypeCode;
      }
    },
    formatCommonCargoSize(row) {
      return row.cargoSize || row.size || row.dimension || row.dimensions || '';
    },
    async selectCommonCargoRow(row) {
      this.commonCargoSelected = [row];
      await this.ensureBillingCargoTypeOptions();
      this.addTransportCargoRow(-1, this.mapCommonCargoRow(row));
      this.commonCargoBox = false;
    },
    mapCommonCargoRow(row = {}) {
      const cargoTypePath = this.resolveCommonCargoTypePath(row);
      const cargoType = this.findBillingCargoTypeByPath(cargoTypePath);
      const labels = this.getBillingCargoTypePathLabels(cargoTypePath);
      return {
        cargoName: row.cargoName,
        cargoTypePath,
        cargoType: labels[1] || row.secondCargoTypeName || row.firstCargoTypeName,
        cargoTypeCode:
          cargoType?.cargoCode ||
          cargoType?.code ||
          row.secondCargoTypeCode ||
          row.firstCargoTypeCode,
        packageType: row.packageType,
        brand: row.brand,
        specification: row.specification,
        model: row.model,
        remark: row.remark || row.descriptionOne || '',
      };
    },
    async handleCargoImportFileChange(file) {
      const rawFile = file.raw;
      if (!rawFile) return;
      this.cargoImportLoading = true;
      try {
        const ExcelJSModule = await import('exceljs');
        const ExcelJS = ExcelJSModule.default || ExcelJSModule;
        const workbook = new ExcelJS.Workbook();
        await workbook.xlsx.load(await rawFile.arrayBuffer());
        const worksheet = workbook.worksheets[0];
        const headerRow = worksheet.getRow(1);
        const headerMap = {};
        headerRow.eachCell((cell, colNumber) => {
          headerMap[String(cell.value || '').trim()] = colNumber;
        });
        const fieldMap = {
          货物名称: 'cargoName',
          货物类型: 'cargoType',
          数量: 'quantity',
          数量单位: 'quantityUnit',
          包装: 'packageType',
          品牌: 'brand',
          规格: 'specification',
          型号: 'model',
          物料编码: 'materialCode',
          设备编码: 'deviceCode',
          备注: 'remark',
        };
        const rows = [];
        worksheet.eachRow((row, rowNumber) => {
          if (rowNumber === 1) return;
          const nextRow = defaultTransportCargo();
          Object.entries(fieldMap).forEach(([label, prop]) => {
            const columnIndex = headerMap[label];
            if (!columnIndex) return;
            nextRow[prop] = String(row.getCell(columnIndex).value || '').trim();
          });
          if (Object.values(nextRow).some(value => String(value || '').trim())) {
            rows.push(nextRow);
          }
        });
        if (!rows.length) {
          this.$message.warning('未读取到货物数据');
          return;
        }
        rows.forEach(row => this.addTransportCargoRow(-1, row));
        this.$message.success(`成功导入${rows.length}条货物`);
        this.cargoImportBox = false;
      } catch (error) {
        window.console.log(error);
        this.$message.error('货物导入失败，请检查文件格式');
      } finally {
        this.cargoImportLoading = false;
      }
    },
    loadOrganizationOptions() {
      if (!this.config.enableOrganizationSelect || this.organizationLoading) return;
      this.organizationLoading = true;
      getDeptTree(this.userInfo?.tenantId)
        .then(res => {
          this.organizationOptions = this.normalizeOrganizationOptions(res.data.data || []);
          this.organizationFlatOptions = this.flattenOrganizationOptions(this.organizationOptions);
          this.syncOrganizationSearchDic();
          this.syncCurrentOrganizationOption();
        })
        .finally(() => {
          this.organizationLoading = false;
        });
    },
    normalizeOrganizationOptions(tree = []) {
      return (tree || []).map(item => {
        const name = item.title || item.deptName || item.name || item.label || '';
        const children = this.normalizeOrganizationOptions(item.children || []);
        return {
          ...item,
          id: String(item.id),
          label: name,
          rawLabel: name,
          children: children.length ? children : undefined,
        };
      });
    },
    flattenOrganizationOptions(tree = []) {
      const result = [];
      tree.forEach(item => {
        result.push({
          id: String(item.id),
          label: item.label,
          rawLabel: item.rawLabel || item.label,
        });
        if (item.children && item.children.length) {
          result.push(...this.flattenOrganizationOptions(item.children));
        }
      });
      return result;
    },
    syncOrganizationSearchDic() {
      const column = this.findColumn?.(this.option.column, 'organizationName');
      if (column) {
        column.dicData = this.organizationOptions;
      }
    },
    syncCurrentOrganizationOption() {
      if (!this.form.organizationId || !this.form.organizationName) return;
      const exists = this.organizationFlatOptions.some(
        item => String(item.id) === String(this.form.organizationId)
      );
      if (!exists) {
        const current = {
          id: String(this.form.organizationId),
          label: this.form.organizationName,
          rawLabel: this.form.organizationName,
        };
        this.organizationOptions.unshift(current);
        this.organizationFlatOptions.unshift(current);
        this.syncOrganizationSearchDic();
      }
    },
    findOrganizationOption(organizationId) {
      const value = Array.isArray(organizationId)
        ? organizationId[organizationId.length - 1]
        : organizationId;
      return this.organizationFlatOptions.find(item => String(item.id) === String(value));
    },
    handleOrganizationChange(organizationId) {
      const organization = this.findOrganizationOption(organizationId);
      if (!organization) {
        this.form.organizationId = '';
        this.form.organizationName = '';
        return;
      }
      this.form.organizationId = organization.id;
      this.form.organizationName = organization.rawLabel || organization.label || '';
      this.$refs.crud?.validateField?.('organizationName');
    },
    handleAmountInput(prop, value) {
      const text = String(value || '').replace(/[^\d.]/g, '');
      const parts = text.split('.');
      this.form[prop] =
        parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
    },
    handleIntegerInput(prop, value) {
      this.form[prop] = String(value || '').replace(/\D/g, '');
    },
    handleContractDateChange() {
      this.syncContractPeriod(true);
    },
    syncContractPeriod(validate = true) {
      if (!this.config.enableContractPeriod) return;
      this.form.contractPeriod =
        this.form.startDate && this.form.endDate
          ? `${this.form.startDate}至${this.form.endDate}`
          : '';
      if (validate) {
        this.$refs.crud?.validateField?.('contractPeriod');
      }
    },
    applyCurrentUserHandler() {
      if (!this.config.enableCurrentUserHandler || this.form.handlerUserName) return;
      this.form.handlerUserName =
        this.userInfo?.realName || this.userInfo?.userName || this.userInfo?.account || '';
    },
    handleAttachmentChange(list) {
      const userName = this.userInfo?.realName || this.userInfo?.userName || '';
      const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
      this.attachmentRows = (list || []).map(item => ({
        ...item,
        description: item.description || '',
        uploadUserName: item.uploadUserName || userName,
        uploadTime: item.uploadTime || uploadTime,
      }));
      this.form.attachmentsJson = JSON.stringify(this.attachmentRows);
    },
    removeAttachment(index) {
      this.attachmentRows.splice(index, 1);
      this.form.attachmentsJson = JSON.stringify(this.attachmentRows);
    },
    formatFileSize(size) {
      const number = Number(size);
      if (!number) return '';
      if (number < 1024) return `${number}B`;
      if (number < 1024 * 1024) return `${(number / 1024).toFixed(1)}KB`;
      return `${(number / 1024 / 1024).toFixed(1)}MB`;
    },
    downloadAttachment(row) {
      const url = row.url || row.link;
      if (!url) {
        this.$message.warning('附件地址为空');
        return;
      }
      downloadFileByUrl(url, row.originalName || row.name || '附件');
    },
    handleBatchDownload() {
      this.attachmentRows.forEach(row => this.downloadAttachment(row));
    },
    handleDispatchItemAttachmentChange(list) {
      const userName = this.userInfo?.realName || this.userInfo?.userName || '';
      const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
      this.dispatchItemAttachmentRows = (list || []).map(item => ({
        ...item,
        description: item.description || '',
        uploadUserName: item.uploadUserName || userName,
        uploadTime: item.uploadTime || uploadTime,
      }));
    },
    removeDispatchItemAttachment(index) {
      this.dispatchItemAttachmentRows.splice(index, 1);
    },
    handleDispatchItemBatchDownload() {
      this.dispatchItemAttachmentRows.forEach(row => this.downloadAttachment(row));
    },
    handleContractFileChange(list) {
      const userName = this.userInfo?.realName || this.userInfo?.userName || '';
      const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
      this.contractFileRows = (list || []).map(item => ({
        ...item,
        uploadUserName: item.uploadUserName || userName,
        uploadTime: item.uploadTime || uploadTime,
      }));
      this.form.contractFileJson = JSON.stringify(this.contractFileRows);
    },
    removeContractFile(index) {
      this.contractFileRows.splice(index, 1);
      this.form.contractFileJson = JSON.stringify(this.contractFileRows);
    },
    cloneData(data) {
      return JSON.parse(JSON.stringify(data));
    },
    syncBillingPlanJson() {
      if (!this.config.enableBillingPlan) return;
      if (
        this.form.billingEnabled === undefined ||
        this.form.billingEnabled === null ||
        this.form.billingEnabled === ''
      ) {
        this.form.billingEnabled = 1;
      }
      this.form.billingPlanJson = JSON.stringify(this.billingPlanRows);
    },
    ensureBillingMatchRegionOptions() {
      if (!this.config.enableBillingPlan && !this.config.enableTransportPlanForm) {
        return Promise.resolve([]);
      }
      if (this.billingMatchRegionOptions.length) {
        return Promise.resolve(this.billingMatchRegionOptions);
      }
      if (this.billingMatchRegionRequest) {
        return this.billingMatchRegionRequest;
      }
      this.billingMatchRegionLoading = true;
      this.billingMatchRegionRequest = getRegionLazyTree()
        .then(res => {
          const regionTree = this.buildBillingMatchRegionTree(res.data.data || []);
          const regions = this.extractBillingMatchChinaRegions(regionTree);
          this.billingMatchRegionOptions = regions
            .map(item => this.normalizeBillingMatchRegionNode(item, 1))
            .filter(Boolean);
          return this.billingMatchRegionOptions;
        })
        .finally(() => {
          this.billingMatchRegionLoading = false;
          this.billingMatchRegionRequest = null;
        });
      return this.billingMatchRegionRequest;
    },
    ensureBillingCargoTypeOptions() {
      if (
        !this.config.enableBillingPlan &&
        !this.config.enableTransportPlanForm &&
        !this.taskInfoFormEnabled
      ) {
        return Promise.resolve([]);
      }
      if (this.billingCargoTypeOptions.length) {
        return Promise.resolve(this.billingCargoTypeOptions);
      }
      if (this.billingCargoTypeRequest) {
        return this.billingCargoTypeRequest;
      }
      this.billingCargoTypeLoading = true;
      this.billingCargoTypeRequest = getCargoTypeList(1, 9999)
        .then(res => {
          this.billingCargoTypeOptions = this.buildBillingCargoTypeTree(extractRecords(res));
          this.billingCargoTypeFlatOptions = this.flattenBillingCargoTypeOptions(
            this.billingCargoTypeOptions
          );
          return this.billingCargoTypeOptions;
        })
        .finally(() => {
          this.billingCargoTypeLoading = false;
          this.billingCargoTypeRequest = null;
        });
      return this.billingCargoTypeRequest;
    },
    buildBillingCargoTypeTree(cargoTypes = []) {
      const flatCargoTypes = [];
      const collectCargoTypes = list => {
        (list || []).forEach(item => {
          flatCargoTypes.push({
            ...item,
            children: undefined,
          });
          if (item.children?.length) collectCargoTypes(item.children);
        });
      };
      collectCargoTypes(cargoTypes);

      const nodeMap = new Map();
      const codeMap = new Map();
      flatCargoTypes.forEach(item => {
        const id = item.id ?? item.cargoCode ?? item.code;
        if (id === undefined || id === null) return;
        const node = {
          ...item,
          id: String(id),
          cargoName: this.formatBillingCargoTypeLabel(item),
          cargoCode: item.cargoCode || item.code || '',
          parentId:
            item.parentId === undefined || item.parentId === null ? '' : String(item.parentId),
          parentCargoCode: item.parentCargoCode || item.parentCode || '',
          children: [],
        };
        nodeMap.set(node.id, node);
        if (node.cargoCode) {
          codeMap.set(String(node.cargoCode), node);
        }
      });

      const rootNodes = [];
      nodeMap.forEach(node => {
        const parent =
          nodeMap.get(node.parentId) ||
          codeMap.get(String(node.parentCargoCode || '')) ||
          codeMap.get(String(node.parentId || ''));
        if (parent && parent !== node && Number(node.typeLevel) !== 1) {
          parent.children.push(node);
        } else {
          rootNodes.push(node);
        }
      });
      return rootNodes.map(item => this.normalizeBillingCargoTypeNode(item, 1)).filter(Boolean);
    },
    normalizeBillingCargoTypeNode(cargoType, level = 1, parentPath = []) {
      if (level > 2) return null;
      const id = String(cargoType.id);
      const path = [...parentPath, id];
      const children =
        level === 1
          ? (cargoType.children || [])
              .map(item => this.normalizeBillingCargoTypeNode(item, level + 1, path))
              .filter(Boolean)
          : [];
      return {
        ...cargoType,
        id,
        cargoName: this.formatBillingCargoTypeLabel(cargoType),
        cargoCode: cargoType.cargoCode || cargoType.code || '',
        path,
        leaf: level === 2,
        children: children.length ? children : undefined,
      };
    },
    flattenBillingCargoTypeOptions(options = []) {
      const result = [];
      const collectOptions = list => {
        (list || []).forEach(item => {
          result.push(item);
          if (item.children?.length) collectOptions(item.children);
        });
      };
      collectOptions(options);
      return result;
    },
    buildBillingMatchRegionTree(regions = []) {
      const flatRegions = [];
      const collectRegions = list => {
        (list || []).forEach(item => {
          flatRegions.push({
            ...item,
            children: undefined,
          });
          if (item.children?.length) collectRegions(item.children);
        });
      };
      collectRegions(regions);

      const nodeMap = new Map();
      flatRegions.forEach(item => {
        const id = item.id ?? item.code ?? item.value;
        if (id === undefined || id === null) return;
        nodeMap.set(String(id), {
          ...item,
          id: String(id),
          parentId:
            item.parentId === undefined || item.parentId === null
              ? String(item.parentCode || '')
              : String(item.parentId),
          children: [],
        });
      });

      const rootNodes = [];
      nodeMap.forEach(node => {
        const parent = nodeMap.get(node.parentId);
        if (parent && parent !== node) {
          parent.children.push(node);
        } else {
          rootNodes.push(node);
        }
      });
      return rootNodes;
    },
    normalizeBillingMatchRegionNode(region, level = 1) {
      if (level > 2) return null;
      const children =
        level === 1
          ? (region.children || [])
              .map(item => this.normalizeBillingMatchRegionNode(item, level + 1))
              .filter(Boolean)
          : [];
      return {
        ...region,
        title: region.title || region.name || '',
        leaf: level === 2,
        children: children.length ? children : undefined,
      };
    },
    isBillingMatchChinaRegion(region = {}) {
      const title = String(region.title || region.name || '').trim();
      return title === '中国' || title === '中华人民共和国';
    },
    extractBillingMatchChinaRegions(regions) {
      const china = (regions || []).find(item => this.isBillingMatchChinaRegion(item));
      if (china?.children?.length) return china.children;
      if (regions.length === 1 && regions[0].children?.length) return regions[0].children;
      return regions;
    },
    getBillingMatchRegionPathLabels(path = []) {
      let options = this.billingMatchRegionOptions;
      const labels = [];
      (path || []).forEach(value => {
        const option = (options || []).find(item => String(item.id) === String(value));
        if (!option) return;
        labels.push(option.title || option.name || '');
        options = option.children || [];
      });
      return labels.filter(Boolean);
    },
    handleBillingMatchRegionChange(prop, value) {
      const pathProp = `${prop}Path`;
      const path = Array.isArray(value) && value.length >= 2 ? value.slice(0, 2) : [];
      const labels = this.getBillingMatchRegionPathLabels(path);
      this.billingMatchForm[pathProp] = path;
      if (!path.length) {
        this.billingMatchForm[prop] = '';
        this.billingMatchForm[`${prop}Code`] = '';
        return;
      }
      if (labels.length) {
        this.billingMatchForm[prop] = labels.join('');
      }
      this.billingMatchForm[`${prop}Code`] = String(path[path.length - 1]);
    },
    formatBillingCargoTypeLabel(item = {}) {
      return item.cargoName || item.name || item.typeName || item.label || '';
    },
    filterBillingCargoType(node, keyword) {
      const text = String(keyword || '').trim();
      if (!text) return true;
      const labels = node.pathLabels?.length ? node.pathLabels : [node.label];
      return (
        labels.some(label => String(label || '').includes(text)) ||
        String(node.text || '').includes(text)
      );
    },
    getBillingCargoTypePathLabels(path = []) {
      let options = this.billingCargoTypeOptions;
      const labels = [];
      (path || []).forEach(value => {
        const option = (options || []).find(item => String(item.id) === String(value));
        if (!option) return;
        labels.push(option.cargoName || '');
        options = option.children || [];
      });
      return labels.filter(Boolean);
    },
    findBillingCargoTypeByPath(path = []) {
      const value = Array.isArray(path) && path.length >= 2 ? String(path[1]) : '';
      if (!value) return null;
      return (
        this.billingCargoTypeFlatOptions.find(
          item => String(item.id) === value && item.path?.length >= 2
        ) || null
      );
    },
    findBillingCargoTypeByCodeOrName(condition = {}) {
      const code = String(condition.cargoTypeCode || '').trim();
      const name = String(condition.cargoType || '').trim();
      return (
        this.billingCargoTypeFlatOptions.find(item => {
          const cargoCode = String(item.cargoCode || item.code || item.id || '');
          return item.path?.length >= 2 && code && cargoCode === code;
        }) ||
        this.billingCargoTypeFlatOptions.find(item => {
          const cargoName = this.formatBillingCargoTypeLabel(item);
          return item.path?.length >= 2 && name && (cargoName === name || name.endsWith(cargoName));
        }) ||
        null
      );
    },
    resolveCommonCargoTypePath(row = {}) {
      const path = this.normalizeBillingCargoTypePath(row.cargoTypePath);
      if (path.length) return path;
      const cargoType =
        this.findBillingCargoTypeByCodeOrName({
          cargoTypeCode: row.secondCargoTypeCode || row.firstCargoTypeCode || row.cargoTypeCode,
          cargoType: row.secondCargoTypeName || row.firstCargoTypeName || row.cargoType,
        }) ||
        this.findBillingCargoTypeByCodeOrName({
          cargoTypeCode: row.firstCargoTypeCode || '',
          cargoType: row.firstCargoTypeName || '',
        });
      return cargoType?.path || [];
    },
    handleBillingMatchCargoTypeChange(value) {
      const path =
        Array.isArray(value) && value.length >= 2
          ? value.slice(0, 2).map(item => String(item))
          : [];
      const cargoType = this.findBillingCargoTypeByPath(path);
      const labels = this.getBillingCargoTypePathLabels(path);
      this.billingMatchForm.cargoTypePath = path;
      if (!path.length || !cargoType) {
        this.billingMatchForm.cargoType = '';
        this.billingMatchForm.cargoTypeCode = '';
        return;
      }
      this.billingMatchForm.cargoType =
        labels.length > 1
          ? labels.join('/')
          : labels[0] || this.formatBillingCargoTypeLabel(cargoType || {});
      this.billingMatchForm.cargoTypeCode =
        cargoType?.cargoCode || cargoType?.code || cargoType?.id || '';
    },
    normalizeBillingCargoTypePath(path) {
      return Array.isArray(path) && path.length >= 2
        ? path.slice(0, 2).map(value => String(value))
        : [];
    },
    resolveBillingMatchCargoTypePath(condition = {}) {
      const path = this.normalizeBillingCargoTypePath(condition.cargoTypePath);
      if (path.length) return path;
      const cargoType = this.findBillingCargoTypeByCodeOrName(condition);
      return cargoType?.path || [];
    },
    normalizeBillingMatchRegionPath(path) {
      return Array.isArray(path) && path.length >= 2
        ? path.slice(0, 2).map(value => String(value))
        : [];
    },
    normalizeBillingMatchCondition(condition = {}) {
      const defaults = defaultBillingRule().matchCondition;
      const nextCondition = {
        ...defaults,
        ...(condition || {}),
      };
      nextCondition.originPath = this.normalizeBillingMatchRegionPath(condition?.originPath);
      nextCondition.destinationPath = this.normalizeBillingMatchRegionPath(
        condition?.destinationPath
      );
      nextCondition.cargoTypePath = this.normalizeBillingCargoTypePath(condition?.cargoTypePath);
      return nextCondition;
    },
    ensureBillingFeeCategoryOptions() {
      if (!this.config.enableBillingPlan) return Promise.resolve([]);
      if (this.billingFeeCategoryOptions.length) {
        return Promise.resolve(this.billingFeeCategoryOptions);
      }
      if (this.billingFeeCategoryRequest) {
        return this.billingFeeCategoryRequest;
      }
      this.billingFeeCategoryLoading = true;
      this.billingFeeCategoryRequest = getDictionary({ code: 'fee_category' })
        .then(res => {
          this.billingFeeCategoryOptions = res.data.data || [];
          return this.billingFeeCategoryOptions;
        })
        .finally(() => {
          this.billingFeeCategoryLoading = false;
          this.billingFeeCategoryRequest = null;
        });
      return this.billingFeeCategoryRequest;
    },
    ensureBillingUnitOptions() {
      if (!this.config.enableBillingPlan) return Promise.resolve([]);
      if (this.billingUnitOptions.length) {
        return Promise.resolve(this.billingUnitOptions);
      }
      if (this.billingUnitRequest) {
        return this.billingUnitRequest;
      }
      this.billingUnitLoading = true;
      this.billingUnitRequest = getDictionary({ code: 'unit_fee' })
        .then(res => {
          this.billingUnitOptions = res.data.data || [];
          return this.billingUnitOptions;
        })
        .finally(() => {
          this.billingUnitLoading = false;
          this.billingUnitRequest = null;
        });
      return this.billingUnitRequest;
    },
    resolveBillingFeeType(value) {
      const feeType = String(value || '').trim();
      if (!feeType) return '';
      const option = this.billingFeeCategoryOptions.find(
        item => String(item.dictKey || '') === feeType || String(item.dictValue || '') === feeType
      );
      return String((option && option.dictKey) || feeType);
    },
    getBillingFeeItemOptions(feeType) {
      const key = this.resolveBillingFeeType(feeType);
      return key ? this.billingFeeItemOptionsMap[key] || [] : [];
    },
    loadBillingFeeItemOptions(feeType) {
      const key = this.resolveBillingFeeType(feeType);
      if (!key) return Promise.resolve([]);
      if (this.billingFeeItemOptionsMap[key]) {
        return Promise.resolve(this.billingFeeItemOptionsMap[key]);
      }
      if (this.billingFeeItemRequestMap[key]) {
        return this.billingFeeItemRequestMap[key];
      }
      this.billingFeeItemLoadingMap[key] = true;
      this.billingFeeItemRequestMap[key] = getFeeItemList(1, 9999, { feeCategory: key })
        .then(res => {
          const records = extractRecords(res);
          this.billingFeeItemOptionsMap[key] = records;
          return records;
        })
        .finally(() => {
          this.billingFeeItemLoadingMap[key] = false;
          delete this.billingFeeItemRequestMap[key];
        });
      return this.billingFeeItemRequestMap[key];
    },
    loadBillingRuleFeeItems(rules = []) {
      const feeTypes = Array.from(
        new Set((rules || []).map(row => this.resolveBillingFeeType(row.feeType)).filter(Boolean))
      );
      feeTypes.forEach(feeType => this.loadBillingFeeItemOptions(feeType));
    },
    handleBillingFeeTypeChange(row, value) {
      row.feeType = this.resolveBillingFeeType(value);
      row.feeItem = '';
      this.loadBillingFeeItemOptions(row.feeType);
    },
    openBillingPlan(row, index = -1) {
      this.billingPlanMode = index > -1 ? 'edit' : 'add';
      this.billingPlanIndex = index;
      const plan = row ? this.cloneData(row) : defaultBillingPlan();
      this.billingPlanForm = {
        ...defaultBillingPlan(),
        ...plan,
        rules: this.normalizeBillingRules(
          Array.isArray(plan.rules) && plan.rules.length ? plan.rules : [defaultBillingRule()]
        ),
      };
      this.billingPlanBox = true;
      this.ensureBillingUnitOptions();
      this.ensureBillingFeeCategoryOptions().then(() => {
        this.billingPlanForm.rules = this.normalizeBillingRules(this.billingPlanForm.rules);
        this.loadBillingRuleFeeItems(this.billingPlanForm.rules);
      });
      this.$nextTick(() => this.$refs.billingPlanFormRef?.clearValidate?.());
    },
    submitBillingPlan() {
      this.$refs.billingPlanFormRef?.validate(valid => {
        if (!valid) return;
        if (!this.validateBillingRules()) return;
        const plan = this.cloneData(this.billingPlanForm);
        plan.rules = this.normalizeBillingRules(plan.rules).map(rule => {
          const ranges = this.canEditBillingLimit(rule) ? rule.limitRanges : [];
          return {
            ...rule,
            limitRanges: ranges,
            lowerLimit: ranges[0]?.lowerLimit || '',
            upperLimit: ranges[0]?.upperLimit || '',
            minimumBillingWeight: this.canEditMinimumBillingWeight(rule)
              ? rule.minimumBillingWeight
              : '',
          };
        });
        if (!this.billingPlanRows.length && this.billingPlanIndex === -1) {
          plan.defaultPlan = true;
        }
        if (plan.defaultPlan) {
          this.billingPlanRows.forEach(item => {
            item.defaultPlan = false;
          });
        }
        if (this.billingPlanIndex > -1) {
          this.billingPlanRows.splice(this.billingPlanIndex, 1, plan);
        } else {
          this.billingPlanRows.push(plan);
        }
        this.syncBillingPlanJson();
        this.billingPlanBox = false;
      });
    },
    validateBillingRules() {
      const groups = {};
      const requiredFields = [
        ['feeType', '费用类型'],
        ['feeItem', '费用项'],
        ['billingElement', '计费要素'],
        ['billingType', '计费类型'],
        ['billingUnit', '计费单位'],
        ['unitPrice', '单价'],
      ];
      for (const [index, row] of (this.billingPlanForm.rules || []).entries()) {
        const emptyField = requiredFields.find(([prop]) => this.isBillingFieldEmpty(row[prop]));
        if (emptyField) {
          this.$message.warning(`第${index + 1}行${emptyField[1]}不能为空`);
          return false;
        }
        if (row.billingElement && !this.getBillingTypeOptions(row).includes(row.billingType)) {
          this.$message.warning('请选择计费要素对应的计费类型');
          return false;
        }
        if (!this.canEditBillingLimit(row)) {
          continue;
        }
        const key = row.billingElement || '';
        groups[key] = groups[key] || [];
        const ranges = this.getBillingLimitRanges(row);
        for (const range of ranges) {
          const lower = Number(range.lowerLimit);
          const upper = Number(range.upperLimit);
          if (
            range.lowerLimit === '' ||
            range.upperLimit === '' ||
            Number.isNaN(lower) ||
            Number.isNaN(upper)
          ) {
            this.$message.warning('请完整填写计费要素上下限');
            return false;
          }
          if (lower > upper) {
            this.$message.warning('计费要素下限不能大于上限');
            return false;
          }
          groups[key].push({ lower, upper });
        }
      }
      return this.validateBillingLimitRangeGroups(groups);
    },
    validateBillingLimitRangeGroups(groups) {
      const precision = 0.000001;
      for (const [billingElement, ranges] of Object.entries(groups)) {
        if (ranges.length <= 1) continue;
        const sortedRanges = [...ranges].sort((prev, next) => {
          if (prev.lower !== next.lower) return prev.lower - next.lower;
          return prev.upper - next.upper;
        });
        for (let index = 1; index < sortedRanges.length; index += 1) {
          const prevRange = sortedRanges[index - 1];
          const currentRange = sortedRanges[index];
          if (currentRange.lower < prevRange.upper - precision) {
            this.$message.warning(`${billingElement}的计费要素区间不能重叠`);
            return false;
          }
          if (currentRange.lower > prevRange.upper + precision) {
            this.$message.warning(`${billingElement}的计费要素区间必须连续，不能存在间隙`);
            return false;
          }
        }
      }
      return true;
    },
    isBillingFieldEmpty(value) {
      return value === undefined || value === null || String(value).trim() === '';
    },
    removeBillingPlan(index) {
      this.$confirm('确定删除该计费方案?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      }).then(() => {
        this.billingPlanRows.splice(index, 1);
        this.syncBillingPlanJson();
      });
    },
    addBillingRule() {
      const row = defaultBillingRule();
      this.billingPlanForm.rules.push(row);
      this.ensureBillingFeeCategoryOptions();
    },
    copyBillingRule(row) {
      const nextRow = this.cloneData(row);
      this.billingPlanForm.rules.push(nextRow);
      this.loadBillingFeeItemOptions(nextRow.feeType);
    },
    removeBillingRule(index) {
      this.billingPlanForm.rules.splice(index, 1);
      if (!this.billingPlanForm.rules.length) {
        this.billingPlanForm.rules.push(defaultBillingRule());
      }
    },
    getBillingTypeOptions(row) {
      return this.billingElementTypeMap[row.billingElement] || [];
    },
    handleBillingElementChange(row) {
      if (!this.getBillingTypeOptions(row).includes(row.billingType)) {
        row.billingType = '';
      }
      if (!this.canEditMinimumBillingWeight(row)) {
        row.minimumBillingWeight = '';
      }
      this.handleBillingTypeChange(row);
    },
    handleBillingTypeChange(row) {
      if (!this.canEditBillingLimit(row)) {
        row.limitRanges = [{ lowerLimit: '', upperLimit: '' }];
        row.lowerLimit = '';
        row.upperLimit = '';
        return;
      }
      row.limitRanges = this.normalizeBillingLimitRanges(row);
      this.syncBillingLegacyLimit(row);
    },
    canEditBillingLimit(row) {
      return (
        !this.dialogReadonly &&
        Boolean(row.billingElement) &&
        Boolean(row.billingType) &&
        (row.billingType.includes('区间') || row.billingType.includes('阶梯'))
      );
    },
    canEditMinimumBillingWeight(row) {
      return !this.dialogReadonly && ['按重量', '按吨·公里'].includes(row.billingElement);
    },
    getBillingLimitRanges(row) {
      return Array.isArray(row.limitRanges) && row.limitRanges.length
        ? row.limitRanges
        : [{ lowerLimit: '', upperLimit: '' }];
    },
    normalizeBillingRules(rules = []) {
      return (rules || []).map(rule => {
        const nextRule = {
          ...defaultBillingRule(),
          ...rule,
        };
        nextRule.feeType = this.resolveBillingFeeType(nextRule.feeType);
        nextRule.limitRanges = this.normalizeBillingLimitRanges(nextRule);
        this.syncBillingLegacyLimit(nextRule);
        return nextRule;
      });
    },
    normalizeBillingLimitRanges(row) {
      const ranges = Array.isArray(row.limitRanges) ? row.limitRanges : [];
      const nextRanges = ranges
        .map(item => ({
          lowerLimit: item?.lowerLimit ?? '',
          upperLimit: item?.upperLimit ?? '',
        }))
        .filter(item => item.lowerLimit !== '' || item.upperLimit !== '');
      if (!nextRanges.length && (row.lowerLimit !== '' || row.upperLimit !== '')) {
        nextRanges.push({
          lowerLimit: row.lowerLimit ?? '',
          upperLimit: row.upperLimit ?? '',
        });
      }
      return nextRanges.length ? nextRanges : [{ lowerLimit: '', upperLimit: '' }];
    },
    syncBillingLegacyLimit(row) {
      const firstRange = row.limitRanges?.[0] || {};
      row.lowerLimit = firstRange.lowerLimit || '';
      row.upperLimit = firstRange.upperLimit || '';
    },
    addBillingLimitRange(row, index) {
      row.limitRanges = this.getBillingLimitRanges(row);
      row.limitRanges.splice(index + 1, 0, { lowerLimit: '', upperLimit: '' });
    },
    removeBillingLimitRange(row, index) {
      row.limitRanges = this.getBillingLimitRanges(row);
      row.limitRanges.splice(index, 1);
      if (!row.limitRanges.length) {
        row.limitRanges.push({ lowerLimit: '', upperLimit: '' });
      }
      this.syncBillingLegacyLimit(row);
    },
    handleBillingLimitInput(row, index, prop, value) {
      row.limitRanges = this.getBillingLimitRanges(row);
      const text = String(value || '').replace(/[^\d.]/g, '');
      const parts = text.split('.');
      row.limitRanges[index][prop] =
        parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
      this.syncBillingLegacyLimit(row);
    },
    openBillingMatch(row, index) {
      this.billingMatchRuleIndex = index;
      this.billingMatchForm = this.normalizeBillingMatchCondition(row.matchCondition);
      this.billingMatchBox = true;
      this.ensureBillingMatchRegionOptions();
      this.ensureBillingCargoTypeOptions().then(() => {
        const path = this.resolveBillingMatchCargoTypePath(this.billingMatchForm);
        if (path.length) {
          this.handleBillingMatchCargoTypeChange(path);
        }
      });
    },
    saveBillingMatch() {
      const row = this.billingPlanForm.rules[this.billingMatchRuleIndex];
      if (row) {
        this.handleBillingMatchRegionChange('origin', this.billingMatchForm.originPath);
        this.handleBillingMatchRegionChange('destination', this.billingMatchForm.destinationPath);
        this.handleBillingMatchCargoTypeChange(this.billingMatchForm.cargoTypePath);
        row.matchCondition = this.cloneData(this.billingMatchForm);
      }
      this.billingMatchBox = false;
    },
    handleBillingDecimalInput(row, prop, value) {
      const text = String(value || '').replace(/[^\d.]/g, '');
      const parts = text.split('.');
      row[prop] =
        parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
    },
    searchReset() {
      this.query = {};
      this.contractExpiryScope = '';
      this.contractSignType = '';
      this.page.currentPage = 1;
      this.onLoad(this.page);
    },
    searchChange(params, done) {
      this.query = {
        ...params,
        ...(this.contractSignType ? { signType: this.contractSignType } : {}),
      };
      this.page.currentPage = 1;
      this.onLoad(this.page, params);
      done();
    },
    selectionChange(list) {
      this.selectionList = list;
    },
    selectionClear() {
      this.selectionList = [];
      if (this.$refs.crud) {
        this.$refs.crud.toggleSelection();
      }
    },
    currentChange(currentPage) {
      this.page.currentPage = currentPage;
    },
    sizeChange(pageSize) {
      this.page.pageSize = pageSize;
    },
    refreshChange() {
      this.onLoad(this.page, this.query);
    },
    handleSearchIconChange(expanded) {
      this.searchExpanded = expanded;
    },
    handleContractExpiryScopeChange(expireScope) {
      this.contractExpiryScope = expireScope;
      this.page.currentPage = 1;
      this.onLoad(this.page, this.query);
    },
    refreshContractExpiryStats(query) {
      if (!this.config.enableContractExpiryTags || typeof this.api.expireStats !== 'function') return;
      const statsQuery = { ...query };
      delete statsQuery.expireScope;
      this.api.expireStats(statsQuery).then(res => {
        this.contractExpiryStats = {
          ...this.contractExpiryStats,
          ...(res.data.data || {}),
        };
      });
    },
    handleScopeChange() {
      if (!this.canViewAllDept) {
        this.allDept = 0;
      }
      this.page.currentPage = 1;
      this.onLoad(this.page);
    },
    normalizeSearchRangeValue(value, suffix) {
      if (!value) return undefined;
      return suffix ? `${value} ${suffix}` : value;
    },
    normalizeSearchRangeParams(params = {}) {
      const query = { ...params };
      const rangeMap = this.config.searchRangeMap || {};
      Object.entries(rangeMap).forEach(([rangeProp, mapping]) => {
        const [startProp, endProp, startSuffix, endSuffix] = Array.isArray(mapping)
          ? mapping
          : [mapping.startProp, mapping.endProp, mapping.startSuffix, mapping.endSuffix];
        const range = query[rangeProp];
        if (Array.isArray(range) && range.length === 2) {
          query[startProp] = this.normalizeSearchRangeValue(range[0], startSuffix);
          query[endProp] = this.normalizeSearchRangeValue(range[1], endSuffix);
        }
        delete query[rangeProp];
      });
      return query;
    },
    normalizeQueryParams(params = {}) {
      let query = { ...params };
      if (Object.keys(this.config.searchRangeMap || {}).length) {
        query = this.normalizeSearchRangeParams(query);
      }
      if (this.config.enableOrganizationSelect && query.organizationName) {
        const organizationId = Array.isArray(query.organizationName)
          ? query.organizationName[query.organizationName.length - 1]
          : query.organizationName;
        if (!organizationId) {
          delete query.organizationId;
          delete query.organizationName;
          return query;
        }
        const organization = this.findOrganizationOption(organizationId);
        query.organizationId = organizationId;
        if (organization) {
          query.organizationName = organization.rawLabel || organization.label || '';
        } else {
          delete query.organizationName;
        }
        if (this.canViewAllDept) {
          query.allDept = 1;
        }
      }
      return query;
    },
    onLoad(page, params = {}) {
      this.loading = true;
      const query = this.normalizeQueryParams({
        ...params,
        ...this.query,
        allDept: this.allDept,
        ...(this.contractSignType ? { signType: this.contractSignType } : {}),
        ...(this.contractExpiryScope ? { expireScope: this.contractExpiryScope } : {}),
      });
      this.api
        .getList(page.currentPage, page.pageSize, query)
        .then(res => {
          const result = res.data.data;
          this.page.total = result.total;
          this.data = result.records;
          this.selectionClear();
          this.refreshContractExpiryStats(query);
        })
        .finally(() => {
          this.loading = false;
        });
    },
    buildExportParams() {
      const query = this.normalizeQueryParams({ ...this.query, allDept: this.allDept });
      return {
        ...query,
        ids: this.ids,
        [this.website.tokenHeader]: getToken(),
      };
    },
    handleExport() {
      this.$confirm(`是否导出${this.config.exportName || this.config.title}?`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      }).then(() => {
        NProgress.start();
        exportBlob(this.config.exportUrl, this.buildExportParams(), { feedback: true })
          .then(res => {
            downloadXls(
              res.data,
              `${this.config.exportName || this.config.title}${this.$dayjs().format(
                'YYYY-MM-DD HH:mm:ss'
              )}.xlsx`
            );
          })
          .finally(() => {
            NProgress.done();
          });
      });
    },
    handleTemplate() {
      NProgress.start();
      exportBlob(
        this.config.templateUrl,
        {
          [this.website.tokenHeader]: getToken(),
        },
        { feedback: true }
      )
        .then(res => {
          downloadXls(res.data, `${this.config.title}模板.xlsx`);
        })
        .finally(() => {
          NProgress.done();
        });
    },
    handleImport() {
      if (this.config.enableWaybillImport) {
        this.waybillImportBox = true;
        return;
      }
      if (this.config.enableTransportPlanImport) {
        this.transportPlanImportBox = true;
        this.loadProjectOptions();
        return;
      }
      const column = this.findColumn(this.excelOptionConfig.column, 'excelFile');
      column.action = this.config.importUrl;
      openImportDialog(this, this.config.title, () => this.onLoad(this.page, this.query));
    },
    resetTransportPlanImport() {
      this.transportPlanImportForm = {
        projectId: '',
        projectName: '',
        customerName: '',
        contractId: '',
        contractName: '',
        file: null,
      };
      this.transportPlanImportFiles = [];
      this.transportPlanImportSourceFile = null;
      this.transportPlanImportRows = [];
      this.transportPlanImportSelection = [];
      this.transportPlanImportTab = 'all';
      this.transportPlanImportEditingKey = '';
      this.transportPlanImportEditSnapshot = null;
      this.transportPlanImportContractOptions = [];
      this.transportPlanImportContractLoading = false;
      this.$refs.transportPlanImportFormRef?.clearValidate();
    },
    handleTransportPlanImportProjectChange(projectId) {
      const project = this.projectOptions.find(item => String(item.id) === String(projectId));
      this.transportPlanImportForm.customerName =
        project?.customerName || project?.customerNames || project?.customer || '';
      this.transportPlanImportForm.projectName = project?.projectName || '';
      this.transportPlanImportForm.contractId = '';
      this.transportPlanImportForm.contractName = '';
      this.transportPlanImportContractOptions = [];
      if (!projectId) return;
      this.transportPlanImportContractLoading = true;
      getContractList(1, 9999, { projectId, projectName: project?.projectName })
        .then(res => {
          this.transportPlanImportContractOptions = extractRecords(res);
          const contract = this.transportPlanImportContractOptions[0];
          this.transportPlanImportForm.customerName =
            this.transportPlanImportForm.customerName || contract?.customerName || '';
          this.transportPlanImportForm.contractId = contract?.id || '';
          this.transportPlanImportForm.contractName = contract?.contractName || '';
        })
        .finally(() => {
          this.transportPlanImportContractLoading = false;
        });
    },
    handleTransportPlanImportContractChange(contractId) {
      const contract = this.transportPlanImportContractOptions.find(
        item => String(item.id) === String(contractId)
      );
      this.transportPlanImportForm.contractName = contract?.contractName || '';
    },
    async handleTransportPlanImportFileChange(file, fileList) {
      const isExcel = /\.(xls|xlsx)$/i.test(file.name || '');
      if (!isExcel) {
        this.$message.error('请上传 .xls 或 .xlsx 格式文件');
        this.transportPlanImportFiles = [];
        this.transportPlanImportForm.file = null;
        return;
      }
      try {
        const XLSX = await import('xlsx');
        const workbook = XLSX.read(await file.raw.arrayBuffer(), {
          type: 'array',
          cellDates: false,
        });
        const worksheet = workbook.Sheets[workbook.SheetNames[0]];
        const values = XLSX.utils.sheet_to_json(worksheet, {
          header: 1,
          defval: '',
          raw: false,
          blankrows: false,
        });
        const headers = values[0] || [];
        const headerMap = headers.reduce((result, label, index) => {
          const normalized = String(label || '')
            .replace(/^\*/, '')
            .trim();
          if (normalized) result[normalized] = index;
          return result;
        }, {});
        const rows = values.slice(1).reduce((result, cells, index) => {
          const row = {
            _key: `${Date.now()}-${index}-${Math.random().toString(36).slice(2)}`,
          };
          transportPlanImportColumns.forEach(([label, prop]) => {
            row[prop] = String(cells[headerMap[label]] ?? '').trim();
          });
          if (transportPlanImportColumns.some(([, prop]) => row[prop])) result.push(row);
          return result;
        }, []);
        if (!rows.length) {
          this.$message.warning('未读取到计划明细数据');
          this.transportPlanImportFiles = [];
          this.transportPlanImportForm.file = null;
          return;
        }
        this.transportPlanImportFiles = fileList.slice(-1);
        this.transportPlanImportSourceFile = file.raw;
        this.transportPlanImportForm.file = file.raw;
        this.transportPlanImportRows = rows;
        this.markTransportPlanImportDuplicates();
        this.$refs.transportPlanImportFormRef?.validateField('file');
        this.$message.success(`已读取${rows.length}条计划明细`);
      } catch (error) {
        this.transportPlanImportFiles = [];
        this.transportPlanImportSourceFile = null;
        this.transportPlanImportForm.file = null;
        this.transportPlanImportRows = [];
        this.$message.error('计划明细表读取失败，请检查文件格式');
      }
    },
    handleTransportPlanImportFileRemove() {
      this.transportPlanImportFiles = [];
      this.transportPlanImportSourceFile = null;
      this.transportPlanImportForm.file = null;
      this.transportPlanImportRows = [];
      this.transportPlanImportSelection = [];
      this.transportPlanImportEditingKey = '';
      this.transportPlanImportEditSnapshot = null;
    },
    transportPlanImportDuplicateKey(row) {
      return transportPlanImportColumns
        .map(([, prop]) => String(row[prop] || '').trim())
        .join('\u0001');
    },
    markTransportPlanImportDuplicates() {
      const countMap = this.transportPlanImportRows.reduce((result, row) => {
        const key = this.transportPlanImportDuplicateKey(row);
        result[key] = (result[key] || 0) + 1;
        return result;
      }, {});
      this.transportPlanImportRows.forEach(row => {
        row._duplicate = countMap[this.transportPlanImportDuplicateKey(row)] > 1;
      });
    },
    handleTransportPlanImportSelectionChange(rows) {
      this.transportPlanImportSelection = rows;
    },
    isEditingTransportPlanImportRow(row) {
      return this.transportPlanImportEditingKey === row._key;
    },
    editTransportPlanImportRow(row) {
      if (this.transportPlanImportEditingKey && this.transportPlanImportEditingKey !== row._key) {
        this.$message.warning('请先保存或取消当前编辑');
        return;
      }
      this.transportPlanImportEditingKey = row._key;
      this.transportPlanImportEditSnapshot = { ...row };
    },
    saveTransportPlanImportRow() {
      this.transportPlanImportEditingKey = '';
      this.transportPlanImportEditSnapshot = null;
      this.markTransportPlanImportDuplicates();
    },
    cancelTransportPlanImportRow(row) {
      Object.assign(row, this.transportPlanImportEditSnapshot || {});
      this.transportPlanImportEditingKey = '';
      this.transportPlanImportEditSnapshot = null;
      this.markTransportPlanImportDuplicates();
    },
    removeTransportPlanImportRow(row) {
      this.transportPlanImportRows = this.transportPlanImportRows.filter(
        item => item._key !== row._key
      );
      this.transportPlanImportSelection = this.transportPlanImportSelection.filter(
        item => item._key !== row._key
      );
      this.markTransportPlanImportDuplicates();
    },
    removeSelectedTransportPlanImportRows() {
      const selectedKeys = new Set(this.transportPlanImportSelection.map(row => row._key));
      this.transportPlanImportRows = this.transportPlanImportRows.filter(
        row => !selectedKeys.has(row._key)
      );
      this.transportPlanImportSelection = [];
      this.markTransportPlanImportDuplicates();
    },
    validateTransportPlanImportRows() {
      if (!this.transportPlanImportRows.length) {
        this.$message.warning('请保留至少一条计划明细');
        return false;
      }
      const requiredFields = [
        ['planName', '计划名称'],
        ['transportType', '运输方式'],
        ['planStartDate', '计划开始日期'],
        ['planEndDate', '计划结束日期'],
        ['cargoType', '货物类型'],
        ['departureAddress', '发货地址'],
        ['arrivalAddress', '收货地址'],
      ];
      for (const [index, row] of this.transportPlanImportRows.entries()) {
        const emptyField = requiredFields.find(([prop]) => !String(row[prop] || '').trim());
        if (emptyField) {
          this.$message.warning(`第${index + 1}行${emptyField[1]}不能为空`);
          return false;
        }
        const isValidDate = value => {
          if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
          const date = new Date(`${value}T00:00:00`);
          return !Number.isNaN(date.getTime()) && date.toISOString().slice(0, 10) === value;
        };
        if (!isValidDate(row.planStartDate) || !isValidDate(row.planEndDate)) {
          this.$message.warning(`第${index + 1}行计划日期格式必须为 YYYY-MM-DD`);
          return false;
        }
        if (row.planEndDate < row.planStartDate) {
          this.$message.warning(`第${index + 1}行计划结束日期不能早于计划开始日期`);
          return false;
        }
        if (
          row.quantity !== '' &&
          (!Number.isFinite(Number(row.quantity)) || Number(row.quantity) < 0)
        ) {
          this.$message.warning(`第${index + 1}行数量必须为非负数`);
          return false;
        }
      }
      return true;
    },
    async buildTransportPlanImportFile() {
      const XLSX = await import('xlsx');
      const headers = transportPlanImportColumns.map(([label]) => {
        return [
          '计划名称',
          '运输方式',
          '计划开始日期',
          '计划结束日期',
          '货物类型',
          '发货地址',
          '收货地址',
        ].includes(label)
          ? `*${label}`
          : label;
      });
      const values = [
        headers,
        ...this.transportPlanImportRows.map(row =>
          transportPlanImportColumns.map(([, prop]) => String(row[prop] ?? ''))
        ),
      ];
      const workbook = XLSX.utils.book_new();
      XLSX.utils.book_append_sheet(workbook, XLSX.utils.aoa_to_sheet(values), '运输计划导入模板');
      const binary = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
      const fileName = (
        this.transportPlanImportSourceFile?.name || '运输计划导入模板.xlsx'
      ).replace(/\.xls$/i, '.xlsx');
      return new File([binary], fileName, {
        type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
      });
    },
    handleTransportPlanTemplate() {
      exportBlob(this.config.transportPlanTemplateUrl, {}, { feedback: true }).then(res => {
        downloadXls(res.data, '运输计划导入模板.xlsx');
      });
    },
    async submitTransportPlanImport() {
      const valid = await this.$refs.transportPlanImportFormRef?.validate().catch(() => false);
      if (!valid || this.transportPlanImportLoading) return;
      if (!this.validateTransportPlanImportRows()) return;
      if (typeof this.api.importTransportPlan !== 'function') {
        this.$message.error('运输计划导入接口不可用');
        return;
      }
      this.transportPlanImportLoading = true;
      try {
        const file = await this.buildTransportPlanImportFile();
        const res = await this.api.importTransportPlan({ ...this.transportPlanImportForm, file });
        const contentType = res.headers?.['content-type'] || res.data?.type || '';
        if (
          contentType.includes('application/vnd.ms-excel') ||
          contentType.includes('spreadsheetml')
        ) {
          downloadXls(
            res.data,
            `运输计划导入失败明细${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`
          );
          this.$message.warning('部分数据导入失败，已下载失败明细');
        } else {
          const text = await res.data.text();
          const result = text ? JSON.parse(text) : {};
          if (result.code !== 200) throw new Error(result.msg || '导入失败');
          this.$message.success('导入完成');
        }
        this.transportPlanImportBox = false;
        this.onLoad(this.page, this.query);
      } catch (error) {
        this.$message.error(error.message || '导入失败');
      } finally {
        this.transportPlanImportLoading = false;
      }
    },
    handleCopy(row) {
      this.$confirm(`确定复制该${this.config.title}?`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      })
        .then(() => this.api.copy(row.id))
        .then(() => {
          this.$message.success('复制成功');
          this.onLoad(this.page, this.query);
        });
    },
    handleAction(action, row) {
      const actionName = {
        enable: '启用',
        disable: '停用',
        cancel: '取消',
        complete: '完成',
        reassign: '重新派单',
      }[action];
      this.$confirm(`确定${actionName}该${this.config.title}?`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      })
        .then(() => this.api[action](row.id))
        .then(() => {
          this.$message.success(`${actionName}成功`);
          this.onLoad(this.page, this.query);
        });
    },
    handleCustomOperation(operation, row) {
      if (operation.action === 'createFromTemplate') {
        this.api.getDetail(row.id).then(res => {
          const template = res.data?.data || row;
          const templateType = template.templateType || template.templateTypeName;
          const target =
            templateType === '运单' ? '/business/waybill-manage' : '/business/transport-plan';
          sessionStorage.setItem(
            'business-template-create',
            JSON.stringify({ target: target.slice('/business/'.length), data: template })
          );
          this.$router.push({ path: target, query: { fromTemplate: Date.now().toString() } });
        });
        return;
      }
      if (operation.action === 'flow') {
        this.handleFlow(row);
        return;
      }
      if (operation.action === 'dispatch') {
        this.openDispatchDialog(row);
        return;
      }
      const run = value => {
        const args = operation.prompt ? [row.id, value] : [row.id];
        this.api[operation.action](...args).then(() => {
          this.$message.success(`${operation.label}成功`);
          this.onLoad(this.page, this.query);
        });
      };
      if (operation.prompt) {
        this.$prompt(operation.prompt, operation.label, {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          inputPattern: /\S+/,
          inputErrorMessage: operation.promptError || '请输入内容',
        }).then(({ value }) => run(value));
        return;
      }
      this.$confirm(operation.confirm || `确定${operation.label}该${this.config.title}?`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      }).then(() => run());
    },
    handleFlow(row, loadedDetail = false) {
      const processInstanceId = row.processInstanceId || row.processInstanceID || row.procInstId;
      if (
        !processInstanceId &&
        !loadedDetail &&
        row.id &&
        typeof this.api.getDetail === 'function'
      ) {
        this.api.getDetail(row.id).then(res => this.handleFlow(res.data.data || {}, true));
        return;
      }
      if (!processInstanceId) {
        this.$message.warning('流程实例为空，无法查看流程');
        return;
      }
      if (this.website.design.designMode) {
        this.processInstanceId = processInstanceId;
      } else {
        this.flowUrl = `/api/blade-flow/process/diagram-view?processInstanceId=${processInstanceId}`;
      }
      this.flowBox = true;
    },
    openDispatchDialog(row) {
      if (!row?.id) {
        this.$message.warning('运输计划数据异常，无法调度');
        return;
      }
      this.dispatchRow = { ...row };
      this.dispatchBox = true;
      this.dispatchLoading = true;
      this.dispatchRows = [];
      const request =
        this.api && typeof this.api.getDetail === 'function'
          ? this.api.getDetail(row.id)
          : Promise.resolve({ data: { data: row } });
      request
        .then(res => {
          const detail = {
            ...row,
            ...(res?.data?.data || {}),
          };
          this.dispatchRow = detail;
          this.dispatchRows = this.buildDispatchRows(detail);
        })
        .finally(() => {
          this.dispatchLoading = false;
        });
    },
    resetDispatchDialog() {
      this.dispatchRow = {};
      this.dispatchRows = [];
      this.dispatchLoading = false;
      this.dispatchSaving = '';
      this.dispatchItemBox = false;
      this.dispatchItemIndex = -1;
      this.dispatchItemForm = defaultDispatchRow();
      this.dispatchItemAttachmentRows = [];
    },
    buildDispatchRows(plan = {}) {
      const goodsRows = this.parseJsonArray(plan.goodsJson);
      const sourceRows = goodsRows.length ? goodsRows : [plan];
      return sourceRows.map((item, index) => this.normalizeDispatchRow(plan, item, index));
    },
    normalizeDispatchRow(plan = {}, item = {}, index = 0) {
      const cargoInfo = this.formatDispatchCargoInfo(plan, item);
      return {
        _key: `${plan.id || 'dispatch'}-${index}-${
          item.id || item.cargoName || item.vehicleNo || 'row'
        }`,
        taskEntryMode: item.taskEntryMode || 'simple',
        transportType: item.transportType || plan.transportType || '',
        carrierType: item.carrierType || plan.carrierType || '承运商',
        carrierName: item.carrierName || plan.carrierName || '',
        driverName: item.driverName || plan.driverName || '',
        driverPhone: item.driverPhone || plan.driverPhone || '',
        vehicleNo: item.vehicleNo || plan.vehicleNo || '',
        trailerVehicleNo: item.trailerVehicleNo || '',
        escortName: item.escortName || '',
        escortPhone: item.escortPhone || '',
        mileage: item.mileage || '',
        cargoType: item.cargoType || item.goodsType || plan.cargoType || '',
        cargoName: item.cargoName || item.goodsName || plan.cargoName || '',
        cargoInfo,
        quantity: item.quantity || item.cargoQuantity || item.goodsQuantity || '',
        quantityUnit: item.quantityUnit || item.goodsQuantityUnit || item.unit || '吨',
        specification: item.specification || item.spec || '',
        model: item.model || '',
        packageType: item.packageType || item.package || '',
        estimatedStartTime: item.estimatedStartTime || plan.planStartDate || '',
        estimatedEndTime: item.estimatedEndTime || plan.planEndDate || '',
        departureAddress: item.departureAddress || plan.departureAddress || '',
        departureName: item.departureName || plan.departureName || '',
        departureContact: item.departureContact || plan.departureContact || '',
        departurePhone: item.departurePhone || plan.departurePhone || '',
        arrivalAddress: item.arrivalAddress || plan.arrivalAddress || '',
        arrivalName: item.arrivalName || plan.arrivalName || '',
        arrivalContact: item.arrivalContact || plan.arrivalContact || '',
        arrivalPhone: item.arrivalPhone || plan.arrivalPhone || '',
        dispatchUserName: item.dispatchUserName || plan.dispatchUserName || '',
        dispatchTime: item.dispatchTime || plan.dispatchTime || '',
        attachmentsJson: item.attachmentsJson || '',
        remark: item.remark || item.taskRemark || plan.remark || '',
      };
    },
    formatDispatchCargoInfo(plan = {}, item = {}) {
      const cargoName = item.cargoName || item.goodsName || plan.cargoName || '';
      const cargoType = item.cargoType || item.goodsType || plan.cargoType || '';
      const quantity = item.quantity || item.cargoQuantity || item.goodsQuantity || '';
      const unit = item.quantityUnit || item.goodsQuantityUnit || item.unit || '';
      const quantityText = quantity ? `${quantity}${unit}` : '';
      return (
        [cargoName, cargoType, quantityText].filter(Boolean).join(' - ') || plan.goodsInfo || ''
      );
    },
    openDispatchItemDialog(index = -1, row = defaultDispatchRow()) {
      const defaultGoods = this.dispatchPlanGoodsRows[0] || {};
      const baseRow = {
        ...defaultDispatchRow(),
        transportType: this.dispatchRow.transportType || '',
        carrierType: this.dispatchRow.carrierType || '承运商',
        carrierName: this.dispatchRow.carrierName || '',
        driverName: this.dispatchRow.driverName || '',
        driverPhone: this.dispatchRow.driverPhone || '',
        vehicleNo: this.dispatchRow.vehicleNo || '',
        cargoName: defaultGoods.cargoName || this.dispatchRow.cargoName || '',
        cargoType: defaultGoods.cargoType || this.dispatchRow.cargoType || '',
        quantity:
          index >= 0
            ? defaultGoods.quantity || ''
            : this.formatDispatchQuantity(this.dispatchSummaryRemainingQuantity),
        quantityUnit: defaultGoods.quantityUnit || '吨',
        specification: defaultGoods.specification || '',
        model: defaultGoods.model || '',
        packageType: defaultGoods.packageType || '',
        departureName: this.dispatchRow.departureName || '',
        departureAddress: this.dispatchRow.departureAddress || '',
        departureContact: this.dispatchRow.departureContact || '',
        departurePhone: this.dispatchRow.departurePhone || '',
        arrivalName: this.dispatchRow.arrivalName || '',
        arrivalAddress: this.dispatchRow.arrivalAddress || '',
        arrivalContact: this.dispatchRow.arrivalContact || '',
        arrivalPhone: this.dispatchRow.arrivalPhone || '',
      };
      this.dispatchItemIndex = index;
      this.dispatchItemForm = {
        ...baseRow,
        ...(index >= 0 ? row : {}),
      };
      this.dispatchItemForm.carrierType = this.dispatchItemForm.carrierType || '承运商';
      this.dispatchItemAttachmentRows = this.parseJsonArray(
        index >= 0 ? row.attachmentsJson : this.dispatchRow.attachmentsJson
      );
      this.dispatchItemBox = true;
    },
    handleDispatchCarrierTypeChange(value) {
      const nextValue = value || '';
      if (nextValue === '承运商') {
        this.dispatchItemForm.trailerVehicleNo = '';
        this.dispatchItemForm.escortName = '';
        this.dispatchItemForm.escortPhone = '';
      } else {
        this.dispatchItemForm.carrierId = '';
        this.dispatchItemForm.carrierName = '';
      }
      this.dispatchItemForm.carrierType = nextValue;
    },
    loadDispatchCarrierOptions() {
      if (this.taskCarrierLoading) return Promise.resolve([]);
      this.taskCarrierLoading = true;
      return getCarrierCustomerList(1, 9999, { customerType: '承运商' })
        .then(res => {
          this.taskCarrierOptions = extractRecords(res);
          return this.taskCarrierOptions;
        })
        .finally(() => {
          this.taskCarrierLoading = false;
        });
    },
    handleDispatchCarrierChange(value) {
      const carrier = this.taskCarrierOptions.find(item =>
        [item.customerName, item.carrierName, item.fullName, item.name].some(
          name => String(name || '') === String(value || '')
        )
      );
      this.dispatchItemForm.carrierId = carrier?.id || '';
      this.dispatchItemForm.carrierName = value || '';
    },
    loadDispatchDriverOptions() {
      if (this.taskDriverLoading) return Promise.resolve([]);
      this.taskDriverLoading = true;
      return getDriverList(1, 9999, {})
        .then(res => {
          this.taskDriverOptions = extractRecords(res);
          return this.taskDriverOptions;
        })
        .finally(() => {
          this.taskDriverLoading = false;
        });
    },
    handleDispatchDriverChange(value) {
      const driver = this.taskDriverOptions.find(item =>
        [item.driverName, item.name].some(name => String(name || '') === String(value || ''))
      );
      this.dispatchItemForm.driverId = driver?.id || '';
      this.dispatchItemForm.driverName = value || '';
      if (driver) {
        this.dispatchItemForm.driverPhone =
          driver.mobile ||
          driver.phone ||
          driver.driverPhone ||
          this.dispatchItemForm.driverPhone ||
          '';
      }
    },
    saveDispatchItem() {
      const nextRow = {
        ...this.dispatchItemForm,
        attachmentsJson: JSON.stringify(this.dispatchItemAttachmentRows),
      };
      const otherDispatchedQuantity = this.dispatchRows.reduce((total, row, index) => {
        if (index === this.dispatchItemIndex) return total;
        return total + this.parseDispatchQuantity(row.quantity);
      }, 0);
      const currentQuantity = this.parseDispatchQuantity(nextRow.quantity);
      const totalQuantity = this.dispatchSummaryTotalQuantity;
      if (totalQuantity > 0 && otherDispatchedQuantity + currentQuantity > totalQuantity) {
        this.$message.warning(
          `已调度数量合计不能超过总数量${this.formatDispatchQuantity(totalQuantity)}${
            this.dispatchSummaryUnit
          }`
        );
        return;
      }
      if (this.dispatchItemIndex >= 0) {
        this.dispatchRows.splice(this.dispatchItemIndex, 1, nextRow);
      } else {
        this.dispatchRows.push(nextRow);
      }
      this.dispatchItemBox = false;
      this.dispatchItemIndex = -1;
      this.dispatchItemForm = defaultDispatchRow();
      this.dispatchItemAttachmentRows = [];
    },
    resetDispatchItemDialog() {
      this.dispatchItemIndex = -1;
      this.dispatchItemForm = defaultDispatchRow();
      this.dispatchItemAttachmentRows = [];
    },
    removeDispatchRow(index) {
      this.$confirm('确定删除该调度明细?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      }).then(() => {
        this.dispatchRows.splice(index, 1);
      });
    },
    handleDispatchAddRow() {
      if (this.dispatchSummaryRemainingQuantity <= 0) {
        this.$message.warning('剩余数量为0，不能新增调度明细');
        return;
      }
      this.openDispatchItemDialog();
    },
    handleDispatchEditRow(row, index) {
      this.openDispatchItemDialog(index, row);
    },
    buildDispatchPayload(mode) {
      const rows = this.dispatchRows.map(
        ({ _key, cargoInfo, dispatchUserName, dispatchTime, ...rest }) => rest
      );
      return {
        id: this.dispatchRow.id,
        mode,
        waybills: rows.map(row => ({
          ...row,
          taskEntryMode: row.taskEntryMode || 'simple',
          taskRemark: row.remark || '',
          estimatedStartTime: this.formatDispatchDate(row.estimatedStartTime),
          estimatedEndTime: this.formatDispatchDate(row.estimatedEndTime),
          goodsJson: JSON.stringify([
            {
              cargoName: row.cargoName,
              cargoType: row.cargoType,
              quantity: row.quantity,
              quantityUnit: row.quantityUnit,
              specification: row.specification,
              model: row.model,
              packageType: row.packageType,
            },
          ]),
        })),
      };
    },
    formatDispatchDate(value) {
      if (!value) return '';
      const date = this.$dayjs(value);
      return date.isValid() ? date.format('YYYY-MM-DD') : value;
    },
    validateDispatchRows(mode) {
      if (!this.dispatchRows.length) {
        this.$message.warning('请先添加调度明细');
        return false;
      }
      if (mode === 'draft') return true;
      const invalidRow = this.dispatchRows.find(row => {
        const selfTransport = row.carrierType !== '承运商';
        return (
          !row.transportType ||
          !row.cargoName ||
          !row.cargoType ||
          !row.quantity ||
          !row.quantityUnit ||
          !row.carrierType ||
          !row.vehicleNo ||
          !row.departureAddress ||
          !row.arrivalAddress ||
          (row.carrierType === '承运商' && !row.carrierName) ||
          (selfTransport &&
            (!row.driverName ||
              !row.driverPhone ||
              !row.trailerVehicleNo ||
              !row.escortName ||
              !row.escortPhone ||
              !row.mileage))
        );
      });
      if (invalidRow) {
        this.$message.warning('请补全调度明细中的必填信息');
        return false;
      }
      return true;
    },
    handleDispatchSubmit(mode) {
      if (!this.dispatchRow?.id || typeof this.api.dispatch !== 'function') {
        this.$message.warning('运输计划调度接口不可用');
        return;
      }
      if (!this.validateDispatchRows(mode)) {
        return;
      }
      if (mode === 'submit') {
        this.$confirm(
          `请确认调度信息，将生成 <span style="color: #409eff; font-size: 20px; padding: 0 4px;">${this.dispatchRows.length}</span> 条运单，是否继续？`,
          '提示',
          {
            confirmButtonText: '确认',
            cancelButtonText: '取消',
            dangerouslyUseHTMLString: true,
            type: 'warning',
          }
        )
          .then(() => this.submitDispatch(mode))
          .catch(() => {});
        return;
      }
      this.submitDispatch(mode);
    },
    submitDispatch(mode) {
      this.dispatchSaving = mode;
      const shouldCompletePlan = mode === 'submit' && this.dispatchSummaryRemainingQuantity <= 0;
      this.api
        .dispatch(this.buildDispatchPayload(mode))
        .then(() => {
          if (shouldCompletePlan && typeof this.api.complete === 'function') {
            return this.api.complete(this.dispatchRow.id);
          }
          return undefined;
        })
        .then(() => {
          const message =
            mode === 'draft'
              ? '生成草稿成功'
              : shouldCompletePlan
              ? '确认生成成功，运输计划已完成'
              : '确认生成成功';
          this.$message.success(message);
          this.dispatchBox = false;
          this.onLoad(this.page, this.query);
        })
        .finally(() => {
          this.dispatchSaving = '';
        });
    },
    dispatchRowKey(row, index) {
      return row._key || `${index}-${row.vehicleNo || row.cargoInfo || row.dispatchTime || 'row'}`;
    },
    dispatchAttachmentLabel(item) {
      return (
        item?.originalName ||
        item?.name ||
        item?.fileName ||
        item?.attachmentName ||
        item?.title ||
        '附件'
      );
    },
    dispatchAttachmentUrl(item) {
      return item?.url || item?.fileUrl || item?.downloadUrl || '';
    },
    formatDispatchRouteContact(contact = '', phone = '') {
      return [contact, phone].filter(Boolean).join(' - ');
    },
    parseDispatchRoutePoint(row, type) {
      const prefix = type === 'start' ? 'departure' : 'arrival';
      const name = row[`${prefix}Name`] || row[`${prefix}Address`] || '';
      const address = row[`${prefix}Address`] || row[`${prefix}DetailAddress`] || '';
      const contact = this.formatDispatchRouteContact(
        row[`${prefix}Contact`],
        row[`${prefix}Phone`]
      );
      return { name, address, contact };
    },
    handleDispatchConfirm() {
      this.handleDispatchSubmit('submit');
    },
    handleChangeRecordFlow(row) {
      const processInstanceId = row.processInstanceId || row.processInstanceID || row.procInstId;
      if (!processInstanceId) {
        this.$message.warning('该变更记录暂无流程实例');
        return;
      }
      this.handleFlow(row, true);
    },
    handleBatchComplete() {
      if (!this.selectionList.length) {
        this.$message.warning('请选择至少一条数据');
        return;
      }
      this.$confirm('确定完成选中的运单?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      })
        .then(() => this.api.batchComplete(this.ids))
        .then(res => {
          const result = res.data.data || {};
          this.$message.success(
            `成功完成${result.successCount || 0}条，跳过${result.skippedCount || 0}条`
          );
          this.onLoad(this.page, this.query);
        });
    },
    handleRoadLoading() {
      if (!this.selectionList.length) {
        this.$message.warning('请选择至少一条数据');
        return;
      }
      if (typeof this.api.roadLoading !== 'function') {
        this.$message.warning('公路配载功能暂未配置');
        return;
      }
      this.$confirm('确定对选中的运单进行公路配载?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      })
        .then(() => this.api.roadLoading(this.ids))
        .then(() => {
          this.$message.success('公路配载成功');
          this.onLoad(this.page, this.query);
        });
    },
  },
};
</script>

<style lang="scss" scoped>
.business-crud-page {
  &__field {
    width: 100%;
  }

  &__contract-expiry-search {
    display: flex;
    order: -1;
    align-items: center;
    gap: 8px;
    margin-right: auto;
    min-width: 0;

    :deep(.el-select) {
      width: 160px;
    }
  }

  &__contract-expiry-label {
    flex: 0 0 auto;
    min-width: 140px;
    color: #606266;
    text-align: right;
    white-space: nowrap;
  }

  &__contract-expiry-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;

    :deep(.el-check-tag) {
      margin: 0;
      white-space: nowrap;
    }
  }

  .dialog-section-title {
    display: flex;
    align-items: center;
    width: 100%;
    color: #303133;
    font-size: 15px;
    font-weight: 600;

    &::before {
      display: block;
      width: 4px;
      height: 16px;
      margin-right: 8px;
      background: #409eff;
      content: '';
    }

    &--action {
      justify-content: space-between;
    }
  }

  &__shipping-title,
  &__goods-title {
    min-width: 100%;
  }

  &__goods-title &__section-actions,
  &__shipping-title .el-button {
    margin-left: auto;
  }

  &__section-actions {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 8px;
  }

  &__task {
    width: 100%;
  }

  &__task-toolbar {
    display: flex;
    justify-content: flex-end;
    margin-bottom: 16px;
  }

  &__task-form {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    column-gap: 24px;
    row-gap: 16px;
    width: 100%;

    :deep(.el-form-item) {
      min-width: 0;
      margin-bottom: 0;
    }

    :deep(.el-form-item__content) {
      min-width: 0;
    }

    :deep(.el-select),
    :deep(.el-cascader),
    :deep(.el-date-editor.el-input) {
      width: 100%;
    }

    :deep(.el-radio-group) {
      width: 100%;
    }

    :deep(.el-radio-button) {
      flex: 1;
    }

    :deep(.el-radio-button__inner) {
      width: 100%;
    }
  }

  &__task-form--full {
    display: block;
  }

  &__task-row {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    column-gap: 24px;
    row-gap: 16px;
    margin-bottom: 16px;
    width: 100%;

    &:last-child {
      margin-bottom: 0;
    }

    :deep(.el-form-item) {
      min-width: 0;
      margin-bottom: 0;
    }

    :deep(.el-form-item__content) {
      min-width: 0;
    }

    :deep(.el-select),
    :deep(.el-cascader),
    :deep(.el-date-editor.el-input) {
      width: 100%;
    }
  }

  &__task-span-4 {
    grid-column: 1 / -1;
  }

  &__task-span-1 {
    grid-column: span 1;
  }

  &__task-carrier-type {
    :deep(.el-form-item__content) {
      flex: 0 1 260px;
      max-width: 260px;
    }
  }

  &__task-remark {
    grid-column: span 3;
  }

  &__task-note {
    grid-column: 1 / -1;

    :deep(.el-input) {
      width: 100%;
    }
  }

  &__suffix-select {
    width: 72px;
    margin-right: -8px;

    :deep(.el-select__wrapper),
    :deep(.el-select__wrapper:hover),
    :deep(.el-select__wrapper.is-focused) {
      min-height: 24px;
      padding: 0 4px;
      border: 0;
      box-shadow: none !important;
    }
  }

  &__suffix-select--wide {
    width: 104px;
  }

  &__append-select {
    width: 92px;
    margin-right: -8px;

    :deep(.el-select__wrapper),
    :deep(.el-select__wrapper:hover),
    :deep(.el-select__wrapper.is-focused) {
      min-height: 24px;
      padding: 0 4px;
      border: 0;
      box-shadow: none !important;
    }
  }

  :deep(.business-crud-page__goods-info-cell .cell) {
    overflow: visible;
    line-height: 20px;
    white-space: normal;
    word-break: break-word;
    text-overflow: clip;
  }

  &__route-address-row {
    display: grid;
    grid-template-columns: 260px minmax(280px, 1fr) 40px 58px minmax(160px, 220px) 72px minmax(
        160px,
        220px
      );
    gap: 8px;
    align-items: center;
    width: 100%;

    :deep(.el-input),
    :deep(.el-select),
    :deep(.el-cascader) {
      width: 100%;
    }

    .el-button {
      width: 32px;
      min-width: 32px;
      padding: 0;
    }
  }

  &__dispatch-shipping-form {
    :deep(.el-form-item) {
      margin-bottom: 16px;
    }
  }

  &__dispatch-shipping-form &__route-address-row {
    grid-template-columns: 260px minmax(280px, 1fr) 58px minmax(160px, 220px) 72px minmax(
        160px,
        220px
      );
  }

  &__dispatch-item-attachments {
    display: flex;
    flex-wrap: wrap;
    gap: 12px 20px;
    min-height: 32px;
    padding: 4px 0 16px 12px;
    color: #909399;
    font-size: 14px;
  }

  &__dispatch-carrier-title {
    flex-basis: 100%;
    margin-top: 16px;
  }

  &__dispatch-task-title {
    margin-right: auto;
  }

  &__route-label {
    color: #606266;
    font-size: 14px;
    line-height: 32px;
    text-align: right;
    white-space: nowrap;
  }

  &__cargo-wrap {
    display: block;
    width: 100%;
    min-width: 0;
    overflow-x: auto;
  }

  &__cargo-table {
    width: 100%;
    min-width: 1870px;

    :deep(.el-table__header th) {
      background: #f5f7fa;
      color: #303133;
      font-weight: 600;
    }

    :deep(.el-table__cell) {
      border-color: #eff1f7;
    }

    :deep(.el-input),
    :deep(.el-select),
    :deep(.el-cascader) {
      width: 100%;
    }
  }

  &__cargo-actions {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    white-space: nowrap;

    .el-button {
      margin-left: 0;
    }
  }

  :deep(.business-crud-page__full-form-item) {
    width: 100%;
    max-width: 100%;
  }

  :deep(.business-crud-page__full-form-item .el-form-item) {
    width: 100%;
  }

  :deep(.business-crud-page__full-form-item .el-form-item__label) {
    display: none;
    width: 0 !important;
    padding: 0 !important;
  }

  :deep(.business-crud-page__full-form-item .el-form-item__content) {
    flex: 1 1 100%;
    width: 100%;
    max-width: 100%;
    margin-left: 0 !important;
  }

  :deep(.business-crud-page__full-form-item .el-form-item__content > div) {
    width: 100%;
    min-width: 0;
  }

  &__cargo-total {
    display: flex;
    justify-content: flex-end;
    padding-top: 12px;
    color: #303133;
    font-weight: 600;
  }

  &__dialog-search {
    padding: 12px 12px 4px;
    margin-bottom: 12px;
    border: 1px solid #eff1f7;
    border-radius: 4px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);

    :deep(.el-form-item) {
      margin-bottom: 8px;
    }

    :deep(.el-input),
    :deep(.el-select),
    :deep(.el-cascader) {
      width: 100%;
    }
  }

  &__dialog-search-actions {
    display: flex;
    align-items: flex-start;
    justify-content: flex-end;
  }

  &__freight-form {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    column-gap: 24px;
    row-gap: 16px;
    width: 100%;

    :deep(.el-form-item) {
      min-width: 0;
      margin-bottom: 0;
    }

    :deep(.el-form-item__content) {
      min-width: 0;
    }

    :deep(.el-input),
    :deep(.el-select) {
      width: 100%;
    }
  }

  &__dialog-pagination {
    display: flex;
    justify-content: flex-end;
    margin-top: 12px;
  }

  &__map-toolbar {
    display: flex;
    gap: 8px;
    margin-bottom: 12px;

    .el-input {
      flex: 1;
      min-width: 0;
    }
  }

  &__map {
    width: 100%;
    height: 420px;
    border: 1px solid #eff1f7;
  }

  &__map-info {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-top: 10px;
    color: #606266;
    line-height: 1.6;
  }

  &__date-range {
    display: flex;
    align-items: center;
    width: 100%;
    min-width: 0;
  }

  &__date {
    flex: 1;
    min-width: 0;
  }

  &__date-separator {
    flex: 0 0 auto;
    padding: 0 8px;
    color: #303133;
  }

  &__inline-number {
    display: flex;
    align-items: center;
    width: 100%;

    .el-input {
      flex: 1;
      min-width: 0;
    }

    span {
      flex: 0 0 auto;
      margin-left: 8px;
      color: #303133;
    }
  }

  &__billing {
    width: 100%;
  }

  &__billing-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 16px;
  }

  &__billing-switch {
    display: flex;
    align-items: center;
    gap: 12px;
    color: #303133;
  }

  &__billing-table {
    width: 100%;

    :deep(.el-table__header th) {
      background: #f5f7fa;
      color: #303133;
      font-weight: 600;
    }

    :deep(.el-table__cell) {
      border-color: #eff1f7;
    }
  }

  &__settlement-rule {
    width: 100%;
  }

  &__settlement-switch {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 18px;
    color: #303133;
  }

  &__module-form {
    width: 100%;

    :deep(.el-form-item) {
      margin-bottom: 16px;
    }

    :deep(.el-select),
    :deep(.el-date-editor.el-input) {
      width: 100%;
    }
  }

  &__module-form--three {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    column-gap: 72px;
  }

  &__module-form--two {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: 96px;
  }

  &__billing-dialog {
    :deep(.el-dialog__body) {
      padding-top: 28px;
    }

    :deep(.el-dialog__body),
    :deep(.el-form) {
      width: 100%;
    }
  }

  &__dispatch-dialog {
    :deep(.el-dialog__header) {
      display: none;
    }

    :deep(.el-dialog__body) {
      padding: 16px;
      background: #f5f6fa;
      max-height: calc(100vh - 140px);
      overflow-y: auto;
    }
  }

  &__dispatch-shell {
    display: flex;
    flex-direction: column;
    min-height: calc(100vh - 140px);
  }

  &__dispatch-top {
    padding: 0;
  }

  &__dispatch-heading {
    margin-bottom: 14px;
  }

  &__dispatch-heading-title {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
  }

  &__dispatch-heading-name {
    font-size: 18px;
    font-weight: 600;
    color: #303133;
  }

  &__dispatch-heading-no {
    font-size: 14px;
    color: #606266;
  }

  &__dispatch-summary {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
    gap: 16px;
    align-items: stretch;
    margin-bottom: 12px;
  }

  &__dispatch-summary-card,
  &__dispatch-route-card {
    display: flex;
    flex-direction: column;

    :deep(.section-card__body) {
      flex: 1;
    }
  }

  &__dispatch-summary-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px 16px;
  }

  &__dispatch-summary-item {
    min-width: 0;

    &.is-wide {
      grid-column: span 2;
    }
  }

  &__dispatch-summary-label {
    margin-bottom: 4px;
    font-size: 12px;
    color: #909399;
    line-height: 1.4;
  }

  &__dispatch-summary-value {
    min-height: 20px;
    font-size: 14px;
    line-height: 1.45;
    color: #303133;
    word-break: break-word;
  }

  &__dispatch-summary-item--attachments {
    grid-column: 1 / -1;
  }

  &__dispatch-attachment-link {
    display: inline-block;
    margin-right: 16px;
    margin-bottom: 4px;
    font-size: 14px;
  }

  &__dispatch-route {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1fr) 56px minmax(0, 1fr);
    gap: 40px;
    align-items: center;
    //height: 100%;
    margin: 20px 0;
  }

  &__dispatch-route-item {
    display: flex;
    align-items: center;
    gap: 12px;
    max-width: 90%;
    padding: 16px;
    min-height: 80px;
    border: 1px dashed #e3e4ec;
    border-radius: 12px;
  }

  &__dispatch-route-badge {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    color: #fff;
    font-size: 16px;
    font-weight: 600;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);

    &.is-start {
      background: #2a92ff;
    }

    &.is-end {
      background: #ffb11a;
    }
  }

  &__dispatch-route-body {
    min-width: 0;
    text-align: left;
  }

  &__dispatch-route-title {
    font-size: 18px;
    line-height: 1.35;
    color: #303133;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  &__dispatch-route-address {
    margin-top: 4px;
    font-size: 13px;
    line-height: 1.5;
    color: #606266;
  }

  &__dispatch-route-contact {
    margin-top: 2px;
    font-size: 13px;
    color: #909399;
  }

  &__dispatch-route-vehicle {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    margin: 0 auto;
    border-radius: 50%;
    background: #fff;
    border: 2px solid var(--el-color-primary);
    color: var(--el-color-primary);
    font-size: 22px;
    box-shadow: 0 2px 8px rgba(64, 158, 255, 0.18);

    &::before,
    &::after {
      content: '';
      position: absolute;
      top: 50%;
      width: 20px;
      height: 0;
      border-top: 2px dashed var(--el-color-primary-light-5);
      transform: translateY(-50%);
    }

    &::before {
      right: 100%;
      margin-right: 6px;
    }

    &::after {
      left: 100%;
      margin-left: 6px;
    }
  }

  &__dispatch-list-head {
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
    padding: 0 0 12px;
  }

  &__dispatch-list-title {
    font-size: 18px;
    font-weight: 600;
    color: #303133;
  }

  &__dispatch-list-summary {
    margin-left: 8px;
    font-size: 14px;
    color: #3c3c3c;
  }

  &__dispatch-table {
    font-size: 13px;

    :deep(.el-table__header .cell) {
      font-size: 13px;
      font-weight: 600;
    }

    :deep(.cell) {
      white-space: normal;
      line-height: 1.35;
    }
  }

  &__dispatch-actions {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    white-space: nowrap;

    .el-button {
      margin-left: 0;
    }
  }

  &__dispatch-footer {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    padding-right: 0;
    padding-top: 16px;
    margin-top: 16px;
    border-top: 1px solid #e9edf5;
  }

  &__dispatch-item-dialog {
    :deep(.el-dialog__body) {
      padding-top: 20px;
    }
  }

  &__dispatch-item-form {
    width: 100%;
  }

  &__dispatch-unit-select {
    width: 104px;

    :deep(.el-select__wrapper),
    :deep(.el-select__wrapper:hover),
    :deep(.el-select__wrapper.is-focused) {
      width: 104px;
      min-height: 24px;
      padding: 0 8px;
      border: 0;
      box-shadow: none !important;
    }
  }

  &__dispatch-item-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 16px 24px;

    :deep(.el-form-item) {
      margin-bottom: 0;
      min-width: 0;
    }

    &--compact {
      grid-template-columns: repeat(4, minmax(0, 1fr));
    }
  }

  &__dispatch-item-span-2 {
    grid-column: span 2;
  }

  &__dispatch-item-span-3 {
    grid-column: span 3;
  }

  &__dispatch-item-carrier-type {
    grid-column: 1 / -1;
    width: calc((100% - 72px) / 4);
  }

  &__billing-form-row {
    display: grid;
    grid-template-columns: minmax(360px, 520px) 1fr;
    column-gap: 48px;
    align-items: center;
  }

  &__billing-default {
    margin-bottom: 18px;
  }

  &__billing-plan-remark {
    width: 80%;
  }

  &__billing-rule-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin: 20px 0 12px;
  }

  &__required-column {
    &::before {
      margin-right: 4px;
      color: #f56c6c;
      content: '*';
    }
  }

  &__billing-tip {
    display: flex;
    max-width: 620px;
    color: #ff6f86;
    line-height: 1.7;

    span {
      position: relative;
      display: inline-flex;
      flex: 0 0 auto;
      align-items: center;
      justify-content: center;
      width: 28px;
      height: 28px;
      margin-right: 12px;
      border-radius: 50%;
      background: #ffd6ea;
      color: #ff4f92;
      font-weight: 600;

      &::after {
        position: absolute;
        right: 6px;
        bottom: -8px;
        width: 0;
        height: 0;
        border: 8px solid transparent;
        border-top-color: #ffd6ea;
        content: '';
      }
    }
  }

  &__billing-rule-wrap {
    width: 100%;
    overflow-x: auto;
  }

  &__billing-rule-table {
    width: 100%;
    min-width: 2230px;

    :deep(.el-table__header th) {
      background: #f5f7fa;
      color: #303133;
      font-weight: 600;
    }

    :deep(.el-table__cell) {
      border-color: #eff1f7;
      vertical-align: top;
    }
  }

  &__limit-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
    width: 100%;
  }

  &__limit-row {
    display: flex;
    align-items: center;
    gap: 8px;

    .el-input {
      flex: 1;
      min-width: 0;
    }

    .el-button {
      flex: 0 0 auto;
      padding: 0;
    }
  }

  &__dialog-footer {
    display: flex;
    justify-content: center;
    gap: 16px;
  }

  &__match-form {
    padding: 12px 32px 4px;

    .el-select,
    .el-cascader {
      width: 100%;
    }
  }

  &__attachment {
    width: 100%;
  }

  &__attachment-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 16px;
  }

  &__attachment-table {
    width: 100%;
    min-width: 1000px;

    :deep(.el-table__header th) {
      background: #f5f7fa;
      color: #303133;
      font-weight: 600;
    }

    :deep(.el-table__cell) {
      border-color: #eff1f7;
    }
  }

  &__change-table {
    width: 100%;

    :deep(.el-table__header th) {
      background: #f5f7fa;
      color: #303133;
      font-weight: 600;
    }

    :deep(.el-table__cell) {
      border-color: #eff1f7;
    }
  }

  &__detail-content {
    display: flex;
    flex-direction: column;
    gap: 12px;

    .section-card {
      :deep(.el-descriptions__label) {
        width: 140px;
        color: #606266;
        font-weight: 600;
        text-align: right;
      }

      :deep(.el-descriptions__content) {
        min-height: 42px;
        white-space: pre-wrap;
        word-break: break-word;
      }

      :deep(.el-table) {
        border-color: #eff1f7;
      }
    }
  }

  &__transport-plan-import-form {
    padding: 12px 24px 4px;

    .el-select,
    .el-input {
      width: 100%;
    }
  }

  &__import-file-item {
    margin-top: 28px;

    :deep(.el-form-item__content) {
      gap: 16px;
    }
  }

  &__import-file-tip {
    color: #606266;
  }

  &__import-preview {
    margin: 8px 24px 0;
    padding-top: 16px;
    border-top: 1px solid #eff1f7;
  }

  &__import-preview-head {
    display: flex;
    align-items: center;
    gap: 24px;
    margin-bottom: 12px;

    .dialog-section-title {
      margin-bottom: 0;
    }
  }

  &__import-preview-tabs {
    margin-bottom: 12px;
  }

  &__import-preview-table {
    overflow-x: auto;

    :deep(.el-table) {
      min-width: 1550px;
    }

    :deep(.el-table__cell) {
      border-color: #eff1f7;
    }

    :deep(.el-table__row:nth-child(even) td) {
      background: #fafafa;
    }
  }

  @media (max-width: 1280px) {
    &__route-address-row {
      grid-template-columns: 240px minmax(240px, 1fr) 40px 58px minmax(140px, 1fr);
    }

    &__route-address-row &__route-label:nth-of-type(2),
    &__route-address-row .el-input:last-child {
      grid-column: auto;
    }
  }

  @media (max-width: 960px) {
    &__route-address-row {
      grid-template-columns: 1fr;
    }

    &__route-label {
      text-align: left;
    }

    &__dialog-search-actions {
      justify-content: flex-start;
    }
  }
}
.section-card{
  margin-bottom: 0 !important;
}

/* 编辑弹窗：含分组的 Avue form 渲染为白卡风格 */
:deep(.avue-form__group:has(.dialog-section-title)) {
  background: #fff;
  border-radius: 6px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
  padding: 16px;
  margin-bottom: 16px;

  .avue-form__group--title {
    padding: 0 !important;
    margin-bottom: 12px;
  }

  .avue-form__row {
    padding: 0 !important;
  }
}
</style>
