<template>
  <basic-container class="project-apply-page">
    <avue-crud
      :option="tableOption"
      :table-loading="loading"
      :data="data"
      v-model:page="page"
      v-model="tableForm"
      ref="crud"
      :permission="permissionList"
      @search-change="searchChange"
      @search-reset="searchReset"
      @selection-change="selectionChange"
      @current-change="currentChange"
      @size-change="sizeChange"
      @refresh-change="refreshChange"
      @on-load="onLoad"
    >
      <template #menu-left>
        <el-button
          type="primary"
          icon="el-icon-plus"
          v-if="canMajorProjectSupplement"
          @click="openProjectDialog('majorSupplement')"
        >
          重大项目补录
        </el-button>
        <el-button
          type="primary"
          icon="el-icon-plus"
          v-if="canCreate"
          @click="openProjectDialog('add')"
        >
          新增
        </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="danger"
          icon="el-icon-delete"
          plain
          v-if="hasPermission(`${config.permission}_delete`) && !readonlyScope"
          @click="handleDelete"
        >
          批量删除
        </el-button>
      </template>

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

      <template #menu="{ row, index }">
        <el-link
          type="primary"
          v-if="hasPermission(`${config.permission}_view`)"
          @click="openProjectDialog('view', row)"
        >
          查看
        </el-link>
        <el-link type="primary" v-if="canEdit(row)" @click="openProjectDialog('edit', 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>
    </avue-crud>

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

    <el-dialog
      v-model="projectBox"
      :title="projectDialogTitle"
      append-to-body
      destroy-on-close
      width="1440px"
      top="4vh"
      class="project-apply-dialog"
      @closed="resetProjectDialog"
    >
      <el-form
        ref="projectForm"
        :model="form"
        :rules="formRules"
        label-position="right"
        label-width="auto"
        class="project-apply-form"
        :disabled="dialogReadonly"
      >
        <div class="dialog-section-title">项目基本信息</div>
        <div class="project-apply-form__grid">
          <el-form-item v-if="isChangeDialog" label="变更类型" prop="changeType">
            <el-select v-model="form.changeType" placeholder="请选择变更类型">
              <el-option
                v-for="item in changeTypeOptions"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </el-select>
          </el-form-item>
          <div v-if="isChangeDialog"></div>
          <el-form-item label="项目类型" prop="projectType">
            <el-select
              v-model="form.projectType"
              placeholder="请选择项目类型"
              clearable
              :disabled="isBasicInfoReadonly || isMajorProjectSupplementDialog"
              @change="handleProjectTypeChange"
            >
              <el-option
                v-for="item in projectTypeOptions"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </el-select>
          </el-form-item>
          <el-form-item label="项目编号" prop="projectCode">
            <el-input v-model="form.projectCode" disabled placeholder="系统自动生成" />
          </el-form-item>
          <el-form-item label="项目名称" prop="projectName">
            <el-input
              v-model="form.projectName"
              placeholder="请填写项目名称"
              maxlength="50"
              :disabled="isBasicInfoReadonly"
            />
          </el-form-item>
          <el-form-item label="项目简称" prop="projectShortName">
            <el-input
              v-model="form.projectShortName"
              placeholder="请填写项目简称"
              maxlength="20"
              :disabled="isBasicInfoReadonly"
            />
          </el-form-item>
          <el-form-item label="业务部门" prop="businessDeptId">
            <el-select
              v-model="form.businessDeptId"
              placeholder="请选择业务部门"
              filterable
              clearable
              :disabled="isBasicInfoReadonly"
              @change="handleBusinessDeptChange"
            >
              <el-option
                v-for="item in deptOptions"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </el-select>
          </el-form-item>
          <el-form-item label="承办部门" prop="undertakeDeptId">
            <el-cascader
              v-model="form.undertakeDeptId"
              placeholder="请选择承办部门"
              clearable
              filterable
              :options="deptTreeOptions"
              :props="deptCascaderProps"
              :disabled="isBasicInfoReadonly"
              @change="handleUndertakeDeptChange"
            />
          </el-form-item>
          <el-form-item label="项目由来" prop="projectSource">
            <el-select
              v-model="form.projectSource"
              placeholder="请选择项目由来"
              clearable
              :disabled="isBasicInfoReadonly"
            >
              <el-option
                v-for="item in projectSourceOptions"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </el-select>
          </el-form-item>
          <el-form-item label="项目由来说明" prop="sourceRemark">
            <el-input v-model="form.sourceRemark" maxlength="300" :disabled="isBasicInfoReadonly" />
          </el-form-item>
        </div>

        <div class="dialog-section-title">项目主要内容</div>
        <div class="project-apply-form__grid">
          <el-form-item label="客户名称" prop="customerNames">
            <el-select
              v-model="selectedCustomerId"
              placeholder="支持多选，选择后下方列表展示"
              multiple
              filterable
              clearable
              collapse-tags
              :loading="customerLoading"
              @visible-change="visible => visible && loadCustomerOptions('客户')"
              @change="handleCustomerChange"
            >
              <el-option
                v-for="item in customerOptions"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </el-select>
          </el-form-item>
          <el-form-item label="下游承运商" prop="carrierNames">
            <el-select
              v-model="selectedCarrierIds"
              placeholder="支持多选，选择后下方列表展示"
              multiple
              filterable
              clearable
              collapse-tags
              :loading="carrierLoading"
              @visible-change="visible => visible && loadCustomerOptions('承运商')"
              @change="handleCarrierChange"
            >
              <el-option
                v-for="item in carrierOptions"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </el-select>
          </el-form-item>
          <el-form-item label="项目资金使用额度（万元）" prop="fundLimit">
            <el-input
              v-model="form.fundLimit"
              placeholder="实际业务回款周期内付款额度"
              @input="value => handleNumberInput('fundLimit', value)"
              @blur="formatAmountField('fundLimit')"
            />
          </el-form-item>
          <el-form-item label="项目应收账款额度（万元）" prop="receivableLimit">
            <el-input
              v-model="form.receivableLimit"
              placeholder="回款期内最大应收账款额度"
              @input="value => handleNumberInput('receivableLimit', value)"
              @blur="formatAmountField('receivableLimit')"
            />
          </el-form-item>
          <el-form-item label="应收账款回款期限" prop="receivableDays">
            <el-input
              v-model="form.receivableDays"
              @input="value => handleIntegerInput('receivableDays', value)"
            >
              <template #suffix>天</template>
            </el-input>
          </el-form-item>
          <el-form-item label="回款账期" prop="paymentDays">
            <el-input
              v-model="form.paymentDays"
              @input="value => handleIntegerInput('paymentDays', value)"
            >
              <template #suffix>天</template>
            </el-input>
          </el-form-item>
          <el-form-item label="货物类型" prop="cargoType">
            <el-select v-model="form.cargoType" placeholder="请选择货物类型" filterable clearable>
              <el-option
                v-for="item in cargoTypeOptions"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </el-select>
          </el-form-item>
          <el-form-item label="预估货物数量" prop="cargoQuantity">
            <el-input
              v-model="form.cargoQuantity"
              placeholder="请输入数字"
              @input="value => handleIntegerInput('cargoQuantity', value)"
            />
          </el-form-item>
          <el-form-item label="业务周期" prop="businessDateRange">
            <el-date-picker
              v-model="form.businessDateRange"
              type="daterange"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              format="YYYY-MM-DD"
              value-format="YYYY-MM-DD"
              unlink-panels
            />
          </el-form-item>
          <el-form-item label="运输线路" prop="transportRoute">
            <el-input v-model="form.transportRoute" maxlength="100" />
          </el-form-item>
          <el-form-item label="运输类型" prop="transportType">
            <el-select v-model="form.transportType" placeholder="请选择运输类型" clearable>
              <el-option
                v-for="item in transportTypeOptions"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </el-select>
          </el-form-item>
          <el-form-item label="业务类型" prop="businessType">
            <el-select v-model="form.businessType" placeholder="请选择业务类型" clearable>
              <el-option
                v-for="item in businessTypeOptions"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </el-select>
          </el-form-item>
          <el-form-item label="项目规模（万元）" prop="projectScale">
            <el-input
              v-model="form.projectScale"
              @input="value => handleNumberInput('projectScale', value)"
            />
          </el-form-item>
          <el-form-item label="结算方式" prop="settlementMode">
            <el-select v-model="form.settlementMode" placeholder="请选择结算方式" clearable>
              <el-option
                v-for="item in settlementModeOptions"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </el-select>
          </el-form-item>
          <el-form-item label="预估利润（万元）" prop="estimatedProfit">
            <el-input
              v-model="form.estimatedProfit"
              @input="value => handleNumberInput('estimatedProfit', value)"
            />
          </el-form-item>
          <el-form-item label="资金需求（万元）" prop="fundDemand">
            <el-input
              v-model="form.fundDemand"
              @input="value => handleNumberInput('fundDemand', value)"
            />
          </el-form-item>
          <el-form-item label="项目经办人" prop="handlerUserId">
            <el-input
              v-model="form.handlerUserName"
              readonly
              placeholder="弹窗搜索选择"
              @click="openUserPicker('handler')"
            >
              <template #suffix>
                <el-icon class="project-apply-form__picker-icon"><Search /></el-icon>
              </template>
            </el-input>
          </el-form-item>
          <el-form-item label="项目负责人" prop="principalUserId">
            <el-input
              v-model="form.principalUserName"
              readonly
              placeholder="弹窗搜索选择"
              @click="openUserPicker('principal')"
            >
              <template #suffix>
                <el-icon class="project-apply-form__picker-icon"><Search /></el-icon>
              </template>
            </el-input>
          </el-form-item>
        </div>

        <div class="dialog-section-title project-apply-form__table-title">客户信息</div>
        <el-table :data="customerRows" border class="project-apply-form__table">
          <el-table-column type="index" label="序号" width="80" align="center" />
          <el-table-column prop="customer" label="客户" min-width="160" align="center" />
          <el-table-column prop="credit" label="客户信用额度" min-width="150" align="center" />
          <el-table-column prop="companyNature" label="企业性质" min-width="140" align="center" />
          <el-table-column prop="legalPerson" label="法定代表人" min-width="140" align="center" />
          <el-table-column
            prop="address"
            label="联系地址"
            min-width="180"
            align="center"
            show-overflow-tooltip
          />
          <el-table-column prop="contact" label="联系人" min-width="120" align="center" />
          <el-table-column prop="phone" label="联系方式" min-width="140" align="center" />
        </el-table>

        <div class="dialog-section-title">承运商信息</div>
        <el-table :data="carrierRows" border class="project-apply-form__table">
          <el-table-column type="index" label="序号" width="80" align="center" />
          <el-table-column prop="carrier" label="承运商" min-width="180" align="center" />
          <el-table-column prop="companyNature" label="企业性质" min-width="140" align="center" />
          <el-table-column prop="legalPerson" label="法定代表人" min-width="140" align="center" />
          <el-table-column
            prop="address"
            label="联系地址"
            min-width="180"
            align="center"
            show-overflow-tooltip
          />
          <el-table-column prop="contact" label="联系人" min-width="120" align="center" />
          <el-table-column prop="phone" label="联系方式" min-width="140" align="center" />
        </el-table>

        <div class="dialog-section-title">项目情况说明</div>
        <div class="project-apply-form__textarea-list">
          <el-form-item label="项目简介" prop="projectIntro">
            <el-input
              v-model="form.projectIntro"
              type="textarea"
              :rows="4"
              placeholder="请填写备注信息"
              maxlength="200"
              show-word-limit
            />
          </el-form-item>
          <el-form-item label="盈利模式及利润测算情况说明" prop="profitRemark">
            <el-input
              v-model="form.profitRemark"
              type="textarea"
              :rows="4"
              placeholder="请填写备注信息"
              maxlength="200"
              show-word-limit
            />
          </el-form-item>
          <el-form-item label="项目风险点" prop="riskPoint">
            <el-input
              v-model="form.riskPoint"
              type="textarea"
              :rows="4"
              placeholder="请填写备注信息"
              maxlength="200"
              show-word-limit
            />
          </el-form-item>
          <el-form-item label="风险防范措施及应急预案" prop="emergencyPlan">
            <el-input
              v-model="form.emergencyPlan"
              type="textarea"
              :rows="4"
              placeholder="请填写备注信息"
              maxlength="200"
              show-word-limit
            />
          </el-form-item>
        </div>

        <div class="project-apply-form__material-card">
          <div class="project-apply-form__material-head">
            <div class="dialog-section-title project-apply-form__material-title">项目材料</div>
            <el-button
              type="primary"
              :disabled="!attachmentRows.length"
              @click="handleBatchDownload"
              >批量下载</el-button
            >
          </div>
          <el-alert
            v-if="dialogReadonly && missingRequiredAttachmentTypes.length"
            class="project-apply-form__material-warning"
            type="warning"
            :closable="false"
            show-icon
            :title="`缺少必需材料：${missingRequiredAttachmentTypes.join('、')}`"
          />
          <el-table :data="attachmentRows" border class="project-apply-form__material-table">
            <el-table-column type="index" label="序号" width="80" align="center" />
            <el-table-column label="附件类型" min-width="160" align="center">
              <template #default="{ row }">
                <el-select v-model="row.fileType" placeholder="请选择" :disabled="dialogReadonly">
                  <el-option
                    v-for="item in projectAttachmentTypeOptions"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  />
                </el-select>
              </template>
            </el-table-column>
            <el-table-column label="文件名" min-width="220" align="center" show-overflow-tooltip>
              <template #default="{ row }">{{ row.originalName || row.name }}</template>
            </el-table-column>
            <el-table-column label="附件描述" min-width="240" align="center">
              <template #default="{ row }">
                <el-input
                  v-model="row.description"
                  placeholder="请输入附件描述"
                  :disabled="dialogReadonly"
                />
              </template>
            </el-table-column>
            <el-table-column label="文件大小" min-width="120" align="center">
              <template #default="{ row }">{{ formatFileSize(row.size) }}</template>
            </el-table-column>
            <el-table-column prop="uploadUserName" label="上传人" min-width="140" align="center" />
            <el-table-column prop="uploadTime" label="上传时间" min-width="170" align="center" />
            <el-table-column label="操作" width="110" align="center">
              <template #default="{ row, $index }">
                <el-link type="primary" @click="downloadAttachment(row)">下载</el-link>
                <el-link v-if="!dialogReadonly" type="primary" @click="removeAttachment($index)"
                  >删除</el-link
                >
              </template>
            </el-table-column>
          </el-table>
          <vehicle-attachment-upload
            v-model="attachmentRows"
            class="project-apply-form__upload"
            :readonly="dialogReadonly"
            :file-types="attachmentFileTypes"
            :max-size="50"
            button-text="上传附件"
            tip="支持pdf、bmp、jpeg、png、jpg、doc、docx、ppt、pptx、xlsx、xls、eml、msg、zip的文件格式，单个文件大小限制50M"
            @change="handleAttachmentChange"
          />
        </div>

        <div v-if="!isChangeDialog" class="dialog-section-title project-apply-form__table-title">
          变更记录
        </div>
        <el-table
          v-if="!isChangeDialog"
          :data="changeRows"
          border
          class="project-apply-form__table"
        >
          <el-table-column type="index" label="序号" width="80" align="center" />
          <el-table-column prop="changeDate" label="变更日期" min-width="140" align="center" />
          <el-table-column prop="changeType" label="变更类型" min-width="160" align="center" />
          <el-table-column prop="handler" label="经办人" min-width="120" align="center" />
          <el-table-column
            prop="content"
            label="变更内容"
            min-width="180"
            align="center"
            show-overflow-tooltip
          />
          <el-table-column
            prop="reason"
            label="变更原因"
            min-width="180"
            align="center"
            show-overflow-tooltip
          />
          <el-table-column prop="status" label="状态" min-width="120" align="center" />
          <el-table-column label="操作" width="100" align="center">
            <template #default="{ row }">
              <el-link type="primary" @click="handleChangeRecordAction(row)">
                {{ row.action || '查看' }}
              </el-link>
            </template>
          </el-table-column>
        </el-table>

        <template v-if="isChangeDialog">
          <div class="dialog-section-title">变更内容</div>
          <el-form-item prop="changeContent" class="project-apply-form__change-textarea">
            <el-input
              v-model="form.changeContent"
              type="textarea"
              :rows="5"
              placeholder="2000字以内"
              maxlength="2000"
              show-word-limit
            />
          </el-form-item>
          <div class="dialog-section-title">变更原因</div>
          <el-form-item prop="changeReason" class="project-apply-form__change-textarea">
            <el-input
              v-model="form.changeReason"
              type="textarea"
              :rows="5"
              placeholder="2000字以内"
              maxlength="2000"
              show-word-limit
            />
          </el-form-item>
        </template>
      </el-form>

      <template #footer>
        <div
          class="project-apply-dialog__footer"
          :class="{ 'project-apply-dialog__footer--change': isChangeDialog }"
        >
          <template v-if="isChangeDialog">
            <el-button type="primary" :loading="submitLoading" @click="saveChangeProject">
              保存
            </el-button>
            <el-button type="primary" :loading="submitLoading" @click="submitChangeProject">
              提交
            </el-button>
            <el-button @click="handleCancelProject">取消</el-button>
          </template>
          <template v-else>
            <el-button @click="handleCancelProject">取消</el-button>
            <el-button
              v-if="!dialogReadonly"
              type="primary"
              :loading="submitLoading"
              @click="saveProject"
            >
              保存
            </el-button>
            <el-button
              v-if="!dialogReadonly"
              type="primary"
              :loading="submitLoading"
              @click="submitProject"
            >
              提交
            </el-button>
          </template>
        </div>
      </template>
    </el-dialog>

    <el-dialog
      v-model="userBox"
      title="选择用户"
      append-to-body
      destroy-on-close
      width="900px"
      class="project-apply-user-dialog"
    >
      <div class="project-apply-user-dialog__search">
        <el-input
          v-model="userQuery.realName"
          placeholder="用户姓名"
          clearable
          @keyup.enter="loadUsers"
        />
        <el-input
          v-model="userQuery.account"
          placeholder="登录账号"
          clearable
          @keyup.enter="loadUsers"
        />
        <el-button type="primary" @click="loadUsers">查询</el-button>
        <el-button @click="resetUserQuery">重置</el-button>
      </div>
      <el-table :data="userData" border v-loading="userLoading" @row-dblclick="selectUser">
        <el-table-column type="index" label="序号" width="70" align="center" />
        <el-table-column prop="account" label="登录账号" min-width="140" align="center" />
        <el-table-column prop="realName" label="用户姓名" min-width="140" align="center" />
        <el-table-column prop="deptName" label="所属部门" min-width="180" align="center" />
        <el-table-column label="操作" width="100" align="center">
          <template #default="{ row }">
            <el-link type="primary" @click="selectUser(row)">选择</el-link>
          </template>
        </el-table-column>
      </el-table>
      <div class="project-apply-user-dialog__pager">
        <el-pagination
          v-model:current-page="userPage.currentPage"
          v-model:page-size="userPage.pageSize"
          :page-sizes="[10, 20, 50]"
          :total="userPage.total"
          layout="total, sizes, prev, pager, next"
          @current-change="loadUsers"
          @size-change="loadUsers"
        />
      </div>
    </el-dialog>
  </basic-container>
</template>

<script>
import { exportBlob } from '@/api/common';
import * as api from '@/api/business/project-apply';
import {
  getList as getCustomerArchiveList,
  getDetail as getCustomerArchiveDetail,
} from '@/api/vehicle/customer-archive';
import { getDeptTree } from '@/api/system/dept';
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
import { getDictionary as getBizDictionary } from '@/api/system/dictbiz';
import { getList as getUserList } from '@/api/system/user';
import { getToken } from '@/utils/auth';
import { downloadFileByUrl, downloadXls } from '@/utils/util';
import { mapGetters } from 'vuex';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
import {
  businessTypeOptions,
  projectSourceOptions,
  projectTypeOptions,
  settlementModeOptions,
} from '@/option/business/common';
import { config, option } from '@/option/business/project-apply';

const emptyForm = () => ({
  projectType: '',
  projectCode: '',
  projectName: '',
  projectShortName: '',
  businessDeptId: '',
  businessDeptName: '',
  undertakeDeptId: '',
  undertakeDeptName: '',
  projectSource: '商务洽谈',
  sourceRemark: '',
  changeType: '',
  customerNames: '',
  carrierNames: '',
  fundLimit: '',
  receivableLimit: '',
  receivableDays: '',
  paymentDays: '',
  cargoType: '',
  cargoQuantity: '',
  businessDateRange: [],
  businessStartDate: '',
  businessEndDate: '',
  transportRoute: '',
  transportType: '',
  businessType: '',
  projectScale: '',
  settlementMode: '',
  estimatedProfit: '',
  fundDemand: '',
  handlerUserId: '',
  handlerUserName: '',
  principalUserId: '',
  principalUserName: '',
  customerJson: '',
  carrierJson: '',
  attachmentsJson: '',
  situationRemark: '',
  projectIntro: '',
  profitRemark: '',
  riskPoint: '',
  emergencyPlan: '',
  changeContent: '',
  changeReason: '',
});

const changeTypeOptions = [
  { label: '项目变更', value: '项目变更' },
  { label: '项目备案调整', value: '项目备案调整' },
];

const normalProjectAttachmentTypeOptions = [
  '项目立项报告',
  '利润测算表',
  '单一来源供应商申请表/三方比价表',
  '合同模板',
  '项目操作说明',
  '项目分析报告',
  '项目考察报告',
].map(item => ({ label: item, value: item }));

const majorProjectAttachmentTypeOptions = [
  '项目立项报告',
  '会议纪要',
  '评审表决票',
  '利润测算表',
  '单一来源供应商申请表/三方比价表',
  '项目操作说明',
  '项目分析报告',
  '项目考察报告',
  '合同模板',
  '上游尽职调查报告',
  '下游尽职调查报告',
  '业务承诺函',
].map(item => ({ label: item, value: item }));

export default {
  data() {
    const validateAmount = (rule, value, callback) => {
      if (value === '' || value === undefined || value === null) {
        callback();
        return;
      }
      if (Number(value) < 0) {
        callback(new Error(`${rule.label || '金额'}不能小于0`));
        return;
      }
      callback();
    };
    const validateRequiredAmount = (rule, value, callback) => {
      if (value === '' || value === undefined || value === null) {
        callback(new Error(`请输入${rule.label}`));
        return;
      }
      validateAmount(rule, value, callback);
    };
    const validateDays = (rule, value, callback) => {
      if (value === '' || value === undefined || value === null) {
        callback(new Error(`请输入${rule.label}`));
        return;
      }
      const number = Number(value);
      if (!Number.isInteger(number) || number < 0 || number > 999) {
        callback(new Error(`${rule.label}范围为0到999天`));
        return;
      }
      callback();
    };
    const validateUser = (rule, value, callback) => {
      if (value || this.form[rule.nameProp]) {
        callback();
        return;
      }
      callback(new Error(`请选择${rule.label}`));
    };
    return {
      api,
      config,
      projectTypeOptions,
      projectSourceOptions,
      transportTypeOptions: [],
      businessTypeOptions,
      settlementModeOptions,
      changeTypeOptions,
      tableForm: {},
      tableOption: this.buildTableOption(),
      loading: true,
      data: [],
      query: {},
      allDept: 0,
      page: {
        pageSize: 10,
        pageSizes: [10, 20, 50, 100],
        currentPage: 1,
        total: 0,
      },
      selectionList: [],
      projectBox: false,
      dialogType: 'add',
      dialogReadonly: false,
      submitLoading: false,
      form: emptyForm(),
      formRules: {
        changeType: [{ required: true, message: '请选择变更类型', trigger: 'change' }],
        projectType: [{ required: true, message: '请选择项目类型', trigger: 'change' }],
        projectName: [
          { required: true, message: '请输入项目名称', trigger: 'blur' },
          { max: 50, message: '项目名称不能超过50个字符', trigger: 'blur' },
        ],
        projectShortName: [
          { required: true, message: '请输入项目简称', trigger: 'blur' },
          { max: 20, message: '项目简称不能超过20个字符', trigger: 'blur' },
        ],
        businessDeptId: [{ required: true, message: '请选择业务部门', trigger: 'change' }],
        undertakeDeptId: [{ required: true, message: '请选择承办部门', trigger: 'change' }],
        customerNames: [{ required: true, message: '请选择客户名称', trigger: 'change' }],
        carrierNames: [{ required: true, message: '请选择下游承运商', trigger: 'change' }],
        fundLimit: [
          {
            required: true,
            label: '项目资金使用额度',
            validator: validateRequiredAmount,
            trigger: 'blur',
          },
        ],
        receivableLimit: [
          {
            required: true,
            label: '项目应收账款额度',
            validator: validateRequiredAmount,
            trigger: 'blur',
          },
        ],
        receivableDays: [
          { required: true, label: '应收账款回款期限', validator: validateDays, trigger: 'blur' },
        ],
        paymentDays: [
          { required: true, label: '回款账期', validator: validateDays, trigger: 'blur' },
        ],
        projectScale: [{ label: '项目规模', validator: validateAmount, trigger: 'blur' }],
        estimatedProfit: [{ label: '预估利润', validator: validateAmount, trigger: 'blur' }],
        fundDemand: [{ label: '资金需求', validator: validateAmount, trigger: 'blur' }],
        handlerUserId: [
          {
            required: true,
            message: '请选择项目经办人',
            label: '项目经办人',
            nameProp: 'handlerUserName',
            validator: validateUser,
            trigger: 'change',
          },
        ],
        principalUserId: [
          {
            required: true,
            message: '请选择项目负责人',
            label: '项目负责人',
            nameProp: 'principalUserName',
            validator: validateUser,
            trigger: 'change',
          },
        ],
        changeContent: [
          { required: true, message: '请输入变更内容', trigger: 'blur' },
          { max: 2000, message: '变更内容不能超过2000个字符', trigger: 'blur' },
        ],
        changeReason: [
          { required: true, message: '请输入变更原因', trigger: 'blur' },
          { max: 2000, message: '变更原因不能超过2000个字符', trigger: 'blur' },
        ],
      },
      deptOptions: [],
      deptTreeOptions: [],
      deptCascaderProps: {
        label: 'label',
        value: 'value',
        children: 'children',
        emitPath: false,
        checkStrictly: true,
      },
      cargoTypeOptions: [],
      selectedCustomerId: [],
      selectedCarrierIds: [],
      customerOptions: [],
      carrierOptions: [],
      customerLoading: false,
      carrierLoading: false,
      customerRows: [],
      carrierRows: [],
      attachmentRows: [],
      attachmentFileTypes: [
        'pdf',
        'bmp',
        'jpeg',
        'png',
        'jpg',
        'doc',
        'docx',
        'ppt',
        'pptx',
        'xlsx',
        'xls',
        'eml',
        'msg',
        'zip',
      ],
      changeRows: [],
      userBox: false,
      userPickType: '',
      userLoading: false,
      userData: [],
      userQuery: {
        realName: '',
        account: '',
      },
      userPage: {
        currentPage: 1,
        pageSize: 10,
        total: 0,
      },
    };
  },
  computed: {
    ...mapGetters(['permission', 'userInfo']),
    permissionList() {
      return {
        addBtn: this.canCreate,
      };
    },
    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.allDept === 1;
    },
    canCreate() {
      return this.hasPermission(`${this.config.permission}_add`) && !this.readonlyScope;
    },
    canMajorProjectSupplement() {
      return this.hasPermission(`${this.config.permission}_supplement`) && !this.readonlyScope;
    },
    isMajorProjectSupplementDialog() {
      return this.dialogType === 'majorSupplement';
    },
    isChangeDialog() {
      return this.dialogType === 'change';
    },
    isBasicInfoReadonly() {
      return this.dialogReadonly || this.isChangeDialog;
    },
    ids() {
      return this.selectionList.map(item => item.id).join(',');
    },
    projectDialogTitle() {
      if (this.isChangeDialog) {
        return '项目变更';
      }
      if (this.isMajorProjectSupplementDialog) {
        return '重大项目补录';
      }
      const text = { add: '新增', edit: '编辑', view: '查看' }[this.dialogType] || '新增';
      return `${text}${this.config.title}`;
    },
    projectAttachmentTypeOptions() {
      return this.form.projectType === '重大项目'
        ? majorProjectAttachmentTypeOptions
        : normalProjectAttachmentTypeOptions;
    },
    missingRequiredAttachmentTypes() {
      const uploadedTypes = new Set(this.attachmentRows.map(item => item.fileType).filter(Boolean));
      return this.projectAttachmentTypeOptions
        .map(item => item.value)
        .filter(fileType => !uploadedTypes.has(fileType));
    },
  },
  created() {
    this.loadDeptOptions();
    this.loadCargoTypeOptions();
    this.loadTransportTypeOptions();
  },
  methods: {
    buildTableOption() {
      return {
        ...option,
        dialogType: 'dialog',
        addBtn: false,
        viewBtn: false,
        editBtn: false,
        delBtn: false,
        // 操作列收窄：本项目操作按钮较少，320px 过宽，调整为 220px（链接会自动换行兜底）
        menuWidth: 180,
        column: (option.column || []).map(column => ({ ...column, display: false })),
      };
    },
    hasPermission(code) {
      return this.isAdmin || this.validData(this.permission && this.permission[code], false);
    },
    statusValue(row) {
      return row[this.config.statusProp || 'status'];
    },
    inStatus(row, statusList) {
      if (!statusList || !statusList.length) return true;
      return statusList.includes(this.statusValue(row));
    },
    canEdit(row) {
      return (
        this.hasPermission(`${this.config.permission}_edit`) &&
        !this.readonlyScope &&
        !row.readonly &&
        this.inStatus(row, this.config.editStatus)
      );
    },
    canDelete(row) {
      return (
        this.hasPermission(`${this.config.permission}_delete`) &&
        !this.readonlyScope &&
        !row.readonly &&
        this.inStatus(row, this.config.deleteStatus)
      );
    },
    customOperations(row) {
      return (this.config.operations || []).filter(operation => this.canOperation(operation, row));
    },
    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 || [];
      return !statusList.length || statusList.includes(row[prop]);
    },
    displayStatus(row, prop) {
      const textProp = prop === this.config.statusProp ? this.config.statusTextProp : '';
      if (textProp && row[textProp]) return row[textProp];
      const column = this.findColumn(this.tableOption.column, prop);
      const item = column && (column.dicData || []).find(dic => dic.value === row[prop]);
      return item ? item.label : row[prop] || '未知';
    },
    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';
    },
    onLoad(page, params = {}) {
      this.loading = true;
      this.api
        .getList(page.currentPage, page.pageSize, {
          ...params,
          ...this.query,
          allDept: this.allDept,
        })
        .then(res => {
          const result = res.data.data || {};
          this.page.total = result.total || 0;
          this.data = result.records || [];
          this.selectionClear();
        })
        .finally(() => {
          this.loading = false;
        });
    },
    searchReset() {
      this.query = {};
      this.page.currentPage = 1;
      this.onLoad(this.page);
    },
    searchChange(params, done) {
      this.query = params;
      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);
    },
    handleScopeChange() {
      if (!this.canViewAllDept) this.allDept = 0;
      this.page.currentPage = 1;
      this.onLoad(this.page);
    },
    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) {
        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.success('删除成功!');
      }
      this.selectionClear();
    },
    handleCustomOperation(operation, row) {
      if (operation.action === 'startChange') {
        this.openProjectDialog('change', row);
        return;
      }
      const run = value => {
        const args = operation.prompt ? [row.id, value] : [row.id];
        this.api[operation.action](...args).then(res => {
          if (res.data?.success === false || res.data?.data === false) {
            this.$message.error(res.data?.msg || `${operation.label}失败，请联系管理员`);
            return;
          }
          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());
    },
    buildExportParams() {
      return {
        ...this.query,
        allDept: this.allDept,
        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();
          });
      });
    },
    openProjectDialog(type, row = {}) {
      this.dialogType = type;
      this.dialogReadonly = type === 'view';
      this.projectBox = true;
      if (type === 'add' || type === 'majorSupplement') {
        this.applyProjectDetail({
          ...emptyForm(),
          projectType: type === 'majorSupplement' ? '重大项目' : '普通项目',
        });
        this.fillDefaultUsers();
        return;
      }
      this.api.getDetail(row.id).then(res => {
        const detail = res.data.data || {};
        this.applyProjectDetail({
          ...detail,
          changeType: type === 'change' ? this.resolveChangeType(detail) : detail.changeType,
        });
      });
    },
    resetProjectDialog() {
      this.$refs.projectForm?.clearValidate();
      this.form = emptyForm();
      this.customerRows = [];
      this.carrierRows = [];
      this.attachmentRows = [];
      this.changeRows = [];
      this.selectedCustomerId = [];
      this.selectedCarrierIds = [];
    },
    applyProjectDetail(row) {
      const displayRow = {
        ...row,
        fundLimit: this.formatAmount(row.fundLimit),
        receivableLimit: this.formatAmount(row.receivableLimit),
        ...(this.dialogReadonly
          ? {
              projectScale: this.normalizeReadonlyAmount(row.projectScale),
              estimatedProfit: this.normalizeReadonlyAmount(row.estimatedProfit),
              fundDemand: this.normalizeReadonlyAmount(row.fundDemand),
            }
          : {}),
      };
      const form = {
        ...emptyForm(),
        ...displayRow,
        handlerUserName: this.resolveUserRealName(displayRow, 'handler'),
        principalUserName: this.resolveUserRealName(displayRow, 'principal'),
        businessDateRange:
          displayRow.businessStartDate && displayRow.businessEndDate
            ? [displayRow.businessStartDate, displayRow.businessEndDate]
            : [],
      };
      const situation = this.parseSituation(displayRow.situationRemark);
      const customerRows = this.parseJsonArray(displayRow.customerJson);
      const carrierRows = this.parseJsonArray(displayRow.carrierJson);
      this.form = {
        ...form,
        ...situation,
        customerNames: this.getCustomerRowNames(customerRows, 'customer') || form.customerNames,
        carrierNames: this.getCustomerRowNames(carrierRows, 'carrier') || form.carrierNames,
      };
      this.customerRows = customerRows;
      this.carrierRows = carrierRows;
      this.attachmentRows = this.parseJsonArray(displayRow.attachmentsJson);
      this.changeRows = this.buildChangeRows(displayRow);
      this.selectedCustomerId = this.customerRows
        .map(item => this.getCustomerRowId(item))
        .filter(Boolean);
      this.selectedCarrierIds = this.carrierRows
        .map(item => this.getCustomerRowId(item))
        .filter(Boolean);
      this.syncSelectedCustomerOptions();
    },
    fillDefaultUsers() {
      const userId = this.userInfo?.userId || this.userInfo?.id || '';
      const userName =
        this.userInfo?.realName ||
        this.userInfo?.real_name ||
        this.userInfo?.name ||
        this.userInfo?.userName ||
        this.userInfo?.user_name ||
        this.userInfo?.account ||
        '';
      if (userId && userName) {
        this.form.handlerUserId = userId;
        this.form.handlerUserName = userName;
        this.form.principalUserId = userId;
        this.form.principalUserName = userName;
      }
    },
    resolveUserRealName(row = {}, role) {
      return (
        row[`${role}RealName`] ||
        row[`${role}UserRealName`] ||
        row[`${role}User`]?.realName ||
        row[role]?.realName ||
        row[`${role}UserName`] ||
        ''
      );
    },
    resolveChangeType(row = {}) {
      if (row.changeType) return row.changeType;
      const node = row.currentNode || '';
      if (String(node).includes('备案')) return '项目备案调整';
      return '项目变更';
    },
    submitProject() {
      this.$refs.projectForm.validate(valid => {
        if (!valid) {
          this.handleValidateFail();
          return;
        }
        if (!this.validateAttachmentFileTypes()) return;
        this.submitLoading = true;
        this.api
          .submit(this.normalizeSubmitForm())
          .then(
            res => {
              if (res.data?.success === false || res.data?.data === false) {
                this.$message.error(res.data?.msg || '保存失败，请联系管理员');
                return;
              }
              this.$message.success('操作成功!');
              this.projectBox = false;
              this.onLoad(this.page, this.query);
            },
            error => {
              window.console.log(error);
            }
          )
          .finally(() => {
            this.submitLoading = false;
          });
      });
    },
    saveProject() {
      this.submitLoading = true;
      this.api
        .saveDraft(this.normalizeSubmitForm())
        .then(res => {
          if (res.data?.success === false || res.data?.data === false) {
            this.$message.error(res.data?.msg || '保存失败，请联系管理员');
            return;
          }
          this.$message.success('保存成功!');
          this.projectBox = false;
          this.onLoad(this.page, this.query);
        })
        .finally(() => {
          this.submitLoading = false;
        });
    },
    handleCancelProject() {
      if (!['add', 'majorSupplement', 'change'].includes(this.dialogType)) {
        this.projectBox = false;
        return;
      }
      this.$confirm('确认后直接关闭弹窗，列表无数据变更，是否继续？', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      }).then(() => {
        this.projectBox = false;
      });
    },
    saveChangeProject() {
      this.submitChangeForm(false);
    },
    submitChangeProject() {
      this.submitChangeForm(true);
    },
    submitChangeForm(needSubmit) {
      this.$refs.projectForm.validate(valid => {
        if (!valid) {
          this.handleValidateFail();
          return;
        }
        if (!this.validateAttachmentFileTypes()) return;
        this.submitLoading = true;
        const request = needSubmit ? this.api.submitChange : this.api.saveChange;
        request(this.normalizeSubmitForm({ includeChangeType: true }))
          .then(res => {
            if (res.data?.success === false || res.data?.data === false) {
              this.$message.error(
                res.data?.msg || `${needSubmit ? '提交' : '保存'}失败，请联系管理员`
              );
              return;
            }
            this.$message.success(`${needSubmit ? '提交' : '保存'}成功!`);
            this.projectBox = false;
            this.onLoad(this.page, this.query);
          })
          .finally(() => {
            this.submitLoading = false;
          });
      });
    },
    normalizeSubmitForm(options = {}) {
      const [businessStartDate, businessEndDate] = this.form.businessDateRange || [];
      const submitRow = {
        ...this.form,
        fundLimit: this.formatAmount(this.form.fundLimit),
        receivableLimit: this.formatAmount(this.form.receivableLimit),
        businessStartDate,
        businessEndDate,
        customerJson: JSON.stringify(this.customerRows),
        carrierJson: JSON.stringify(this.carrierRows),
        attachmentsJson: JSON.stringify(this.attachmentRows),
        situationRemark: JSON.stringify({
          projectIntro: this.form.projectIntro || '',
          profitRemark: this.form.profitRemark || '',
          riskPoint: this.form.riskPoint || '',
          emergencyPlan: this.form.emergencyPlan || '',
        }),
      };
      delete submitRow.businessDateRange;
      if (!options.includeChangeType) {
        delete submitRow.changeType;
      }
      ['projectIntro', 'profitRemark', 'riskPoint', 'emergencyPlan'].forEach(
        key => delete submitRow[key]
      );
      Object.keys(submitRow).forEach(key => {
        if (typeof submitRow[key] === 'string') {
          submitRow[key] = submitRow[key].trim();
        }
      });
      return submitRow;
    },
    normalizeAttachmentFileTypes() {
      const values = this.projectAttachmentTypeOptions.map(item => item.value);
      this.attachmentRows = this.attachmentRows.map(row => ({
        ...row,
        fileType: values.includes(row.fileType) ? row.fileType : '',
      }));
    },
    validateAttachmentFileTypes() {
      const values = this.projectAttachmentTypeOptions.map(item => item.value);
      const invalidIndex = this.attachmentRows.findIndex(row => !values.includes(row.fileType));
      if (invalidIndex === -1) return true;
      this.$message.warning(`请选择第${invalidIndex + 1}行项目材料的附件类型`);
      this.$nextTick(() => {
        const field = this.$el.querySelector('.project-apply-form__material-head');
        if (field && typeof field.scrollIntoView === 'function') {
          field.scrollIntoView({ behavior: 'smooth', block: 'center' });
        }
      });
      return false;
    },
    handleValidateFail() {
      this.$message.warning('请完善表单必填项后再提交');
      this.$nextTick(() => {
        const field = this.$el.querySelector('.is-error');
        if (field && typeof field.scrollIntoView === 'function') {
          field.scrollIntoView({ behavior: 'smooth', block: 'center' });
        }
      });
    },
    parseSituation(value) {
      if (!value) {
        return { projectIntro: '', profitRemark: '', riskPoint: '', emergencyPlan: '' };
      }
      try {
        const data = JSON.parse(value);
        if (data && typeof data === 'object') {
          return {
            projectIntro: data.projectIntro || '',
            profitRemark: data.profitRemark || '',
            riskPoint: data.riskPoint || '',
            emergencyPlan: data.emergencyPlan || '',
          };
        }
      } catch (error) {
        return { projectIntro: value, profitRemark: '', riskPoint: '', emergencyPlan: '' };
      }
      return { projectIntro: String(value), profitRemark: '', riskPoint: '', emergencyPlan: '' };
    },
    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 [];
      }
    },
    getCustomerRowId(row = {}) {
      return row.id || row.customerId || row.carrierId || '';
    },
    getCustomerRowLabel(row = {}, type) {
      return (
        row[type === 'carrier' ? 'carrier' : 'customer'] ||
        row.fullName ||
        row.shortName ||
        row.customerName ||
        row.carrierName ||
        row.customerCode ||
        ''
      );
    },
    getCustomerRowNames(rows = [], type) {
      return rows
        .map(row => this.getCustomerRowLabel(row, type))
        .filter(Boolean)
        .join('、');
    },
    buildCustomerOptionsFromRows(rows = [], type) {
      return rows
        .map(row => {
          const value = this.getCustomerRowId(row);
          const label = this.getCustomerRowLabel(row, type);
          return value && label ? { label, value, raw: row } : null;
        })
        .filter(Boolean);
    },
    normalizeReadonlyAmount(value) {
      return value === null || value === undefined || value === '' || Number(value) === -1
        ? ''
        : value;
    },
    mergeCustomerOptions(options = [], selectedOptions = []) {
      const result = [...selectedOptions];
      options.forEach(option => {
        const exists = result.some(item => String(item.value) === String(option.value));
        if (!exists) {
          result.push(option);
        }
      });
      return result;
    },
    syncSelectedCustomerOptions() {
      this.customerOptions = this.mergeCustomerOptions(
        this.customerOptions,
        this.buildCustomerOptionsFromRows(this.customerRows, 'customer')
      );
      this.carrierOptions = this.mergeCustomerOptions(
        this.carrierOptions,
        this.buildCustomerOptionsFromRows(this.carrierRows, 'carrier')
      );
    },
    buildChangeRows(row) {
      if (!row || (!row.changeContent && !row.changeReason)) return [];
      return [
        {
          changeDate: row.updateTime || row.createTime || '',
          changeType:
            row.changeType ||
            (String(row.currentNode || '').includes('备案') ? '项目备案调整' : '项目变更'),
          handler: this.resolveUserRealName(row, 'handler'),
          content: row.changeContent || '',
          reason: row.changeReason || '',
          status: row.approvalStatusName || row.approvalStatus || '',
          action: this.dialogReadonly ? '流程' : '编辑',
        },
      ];
    },
    handleBusinessDeptChange(value) {
      const dept = this.deptOptions.find(item => item.value === value);
      this.form.businessDeptName = dept ? dept.rawLabel : '';
      this.$refs.projectForm?.validateField('businessDeptId');
    },
    handleUndertakeDeptChange(value) {
      const dept = this.deptOptions.find(item => item.value === value);
      this.form.undertakeDeptName = dept ? dept.rawLabel : '';
      this.$refs.projectForm?.validateField('undertakeDeptId');
    },
    handleProjectTypeChange() {
      this.normalizeAttachmentFileTypes();
      this.$refs.projectForm?.validateField('projectType');
    },
    loadDeptOptions() {
      getDeptTree().then(res => {
        const deptTree = res.data.data || [];
        this.deptOptions = this.flattenDept(deptTree);
        this.deptTreeOptions = this.toDeptCascaderOptions(deptTree);
      });
    },
    toDeptCascaderOptions(list = []) {
      return list.map(item => ({
        label: item.title || item.deptName || item.name,
        value: item.id,
        children: item.children?.length ? this.toDeptCascaderOptions(item.children) : undefined,
      }));
    },
    flattenDept(list = [], level = 0) {
      return list.reduce((result, item) => {
        const label = item.title || item.deptName || item.name;
        result.push({
          label: `${'　'.repeat(level)}${label}`,
          rawLabel: label,
          value: item.id,
        });
        if (item.children && item.children.length) {
          result.push(...this.flattenDept(item.children, level + 1));
        }
        return result;
      }, []);
    },
    loadCargoTypeOptions() {
      getSystemDictionary({ code: 'type_of_goods' }).then(res => {
        this.cargoTypeOptions = (res.data.data || []).map(item => ({
          label: item.dictValue,
          value: item.dictKey,
        }));
      });
    },
    loadTransportTypeOptions() {
      getBizDictionary({ code: 'transport_type' }).then(res => {
        this.transportTypeOptions = (res.data.data || []).map(item => ({
          label: item.dictValue,
          value: item.dictKey,
        }));
      });
    },
    loadCustomerOptions(type) {
      const loadingKey = type === '客户' ? 'customerLoading' : 'carrierLoading';
      const optionsKey = type === '客户' ? 'customerOptions' : 'carrierOptions';
      this[loadingKey] = true;
      getCustomerArchiveList(1, 100, {
        customerType: type,
        status: 1,
        ...(type === '客户' ? { approvalStatus: 'approved' } : {}),
      })
        .then(res => {
          const records = (res.data.data && res.data.data.records) || [];
          const selectedOptions = this.buildCustomerOptionsFromRows(
            type === '客户' ? this.customerRows : this.carrierRows,
            type === '客户' ? 'customer' : 'carrier'
          );
          this[optionsKey] = this.mergeCustomerOptions(
            records.map(item => this.mapCustomerOption(item)),
            selectedOptions
          );
        })
        .finally(() => {
          this[loadingKey] = false;
        });
    },
    mapCustomerOption(item) {
      const label = item.fullName || item.shortName || item.customerCode;
      return {
        label,
        value: item.id,
        raw: item,
      };
    },
    handleCustomerChange(ids) {
      if (!ids || !ids.length) {
        this.customerRows = [];
        this.form.customerNames = '';
        this.form.customerJson = '';
        return;
      }
      Promise.all(ids.map(id => this.loadCustomerDetailRow(id, 'customer'))).then(rows => {
        this.customerRows = rows;
        this.form.customerNames = rows.map(item => item.customer).join('、');
        this.form.customerJson = JSON.stringify(this.customerRows);
        this.syncSelectedCustomerOptions();
        this.$refs.projectForm?.validateField('customerNames');
      });
    },
    handleCarrierChange(ids) {
      if (!ids || !ids.length) {
        this.carrierRows = [];
        this.form.carrierNames = '';
        this.form.carrierJson = '';
        return;
      }
      Promise.all(ids.map(id => this.loadCustomerDetailRow(id, 'carrier'))).then(rows => {
        this.carrierRows = rows;
        this.form.carrierNames = rows.map(item => item.carrier).join('、');
        this.form.carrierJson = JSON.stringify(this.carrierRows);
        this.syncSelectedCustomerOptions();
        this.$refs.projectForm?.validateField('carrierNames');
      });
    },
    loadCustomerDetailRow(id, type) {
      return getCustomerArchiveDetail(id).then(res => {
        const detail = res.data.data || {};
        return this.mapCustomerTableRow(detail, type);
      });
    },
    mapCustomerTableRow(item, type) {
      const contacts = item.contacts || [];
      const defaultContact = contacts.find(contact => contact.isDefault === 1) || contacts[0] || {};
      const name = item.fullName || item.shortName || item.customerCode || '';
      const row = {
        id: item.id,
        credit: item.maxCreditLimit || item.applyCreditLimit || '',
        companyNature: item.customerNature || '',
        legalPerson: item.legalPerson || '',
        address: item.registeredAddress || '',
        contact: defaultContact.contactName || item.principal || '',
        phone: defaultContact.contactPhone || item.contactPhone || '',
      };
      if (type === 'carrier') {
        row.carrier = name;
      } else {
        row.customer = name;
      }
      return row;
    },
    handleNumberInput(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];
    },
    formatAmount(value) {
      if (value === null || value === undefined || value === '' || Number(value) === -1) return '';
      const amount = Number(value);
      return Number.isFinite(amount) ? amount.toFixed(2) : value;
    },
    formatAmountField(prop) {
      this.form[prop] = this.formatAmount(this.form[prop]);
    },
    handleIntegerInput(prop, value) {
      this.form[prop] = String(value || '').replace(/[^\d]/g, '');
    },
    handleAttachmentChange(list) {
      const userName = this.userInfo?.realName || this.userInfo?.userName || '';
      const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
      const values = this.projectAttachmentTypeOptions.map(item => item.value);
      this.attachmentRows = (list || []).map(item => ({
        ...item,
        fileType: values.includes(item.fileType) ? item.fileType : '',
        description: item.description || '',
        uploadUserName: item.uploadUserName || userName,
        uploadTime: item.uploadTime || uploadTime,
      }));
    },
    removeAttachment(index) {
      this.attachmentRows.splice(index, 1);
    },
    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));
    },
    handleChangeRecordAction() {
      this.$message.info('变更记录详情由后端明细能力生成后展示');
    },
    openUserPicker(type) {
      if (this.dialogReadonly) return;
      this.userPickType = type;
      this.userBox = true;
      this.loadUsers();
    },
    loadUsers() {
      this.userLoading = true;
      getUserList(this.userPage.currentPage, this.userPage.pageSize, this.userQuery)
        .then(res => {
          const result = res.data.data || {};
          this.userData = result.records || [];
          this.userPage.total = result.total || 0;
        })
        .finally(() => {
          this.userLoading = false;
        });
    },
    resetUserQuery() {
      this.userQuery = { realName: '', account: '' };
      this.userPage.currentPage = 1;
      this.loadUsers();
    },
    selectUser(row) {
      if (this.userPickType === 'handler') {
        this.form.handlerUserId = row.id;
        this.form.handlerUserName = row.realName || row.name || row.account;
        this.$refs.projectForm?.validateField('handlerUserId');
      } else {
        this.form.principalUserId = row.id;
        this.form.principalUserName = row.realName || row.name || row.account;
        this.$refs.projectForm?.validateField('principalUserId');
      }
      this.userBox = false;
    },
  },
};
</script>

<style lang="scss" scoped>
.project-apply-form {
  padding: 0;

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

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

  :deep(.el-form-item__label) {
    min-width: 160px;
    white-space: normal;
    line-height: 20px;
  }

  // 分区标题融入白色卡片顶部（与下方内容块拼成一张完整卡片）
  :deep(.dialog-section-title:not(.project-apply-form__material-title)) {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: 8px;
    background: #fff;
    border-radius: 6px 6px 0 0;
    padding: 14px 16px 4px;
    margin-bottom: 0;
    font-weight: 500;
  }

  &__grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: 72px;
    row-gap: 0;
    margin-bottom: 16px;
    background: #fff;
    border-radius: 0 0 6px 6px;
    padding: 14px 16px 4px;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
  }

  &__picker-icon {
    color: #a8abb2;
    cursor: pointer;
  }

  &__table-title {
    display: flex;
    align-items: center;
    gap: 24px;
  }

  &__tip {
    color: #ff7a95;
    font-size: 14px;
    font-weight: 500;
  }

  &__table {
    width: 100%;
    margin-bottom: 16px;
    background: #fff;
    border-radius: 0 0 6px 6px;
    padding: 12px;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);

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

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

  &__textarea-list {
    margin-bottom: 16px;
    background: #fff;
    border-radius: 0 0 6px 6px;
    padding: 14px 16px 4px;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);

    :deep(.el-form-item) {
      align-items: flex-start;
    }

    :deep(.el-form-item__content) {
      width: calc(100% - 180px);
    }
  }

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

  &__material-card {
    margin-bottom: 16px;
    background: #fff;
    border-radius: 6px;
    padding: 14px 16px;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
  }

  &__material-table {
    width: 100%;
    margin-bottom: 12px;

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

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

  &__material-warning {
    margin-bottom: 12px;
  }

  &__material-title {
    flex: 1;
  }

  &__material-warn {
    margin-left: 24px;
    color: #f56c6c;
    font-size: 14px;
    font-weight: 500;
  }

  &__upload {
    margin: 0 0 16px;
    background: #fff;
    border-radius: 6px;
    padding: 12px;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
  }

  &__change-textarea {
    margin-bottom: 16px;
    background: #fff;
    border-radius: 0 0 6px 6px;
    padding: 14px 16px 4px;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);

    :deep(.el-form-item__content) {
      width: 100%;
      margin-left: 0 !important;
    }
  }
}

:global(.project-apply-dialog .el-dialog__body) {
  max-height: 76vh;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 16px;
  background: #f5f6fa;
}

.project-apply-dialog__footer {
  display: flex;
  justify-content: flex-end;
  gap: 8px;

  &--change {
    justify-content: flex-start;
    padding-left: 24px;
  }
}

.project-apply-user-dialog {
  &__search {
    display: grid;
    grid-template-columns: 1fr 1fr auto auto;
    gap: 8px;
    margin-bottom: 12px;
  }

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

@media (max-width: 1200px) {
  .project-apply-form {
    &__grid {
      grid-template-columns: 1fr;
      column-gap: 0;
    }
  }
}
</style>
