zhangwencui
昨天 2d5ff68e16ce08e814df35b226687c8575498a44
维护审批人功能,以及影响到的模块更改
已修改7个文件
12588 ■■■■ 文件已修改
src/api/collaborativeApproval/approvalProcess.js 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue 783 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue 764 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/index.vue 945 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementLedger/index.vue 270 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesLedger/index.vue 7716 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesQuotation/index.vue 2086 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/collaborativeApproval/approvalProcess.js
@@ -60,4 +60,28 @@
        url: '/approveNode/details/' + query,
        method: 'get',
    })
}
// ç»´æŠ¤å®¡æ‰¹äººæ–°å¢ž-更新
export function addApproveUser(query) {
    return request({
        url: '/approveUser/add',
        method: 'post',
        data: query,
    })
}
// åˆ é™¤å®¡æ‰¹äºº
export function deleteApproveUser(query) {
    return request({
        url: '/approveUser/del',
        method: 'delete',
        data: query,
    })
}
// æŸ¥è¯¢å®¡æ‰¹äºº
export function approveUserList(query) {
    return request({
        url: '/approveUser/getList',
        method: 'get',
        params: query,
    })
}
src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
@@ -1,118 +1,135 @@
<template>
  <div>
    <el-dialog
      v-model="dialogFormVisible"
      :title="operationType === 'approval' ? '审批' : '详情'"
      width="700px"
      @close="closeDia"
    >
            <el-form :model="form" label-width="140px" label-position="top" ref="formRef">
                <el-row>
                    <el-col :span="24">
                        <el-form-item label="流程编号:" prop="approveId">
                            <el-input v-model="form.approveId" placeholder="自动编号" clearable disabled/>
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row>
                    <el-col :span="24">
                        <el-form-item label="申请部门:">
                            <el-select
                                disabled
                                v-model="form.approveDeptId"
                                placeholder="选择部门"
                            >
                                <el-option
                                    v-for="user in productOptions"
                                    :key="user.deptId"
                                    :label="user.deptName"
                                    :value="user.deptId"
                                />
                            </el-select>
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row v-if="!isQuotationApproval && !isPurchaseApproval">
                    <el-col :span="24">
                        <el-form-item :label="props.approveType == 5 ? '采购合同号:' : '审批事由:'" prop="approveReason">
                            <el-input v-model="form.approveReason" placeholder="请输入" clearable type="textarea" disabled/>
                        </el-form-item>
                    </el-col>
                </el-row>
                <!-- å®¡æ‰¹äººé€‰æ‹©ï¼ˆåŠ¨æ€èŠ‚ç‚¹ï¼‰ -->
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="申请人:" prop="approveUser">
                            <el-select
                                v-model="form.approveUser"
                                placeholder="选择人员"
                                disabled
                            >
                                <el-option
                                    v-for="user in userList"
                                    :key="user.userId"
                                    :label="user.nickName"
                                    :value="user.userId"
                                />
                            </el-select>
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="申请日期:" prop="approveTime">
                            <el-date-picker
                                v-model="form.approveTime"
                                type="date"
                                placeholder="请选择日期"
                                value-format="YYYY-MM-DD"
                                format="YYYY-MM-DD"
                                clearable
                                style="width: 100%"
                                disabled
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
            </el-form>
    <el-dialog v-model="dialogFormVisible"
               :title="operationType === 'approval' ? '审批' : '详情'"
               width="700px"
               @close="closeDia">
      <el-form :model="form"
               label-width="140px"
               label-position="top"
               ref="formRef">
        <el-row>
          <el-col :span="24">
            <el-form-item label="流程编号:"
                          prop="approveId">
              <el-input v-model="form.approveId"
                        placeholder="自动编号"
                        clearable
                        disabled />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="申请部门:">
              <el-select disabled
                         v-model="form.approveDeptId"
                         placeholder="选择部门">
                <el-option v-for="user in productOptions"
                           :key="user.deptId"
                           :label="user.deptName"
                           :value="user.deptId" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row v-if="!isQuotationApproval && !isPurchaseApproval">
          <el-col :span="24">
            <el-form-item :label="props.approveType == 5 ? '采购合同号:' : '审批事由:'"
                          prop="approveReason">
              <el-input v-model="form.approveReason"
                        placeholder="请输入"
                        clearable
                        type="textarea"
                        disabled />
            </el-form-item>
          </el-col>
        </el-row>
        <!-- å®¡æ‰¹äººé€‰æ‹©ï¼ˆåŠ¨æ€èŠ‚ç‚¹ï¼‰ -->
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="申请人:"
                          prop="approveUser">
              <el-input v-model="form.approveUserName"
                        clearable
                        disabled />
              <!-- <el-select v-model="form.approveUser"
                         placeholder="选择人员"
                         disabled>
                <el-option v-for="user in userList"
                           :key="user.userId"
                           :label="user.nickName"
                           :value="user.userId" />
              </el-select> -->
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="申请日期:"
                          prop="approveTime">
              <el-date-picker v-model="form.approveTime"
                              type="date"
                              placeholder="请选择日期"
                              value-format="YYYY-MM-DD"
                              format="YYYY-MM-DD"
                              clearable
                              style="width: 100%"
                              disabled />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <!-- æŠ¥ä»·å®¡æ‰¹ï¼šå±•示报价详情(复用销售报价"查看详情对话框"内容结构) -->
      <div v-if="isQuotationApproval" style="margin: 10px 0 18px;">
      <div v-if="isQuotationApproval"
           style="margin: 10px 0 18px;">
        <el-divider content-position="left">报价详情</el-divider>
        <el-skeleton :loading="quotationLoading" animated>
        <el-skeleton :loading="quotationLoading"
                     animated>
          <template #template>
            <el-skeleton-item variant="h3" style="width: 30%" />
            <el-skeleton-item variant="text" style="width: 100%" />
            <el-skeleton-item variant="text" style="width: 100%" />
            <el-skeleton-item variant="h3"
                              style="width: 30%" />
            <el-skeleton-item variant="text"
                              style="width: 100%" />
            <el-skeleton-item variant="text"
                              style="width: 100%" />
          </template>
          <template #default>
            <el-empty v-if="!currentQuotation || !currentQuotation.quotationNo" description="未查询到对应报价详情" />
            <el-empty v-if="!currentQuotation || !currentQuotation.quotationNo"
                      description="未查询到对应报价详情" />
            <template v-else>
              <el-descriptions :column="2" border>
              <el-descriptions :column="2"
                               border>
                <el-descriptions-item label="报价单号">{{ currentQuotation.quotationNo }}</el-descriptions-item>
                <el-descriptions-item label="客户名称">{{ currentQuotation.customer }}</el-descriptions-item>
                <el-descriptions-item label="业务员">{{ currentQuotation.salesperson }}</el-descriptions-item>
                <el-descriptions-item label="报价日期">{{ currentQuotation.quotationDate }}</el-descriptions-item>
                <el-descriptions-item label="有效期至">{{ currentQuotation.validDate }}</el-descriptions-item>
                <el-descriptions-item label="付款方式">{{ currentQuotation.paymentMethod }}</el-descriptions-item>
                <el-descriptions-item label="报价总额" :span="2">
                <el-descriptions-item label="报价总额"
                                      :span="2">
                  <span style="font-size: 18px; color: #e6a23c; font-weight: bold;">
                    Â¥{{ Number(currentQuotation.totalAmount ?? 0).toFixed(2) }}
                  </span>
                </el-descriptions-item>
              </el-descriptions>
              <div style="margin-top: 20px;">
                <h4>产品明细</h4>
                <el-table :data="currentQuotation.products || []" border style="width: 100%">
                  <el-table-column prop="product" label="产品名称" />
                  <el-table-column prop="specification" label="规格型号" />
                  <el-table-column prop="unit" label="单位" />
                  <el-table-column prop="unitPrice" label="单价">
                <el-table :data="currentQuotation.products || []"
                          border
                          style="width: 100%">
                  <el-table-column prop="product"
                                   label="产品名称" />
                  <el-table-column prop="specification"
                                   label="规格型号" />
                  <el-table-column prop="unit"
                                   label="单位" />
                  <el-table-column prop="unitPrice"
                                   label="单价">
                    <template #default="scope">Â¥{{ Number(scope.row.unitPrice ?? 0).toFixed(2) }}</template>
                  </el-table-column>
                </el-table>
              </div>
              <div v-if="currentQuotation.remark" style="margin-top: 20px;">
              <div v-if="currentQuotation.remark"
                   style="margin-top: 20px;">
                <h4>备注</h4>
                <p>{{ currentQuotation.remark }}</p>
              </div>
@@ -120,20 +137,26 @@
          </template>
        </el-skeleton>
      </div>
      <!-- é‡‡è´­å®¡æ‰¹ï¼šå±•示采购详情 -->
      <div v-if="isPurchaseApproval" style="margin: 10px 0 18px;">
      <div v-if="isPurchaseApproval"
           style="margin: 10px 0 18px;">
        <el-divider content-position="left">采购详情</el-divider>
        <el-skeleton :loading="purchaseLoading" animated>
        <el-skeleton :loading="purchaseLoading"
                     animated>
          <template #template>
            <el-skeleton-item variant="h3" style="width: 30%" />
            <el-skeleton-item variant="text" style="width: 100%" />
            <el-skeleton-item variant="text" style="width: 100%" />
            <el-skeleton-item variant="h3"
                              style="width: 30%" />
            <el-skeleton-item variant="text"
                              style="width: 100%" />
            <el-skeleton-item variant="text"
                              style="width: 100%" />
          </template>
          <template #default>
            <el-empty v-if="!currentPurchase || !currentPurchase.purchaseContractNumber" description="未查询到对应采购详情" />
            <el-empty v-if="!currentPurchase || !currentPurchase.purchaseContractNumber"
                      description="未查询到对应采购详情" />
            <template v-else>
              <el-descriptions :column="2" border>
              <el-descriptions :column="2"
                               border>
                <el-descriptions-item label="采购合同号">{{ currentPurchase.purchaseContractNumber }}</el-descriptions-item>
                <el-descriptions-item label="供应商名称">{{ currentPurchase.supplierName }}</el-descriptions-item>
                <el-descriptions-item label="项目名称">{{ currentPurchase.projectName }}</el-descriptions-item>
@@ -141,24 +164,32 @@
                <el-descriptions-item label="签订日期">{{ currentPurchase.executionDate }}</el-descriptions-item>
                <el-descriptions-item label="录入日期">{{ currentPurchase.entryDate }}</el-descriptions-item>
                <el-descriptions-item label="付款方式">{{ currentPurchase.paymentMethod }}</el-descriptions-item>
                <el-descriptions-item label="合同金额" :span="2">
                <el-descriptions-item label="合同金额"
                                      :span="2">
                  <span style="font-size: 18px; color: #e6a23c; font-weight: bold;">
                    Â¥{{ Number(currentPurchase.contractAmount ?? 0).toFixed(2) }}
                  </span>
                </el-descriptions-item>
              </el-descriptions>
              <div style="margin-top: 20px;">
                <h4>产品明细</h4>
                <el-table :data="currentPurchase.productData || []" border style="width: 100%">
                  <el-table-column prop="productCategory" label="产品名称" />
                  <el-table-column prop="specificationModel" label="规格型号" />
                  <el-table-column prop="unit" label="单位" />
                  <el-table-column prop="quantity" label="数量" />
                  <el-table-column prop="taxInclusiveUnitPrice" label="含税单价">
                <el-table :data="currentPurchase.productData || []"
                          border
                          style="width: 100%">
                  <el-table-column prop="productCategory"
                                   label="产品名称" />
                  <el-table-column prop="specificationModel"
                                   label="规格型号" />
                  <el-table-column prop="unit"
                                   label="单位" />
                  <el-table-column prop="quantity"
                                   label="数量" />
                  <el-table-column prop="taxInclusiveUnitPrice"
                                   label="含税单价">
                    <template #default="scope">Â¥{{ Number(scope.row.taxInclusiveUnitPrice ?? 0).toFixed(2) }}</template>
                  </el-table-column>
                  <el-table-column prop="taxInclusiveTotalPrice" label="含税总价">
                  <el-table-column prop="taxInclusiveTotalPrice"
                                   label="含税总价">
                    <template #default="scope">Â¥{{ Number(scope.row.taxInclusiveTotalPrice ?? 0).toFixed(2) }}</template>
                  </el-table-column>
                </el-table>
@@ -167,52 +198,78 @@
          </template>
        </el-skeleton>
      </div>
      <el-form :model="{ activities }" ref="formRef" label-position="top">
        <el-steps :active="getActiveStep()" finish-status="success" process-status="process" align-center direction="vertical">
          <el-step
            v-for="(activity, index) in activities"
            :key="index"
                        finish-status="success"
            :title="getNodeTitle(index, activities.length)"
            :description="activity.approveNodeUser"
            :icon="getNodeIcon(activity, index)"
          >
                        <template #icon>
                            <el-icon v-if="activity.approveNodeStatus === 2" color="red" :size="22"><WarningFilled /></el-icon>
                            <el-icon v-else-if="activity.isShen" color="#1890ff" :size="22"><Edit /></el-icon>
                            <el-icon v-else-if="activity.approveNodeStatus === 1" color="#67C23A" :size="26"><Check /></el-icon>
                            <el-icon v-else color="#C0C4CC" :size="22"><MoreFilled /></el-icon>
                        </template>
      <el-form :model="{ activities }"
               ref="formRef"
               label-position="top">
        <el-steps :active="getActiveStep()"
                  finish-status="success"
                  process-status="process"
                  align-center
                  direction="vertical">
          <el-step v-for="(activity, index) in activities"
                   :key="index"
                   finish-status="success"
                   :title="getNodeTitle(index, activities.length)"
                   :description="activity.approveNodeUser"
                   :icon="getNodeIcon(activity, index)">
            <template #icon>
              <el-icon v-if="activity.approveNodeStatus === 2"
                       color="red"
                       :size="22">
                <WarningFilled />
              </el-icon>
              <el-icon v-else-if="activity.isShen"
                       color="#1890ff"
                       :size="22">
                <Edit />
              </el-icon>
              <el-icon v-else-if="activity.approveNodeStatus === 1"
                       color="#67C23A"
                       :size="26">
                <Check />
              </el-icon>
              <el-icon v-else
                       color="#C0C4CC"
                       :size="22">
                <MoreFilled />
              </el-icon>
            </template>
            <template #title>
              <span style="color: #000000">{{ getNodeTitle(index, activities.length) }}</span>
            </template>
            <template #description>
              <div class="node-user">
                <div class="avatar-wrapper">
                  <img :src="userStore.avatar" class="user-avatar" alt=""/>
                  <img :src="userStore.avatar"
                       class="user-avatar"
                       alt="" />
                </div>
                <span style="color: #000000">{{ activity.approveNodeUser }}-{{activity.isApproval}}</span>
              </div>
              <div v-if="!activity.isShen" class="node-reason">
              <div v-if="!activity.isShen"
                   class="node-reason">
                <span>审批意见:</span>{{ activity.approveNodeReason }}
              </div>
              <div v-else-if="activity.isShen">
                <el-form-item
                  :prop="'activities.' + index + '.approveNodeReason'"
                  :rules="[{ required: true, message: '审批意见不能为空', trigger: 'blur' }]"
                >
                  <el-input v-model="activity.approveNodeReason" clearable type="textarea" :disabled="operationType === 'view'"></el-input>
                <el-form-item :prop="'activities.' + index + '.approveNodeReason'"
                              :rules="[{ required: true, message: '审批意见不能为空', trigger: 'blur' }]">
                  <el-input v-model="activity.approveNodeReason"
                            clearable
                            type="textarea"
                            :disabled="operationType === 'view'"></el-input>
                </el-form-item>
              </div>
            </template>
          </el-step>
        </el-steps>
      </el-form>
      <template #footer v-if="operationType === 'approval'">
      <template #footer
                v-if="operationType === 'approval'">
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm(2)">不通过</el-button>
          <el-button type="primary" @click="submitForm(1)">通过</el-button>
          <el-button type="primary"
                     @click="submitForm(2)">不通过</el-button>
          <el-button type="primary"
                     @click="submitForm(1)">通过</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
@@ -221,228 +278,248 @@
</template>
<script setup>
import { computed, getCurrentInstance, nextTick, reactive, ref, toRefs } from "vue";
import {
    approveProcessDetails,
    getDept,
    updateApproveNode
} from "@/api/collaborativeApproval/approvalProcess.js";
import useUserStore from "@/store/modules/user.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import { WarningFilled, Edit, Check, MoreFilled } from '@element-plus/icons-vue'
import { getQuotationList } from "@/api/salesManagement/salesQuotation.js";
import { getPurchaseByCode } from "@/api/procurementManagement/procurementLedger.js";
const emit = defineEmits(['close'])
const { proxy } = getCurrentInstance()
  import {
    computed,
    getCurrentInstance,
    nextTick,
    reactive,
    ref,
    toRefs,
  } from "vue";
  import {
    approveProcessDetails,
    getDept,
    updateApproveNode,
  } from "@/api/collaborativeApproval/approvalProcess.js";
  import useUserStore from "@/store/modules/user.js";
  import { approveUserList } from "@/api/collaborativeApproval/approvalProcess.js";
  import {
    WarningFilled,
    Edit,
    Check,
    MoreFilled,
  } from "@element-plus/icons-vue";
  import { getQuotationList } from "@/api/salesManagement/salesQuotation.js";
  import { getPurchaseByCode } from "@/api/procurementManagement/procurementLedger.js";
  const emit = defineEmits(["close"]);
  const { proxy } = getCurrentInstance();
const props = defineProps({
  approveType: {
    type: [Number, String],
    default: 0
  }
})
const dialogFormVisible = ref(false);
const operationType = ref('')
const activities = ref([])
const formRef = ref(null);
const userStore = useUserStore()
const productOptions = ref([]);
const userList = ref([])
const quotationLoading = ref(false)
const currentQuotation = ref({})
const purchaseLoading = ref(false)
const currentPurchase = ref({})
const isQuotationApproval = computed(() => Number(props.approveType) === 6)
const isPurchaseApproval = computed(() => Number(props.approveType) === 5)
const data = reactive({
    form: {
        approveTime: "",
        approveId: "",
        approveUser: "",
        approveDeptId: "",
        approveReason: "",
        checkResult: "",
    },
});
const { form } = toRefs(data);
// èŠ‚ç‚¹æ ‡é¢˜
const getNodeTitle = (index, len) => {
  if (index === len - 1) return '结束';
  return '审批';
};
// èŽ·å–å½“å‰æ¿€æ´»æ­¥éª¤
const getActiveStep = () => {
  // å¦‚果所有 isShen éƒ½ä¸º false,返回最后一个步骤(全部完成)
  const hasActive = activities.value.some(a => a.isShen === true);
  if (!hasActive) return activities.value.length;
  // å½“前节点索引
  return activities.value.findIndex(a => a.isShen  == true);
};
// æ­¥éª¤icon
const getNodeIcon = (activity, index) => {
  if (activity.approveNodeStatus === 2) return 'el-icon-warning'; // ä¸é€šè¿‡
  if (activity.isShen) return 'Edit';
  return '';
};
// æ‰“开弹框
const openDialog = (type, row) => {
  operationType.value = type;
  dialogFormVisible.value = true;
  currentQuotation.value = {}
  currentPurchase.value = {}
    userListNoPageByTenantId().then((res) => {
        userList.value = res.data;
    });
    form.value = {...row}
    // ç«‹å³æ¸…除表单验证状态(因为字段是disabled的,不需要验证)
    nextTick(() => {
        if (formRef.value) {
            formRef.value.clearValidate();
        }
    });
    // ç¡®ä¿é€‰é¡¹åŠ è½½å®ŒæˆåŽå†åŒ¹é…å€¼ç±»åž‹
    getProductOptions().then(() => {
        // ç¡®ä¿å€¼ç±»åž‹åŒ¹é…ï¼ˆå¦‚果选项已加载)
        if (productOptions.value.length > 0 && form.value.approveDeptId) {
            const matchedOption = productOptions.value.find(opt =>
                opt.deptId == form.value.approveDeptId ||
                String(opt.deptId) === String(form.value.approveDeptId)
            );
            if (matchedOption) {
                form.value.approveDeptId = matchedOption.deptId;
            }
        }
        // å†æ¬¡æ¸…除验证,确保选项加载后值匹配正确
        nextTick(() => {
            if (formRef.value) {
                formRef.value.clearValidate();
            }
        });
    });
  // æŠ¥ä»·å®¡æ‰¹ï¼šç”¨å®¡æ‰¹äº‹ç”±å­—段承载的"报价单号"去查报价列表
  if (isQuotationApproval.value) {
    const quotationNo = row?.approveReason;
    if (quotationNo) {
      quotationLoading.value = true
      getQuotationList({ quotationNo }).then((res) => {
        const records = res?.data?.records || []
        currentQuotation.value = records[0] || {}
      }).finally(() => {
        quotationLoading.value = false
      })
    }
  }
  // é‡‡è´­å®¡æ‰¹ï¼šç”¨å®¡æ‰¹äº‹ç”±å­—段承载的"采购合同号"去查采购详情
  if (isPurchaseApproval.value) {
    const purchaseContractNumber = row?.approveReason;
    if (purchaseContractNumber) {
      purchaseLoading.value = true
      getPurchaseByCode({ purchaseContractNumber }).then((res) => {
        currentPurchase.value = res
      }).catch((err) => {
        console.error('查询采购详情失败:', err)
        proxy.$modal.msgError('查询采购详情失败')
      }).finally(() => {
        purchaseLoading.value = false
      })
    }
  }
  approveProcessDetails(row.approveId).then((res) => {
    activities.value = res.data
    // å¢žåŠ isApproval字段
    activities.value.forEach(item => {
            if (item.url && item.url.includes('word')) {
                item.urlTem = item.url.replaceAll('word', 'img')
            } else {
                item.urlTem = item.url
            }
      if (item.approveNodeStatus === 2) {
        item.isApproval = '已驳回';
      } else if (item.approveNodeStatus === 1) {
        item.isApproval = '已同意';
      } else {
        item.isApproval = '未审批';
      }
    })
  })
}
const getProductOptions = () => {
    return getDept().then((res) => {
        productOptions.value = res.data;
    });
};
// æäº¤å®¡æ‰¹
const submitForm = (status) => {
  const filteredActivities = activities.value.filter(activity => activity.isShen);
  if (!filteredActivities || filteredActivities.length === 0) {
    proxy.$modal.msgError("未找到待审批的节点");
    return;
  }
  const currentActivity = filteredActivities[0];
  if (!currentActivity) {
    proxy.$modal.msgError("未找到待审批的节点");
    return;
  }
  currentActivity.approveNodeStatus = status;
  // åˆ¤æ–­æ˜¯å¦ä¸ºæœ€åŽä¸€æ­¥
  const isLast = activities.value.findIndex(a => a.isShen) === activities.value.length-1;
  updateApproveNode({ ...currentActivity, isLast }).then(() => {
    proxy.$modal.msgSuccess("提交成功");
    closeDia();
  const props = defineProps({
    approveType: {
      type: [Number, String],
      default: 0,
    },
  });
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
  dialogFormVisible.value = false;
  quotationLoading.value = false
  currentQuotation.value = {}
  purchaseLoading.value = false
  currentPurchase.value = {}
  emit('close')
};
defineExpose({
  openDialog,
});
  const dialogFormVisible = ref(false);
  const operationType = ref("");
  const activities = ref([]);
  const formRef = ref(null);
  const userStore = useUserStore();
  const productOptions = ref([]);
  const userList = ref([]);
  const quotationLoading = ref(false);
  const currentQuotation = ref({});
  const purchaseLoading = ref(false);
  const currentPurchase = ref({});
  const isQuotationApproval = computed(() => Number(props.approveType) === 6);
  const isPurchaseApproval = computed(() => Number(props.approveType) === 5);
  const data = reactive({
    form: {
      approveTime: "",
      approveId: "",
      approveUser: "",
      approveDeptId: "",
      approveReason: "",
      checkResult: "",
    },
  });
  const { form } = toRefs(data);
  // èŠ‚ç‚¹æ ‡é¢˜
  const getNodeTitle = (index, len) => {
    if (index === len - 1) return "结束";
    return "审批";
  };
  // èŽ·å–å½“å‰æ¿€æ´»æ­¥éª¤
  const getActiveStep = () => {
    // å¦‚果所有 isShen éƒ½ä¸º false,返回最后一个步骤(全部完成)
    const hasActive = activities.value.some(a => a.isShen === true);
    if (!hasActive) return activities.value.length;
    // å½“前节点索引
    return activities.value.findIndex(a => a.isShen == true);
  };
  // æ­¥éª¤icon
  const getNodeIcon = (activity, index) => {
    if (activity.approveNodeStatus === 2) return "el-icon-warning"; // ä¸é€šè¿‡
    if (activity.isShen) return "Edit";
    return "";
  };
  // æ‰“开弹框
  const openDialog = (type, row) => {
    operationType.value = type;
    dialogFormVisible.value = true;
    currentQuotation.value = {};
    currentPurchase.value = {};
    approveUserList({ approveType: props.approveType }).then(res => {
      userList.value = res.data;
    });
    form.value = { ...row };
    // ç«‹å³æ¸…除表单验证状态(因为字段是disabled的,不需要验证)
    nextTick(() => {
      if (formRef.value) {
        formRef.value.clearValidate();
      }
    });
    // ç¡®ä¿é€‰é¡¹åŠ è½½å®ŒæˆåŽå†åŒ¹é…å€¼ç±»åž‹
    getProductOptions().then(() => {
      // ç¡®ä¿å€¼ç±»åž‹åŒ¹é…ï¼ˆå¦‚果选项已加载)
      if (productOptions.value.length > 0 && form.value.approveDeptId) {
        const matchedOption = productOptions.value.find(
          opt =>
            opt.deptId == form.value.approveDeptId ||
            String(opt.deptId) === String(form.value.approveDeptId)
        );
        if (matchedOption) {
          form.value.approveDeptId = matchedOption.deptId;
        }
      }
      // å†æ¬¡æ¸…除验证,确保选项加载后值匹配正确
      nextTick(() => {
        if (formRef.value) {
          formRef.value.clearValidate();
        }
      });
    });
    // æŠ¥ä»·å®¡æ‰¹ï¼šç”¨å®¡æ‰¹äº‹ç”±å­—段承载的"报价单号"去查报价列表
    if (isQuotationApproval.value) {
      const quotationNo = row?.approveReason;
      if (quotationNo) {
        quotationLoading.value = true;
        getQuotationList({ quotationNo })
          .then(res => {
            const records = res?.data?.records || [];
            currentQuotation.value = records[0] || {};
          })
          .finally(() => {
            quotationLoading.value = false;
          });
      }
    }
    // é‡‡è´­å®¡æ‰¹ï¼šç”¨å®¡æ‰¹äº‹ç”±å­—段承载的"采购合同号"去查采购详情
    if (isPurchaseApproval.value) {
      const purchaseContractNumber = row?.approveReason;
      if (purchaseContractNumber) {
        purchaseLoading.value = true;
        getPurchaseByCode({ purchaseContractNumber })
          .then(res => {
            currentPurchase.value = res;
          })
          .catch(err => {
            console.error("查询采购详情失败:", err);
            proxy.$modal.msgError("查询采购详情失败");
          })
          .finally(() => {
            purchaseLoading.value = false;
          });
      }
    }
    approveProcessDetails(row.approveId).then(res => {
      activities.value = res.data;
      // å¢žåŠ isApproval字段
      activities.value.forEach(item => {
        if (item.url && item.url.includes("word")) {
          item.urlTem = item.url.replaceAll("word", "img");
        } else {
          item.urlTem = item.url;
        }
        if (item.approveNodeStatus === 2) {
          item.isApproval = "已驳回";
        } else if (item.approveNodeStatus === 1) {
          item.isApproval = "已同意";
        } else {
          item.isApproval = "未审批";
        }
      });
    });
  };
  const getProductOptions = () => {
    return getDept().then(res => {
      productOptions.value = res.data;
    });
  };
  // æäº¤å®¡æ‰¹
  const submitForm = status => {
    const filteredActivities = activities.value.filter(
      activity => activity.isShen
    );
    if (!filteredActivities || filteredActivities.length === 0) {
      proxy.$modal.msgError("未找到待审批的节点");
      return;
    }
    const currentActivity = filteredActivities[0];
    if (!currentActivity) {
      proxy.$modal.msgError("未找到待审批的节点");
      return;
    }
    currentActivity.approveNodeStatus = status;
    // åˆ¤æ–­æ˜¯å¦ä¸ºæœ€åŽä¸€æ­¥
    const isLast =
      activities.value.findIndex(a => a.isShen) === activities.value.length - 1;
    updateApproveNode({ ...currentActivity, isLast }).then(() => {
      proxy.$modal.msgSuccess("提交成功");
      closeDia();
    });
  };
  // å…³é—­å¼¹æ¡†
  const closeDia = () => {
    proxy.resetForm("formRef");
    dialogFormVisible.value = false;
    quotationLoading.value = false;
    currentQuotation.value = {};
    purchaseLoading.value = false;
    currentPurchase.value = {};
    emit("close");
  };
  defineExpose({
    openDialog,
  });
</script>
<style scoped>
.node-user {
  margin: 10px 0;
  font-size: 16px;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 8px;
}
.node-status {
  color: #1890ff;
  margin-left: 8px;
  font-size: 14px;
}
.node-reason {
  font-size: 15px;
  color: #333;
  margin: 10px 0;
}
.user-avatar {
    cursor: pointer;
    width: 30px;
    height: 30px;
    border-radius: 50px;
}
.signImg {
    cursor: pointer;
    width: 200px;
    height: 60px;
}
  .node-user {
    margin: 10px 0;
    font-size: 16px;
    font-weight: 600;
    display: flex;
    align-items: center;
    gap: 8px;
  }
  .node-status {
    color: #1890ff;
    margin-left: 8px;
    font-size: 14px;
  }
  .node-reason {
    font-size: 15px;
    color: #333;
    margin: 10px 0;
  }
  .user-avatar {
    cursor: pointer;
    width: 30px;
    height: 30px;
    border-radius: 50px;
  }
  .signImg {
    cursor: pointer;
    width: 200px;
    height: 60px;
  }
</style>
src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue
@@ -1,99 +1,103 @@
<template>
  <div>
    <el-dialog
        v-model="dialogFormVisible"
        :title="operationType === 'add' ? '新增审批流程' : '编辑审批流程'"
        width="50%"
        @close="closeDia"
    >
      <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
    <el-dialog v-model="dialogFormVisible"
               :title="operationType === 'add' ? '新增审批流程' : '编辑审批流程'"
               width="50%"
               @close="closeDia">
      <el-form :model="form"
               label-width="140px"
               label-position="top"
               :rules="rules"
               ref="formRef">
        <el-row>
          <el-col :span="24">
            <el-form-item label="流程编号:" prop="approveId">
              <el-input v-model="form.approveId" placeholder="自动编号" clearable disabled/>
            <el-form-item label="流程编号:"
                          prop="approveId">
              <el-input v-model="form.approveId"
                        placeholder="自动编号"
                        clearable
                        disabled />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="申请部门:" prop="approveDeptName">
<!--              <el-input v-model="form.approveDeptName" placeholder="请输入" clearable/>-->
                            <el-select
                                v-model="form.approveDeptId"
                                placeholder="选择部门"
                @change="handleDeptChange"
                            >
                                <el-option
                                    v-for="user in productOptions"
                                    :key="user.deptId"
                                    :label="user.deptName"
                                    :value="user.deptId"
                                />
                            </el-select>
            <el-form-item label="申请部门:"
                          prop="approveDeptName">
              <!--              <el-input v-model="form.approveDeptName" placeholder="请输入" clearable/>-->
              <el-select v-model="form.approveDeptId"
                         placeholder="选择部门"
                         @change="handleDeptChange">
                <el-option v-for="user in productOptions"
                           :key="user.deptId"
                           :label="user.deptName"
                           :value="user.deptId" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item :label="props.approveType == 5 ? '采购合同号:' : '审批事由:'" prop="approveReason">
              <el-input v-model="form.approveReason" placeholder="请输入" clearable type="textarea" />
            <el-form-item :label="props.approveType == 5 ? '采购合同号:' : '审批事由:'"
                          prop="approveReason">
              <el-input v-model="form.approveReason"
                        placeholder="请输入"
                        clearable
                        type="textarea" />
            </el-form-item>
          </el-col>
        </el-row>
        <!-- è¯·å‡æ—¶é—´ï¼ˆä»…当 approveType ä¸º 2 æ—¶æ˜¾ç¤ºï¼‰ -->
        <el-row :gutter="30" v-if="props.approveType == 2">
        <el-row :gutter="30"
                v-if="props.approveType == 2">
          <el-col :span="12">
            <el-form-item label="请假开始时间:" prop="startDate">
              <el-date-picker
                  v-model="form.startDate"
                  type="date"
                  placeholder="请选择开始日期"
                  value-format="YYYY-MM-DD"
                  format="YYYY-MM-DD"
                  clearable
                  style="width: 100%"
              />
            <el-form-item label="请假开始时间:"
                          prop="startDate">
              <el-date-picker v-model="form.startDate"
                              type="date"
                              placeholder="请选择开始日期"
                              value-format="YYYY-MM-DD"
                              format="YYYY-MM-DD"
                              clearable
                              style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="请假结束时间:" prop="endDate">
              <el-date-picker
                  v-model="form.endDate"
                  type="date"
                  placeholder="请选择结束日期"
                  value-format="YYYY-MM-DD"
                  format="YYYY-MM-DD"
                  clearable
                  style="width: 100%"
              />
            <el-form-item label="请假结束时间:"
                          prop="endDate">
              <el-date-picker v-model="form.endDate"
                              type="date"
                              placeholder="请选择结束日期"
                              value-format="YYYY-MM-DD"
                              format="YYYY-MM-DD"
                              clearable
                              style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <!-- æŠ¥é”€é‡‘额(仅当 approveType ä¸º 4 æ—¶æ˜¾ç¤ºï¼‰ -->
        <el-row v-if="props.approveType == 4">
          <el-col :span="24">
            <el-form-item label="报销金额:" prop="price">
              <el-input-number
                  v-model="form.price"
                  placeholder="请输入报销金额"
                  :min="0"
                  :precision="2"
                  :step="0.01"
                  style="width: 100%"
                  clearable
              />
            <el-form-item label="报销金额:"
                          prop="price">
              <el-input-number v-model="form.price"
                               placeholder="请输入报销金额"
                               :min="0"
                               :precision="2"
                               :step="0.01"
                               style="width: 100%"
                               clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <!-- å‡ºå·®åœ°ç‚¹ï¼ˆä»…当 approveType ä¸º 3 æ—¶æ˜¾ç¤ºï¼‰ -->
        <el-row v-if="props.approveType == 3">
          <el-col :span="24">
            <el-form-item label="出差地点:" prop="location">
              <el-input
                  v-model="form.location"
                  placeholder="请输入出差地点"
                  clearable
              />
            <el-form-item label="出差地点:"
                          prop="location">
              <el-input v-model="form.location"
                        placeholder="请输入出差地点"
                        clearable />
            </el-form-item>
          </el-col>
        </el-row>
@@ -103,37 +107,31 @@
            <el-form-item>
              <template #label>
                <span>审批人选择:</span>
                <el-button type="primary" @click="addApproverNode" style="margin-left: 8px;">新增节点</el-button>
                <el-button type="primary"
                           @click="addApproverNode"
                           style="margin-left: 8px;">新增节点</el-button>
              </template>
              <div style="display: flex; align-items: flex-end; flex-wrap: wrap;">
                <div
                  v-for="(node, index) in approverNodes"
                  :key="node.id"
                  style="margin-right: 30px; text-align: center; margin-bottom: 10px;"
                >
                <div v-for="(node, index) in approverNodes"
                     :key="node.id"
                     style="margin-right: 30px; text-align: center; margin-bottom: 10px;">
                  <div>
                    <span>审批人</span>
                    â†’
                  </div>
                  <el-select
                    v-model="node.userId"
                    placeholder="选择人员"
                    style="width: 120px; margin-bottom: 8px;"
                  >
                    <el-option
                      v-for="user in userList"
                      :key="user.userId"
                      :label="user.nickName"
                      :value="user.userId"
                    />
                  <el-select v-model="node.userId"
                             placeholder="选择人员"
                             style="width: 120px; margin-bottom: 8px;">
                    <el-option v-for="user in userListApproval"
                               :key="user.userId"
                               :label="user.userName"
                               :value="user.userId" />
                  </el-select>
                  <div>
                    <el-button
                      type="danger"
                      size="small"
                      @click="removeApproverNode(index)"
                      v-if="approverNodes.length > 1"
                    >删除</el-button>
                    <el-button type="danger"
                               size="small"
                               @click="removeApproverNode(index)"
                               v-if="approverNodes.length > 1">删除</el-button>
                  </div>
                </div>
              </div>
@@ -142,45 +140,51 @@
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="申请人:" prop="approveUser">
                            <el-select
                                v-model="form.approveUser"
                                placeholder="选择人员"
                filterable
                default-first-option
                :reserve-keyword="false"
                            >
                                <el-option
                                    v-for="user in userList"
                                    :key="user.userId"
                                    :label="user.nickName"
                                    :value="user.userId"
                                />
                            </el-select>
            <el-form-item label="申请人:"
                          prop="approveUser">
              <el-select v-model="form.approveUser"
                         placeholder="选择人员"
                         filterable
                         default-first-option
                         :reserve-keyword="false">
                <el-option v-for="user in userList"
                           :key="user.userId"
                           :label="user.nickName"
                           :value="user.userId" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="申请日期:" prop="approveTime">
              <el-date-picker
                  v-model="form.approveTime"
                  type="date"
                  placeholder="请选择日期"
                  value-format="YYYY-MM-DD"
                  format="YYYY-MM-DD"
                  clearable
                  style="width: 100%"
              />
            <el-form-item label="申请日期:"
                          prop="approveTime">
              <el-date-picker v-model="form.approveTime"
                              type="date"
                              placeholder="请选择日期"
                              value-format="YYYY-MM-DD"
                              format="YYYY-MM-DD"
                              clearable
                              style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="附件材料:" prop="remark">
              <el-upload v-model:file-list="fileList" :action="upload.url" multiple ref="fileUpload" auto-upload
                         :headers="upload.headers" :before-upload="handleBeforeUpload" :on-error="handleUploadError"
                         :on-success="handleUploadSuccess" :on-remove="handleRemove">
                <el-button type="primary" v-if="operationType !== 'view'">上传</el-button>
                <template #tip v-if="operationType !== 'view'">
            <el-form-item label="附件材料:"
                          prop="remark">
              <el-upload v-model:file-list="fileList"
                         :action="upload.url"
                         multiple
                         ref="fileUpload"
                         auto-upload
                         :headers="upload.headers"
                         :before-upload="handleBeforeUpload"
                         :on-error="handleUploadError"
                         :on-success="handleUploadSuccess"
                         :on-remove="handleRemove">
                <el-button type="primary"
                           v-if="operationType !== 'view'">上传</el-button>
                <template #tip
                          v-if="operationType !== 'view'">
                  <div class="el-upload__tip">
                    æ–‡ä»¶æ ¼å¼æ”¯æŒ
                    doc,docx,xls,xlsx,ppt,pptx,pdf,txt,xml,jpg,jpeg,png,gif,bmp,rar,zip,7z
@@ -193,7 +197,8 @@
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button type="primary"
                     @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
@@ -202,275 +207,286 @@
</template>
<script setup>
import {ref, reactive, toRefs, getCurrentInstance} from "vue";
import {
  approveProcessAdd, approveProcessGetInfo,
  approveProcessUpdate,
  getDept
} from "@/api/collaborativeApproval/approvalProcess.js";
import {
  delLedgerFile,
} from "@/api/salesManagement/salesLedger.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import { getToken } from "@/utils/auth";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
import useUserStore from "@/store/modules/user";
import { getCurrentDate } from "@/utils/index.js";
import log from "@/views/monitor/job/log.vue";
const userStore = useUserStore();
  import { ref, reactive, toRefs, getCurrentInstance } from "vue";
  import {
    approveProcessAdd,
    approveProcessGetInfo,
    approveProcessUpdate,
    getDept,
  } from "@/api/collaborativeApproval/approvalProcess.js";
  import { delLedgerFile } from "@/api/salesManagement/salesLedger.js";
  import { userListNoPageByTenantId } from "@/api/system/user.js";
  import { approveUserList } from "@/api/collaborativeApproval/approvalProcess.js";
  import { getToken } from "@/utils/auth";
  const { proxy } = getCurrentInstance();
  const emit = defineEmits(["close"]);
  import useUserStore from "@/store/modules/user";
  import { getCurrentDate } from "@/utils/index.js";
  import log from "@/views/monitor/job/log.vue";
  const userStore = useUserStore();
const dialogFormVisible = ref(false);
const operationType = ref('')
const fileList = ref([]);
const upload = reactive({
  // ä¸Šä¼ çš„地址
  url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
  // è®¾ç½®ä¸Šä¼ çš„请求头部
  headers: { Authorization: "Bearer " + getToken() },
});
const data = reactive({
  form: {
    approveTime: "",
    approveId: "",
    approveUser: "",
        approveDeptId: "",
    approveDeptName: "",
    approveReason: "",
    checkResult: "",
    tempFileIds: [],
    approverList: [], // æ–°å¢žå­—段,存储所有节点的审批人id
    startDate: "", // è¯·å‡å¼€å§‹æ—¶é—´
    endDate: "", // è¯·å‡ç»“束时间
    price: null, // æŠ¥é”€é‡‘额
    location: "" // å‡ºå·®åœ°ç‚¹
  },
  rules: {
    approveTime: [{ required: false, message: "请输入", trigger: "change" },],
    approveId: [{ required: false, message: "请输入", trigger: "blur" }],
    approveUser: [{ required: false, message: "请输入", trigger: "blur" }],
    approveDeptName: [{ required: true, message: "请输入", trigger: "blur" }],
    approveReason: [{ required: true, message: "请输入", trigger: "blur" }],
    checkResult: [{ required: false, message: "请输入", trigger: "blur" }],
    startDate: [{ required: true, message: "请选择请假开始时间", trigger: "change" }],
    endDate: [{ required: true, message: "请选择请假结束时间", trigger: "change" }],
    price: [{ required: true, message: "请输入报销金额", trigger: "blur" }],
    location: [{ required: true, message: "请输入出差地点", trigger: "blur" }],
  },
});
const { form, rules } = toRefs(data);
const productOptions = ref([]);
const currentApproveStatus = ref(0)
const props = defineProps({
  approveType: {
    type: [Number, String],
    default: 0
  }
})
// å®¡æ‰¹äººèŠ‚ç‚¹ç›¸å…³
const approverNodes = ref([
  { id: 1, userId: null }
])
let nextApproverId = 2
const userList = ref([])
function addApproverNode() {
  approverNodes.value.push({ id: nextApproverId++, userId: null })
}
function removeApproverNode(index) {
  approverNodes.value.splice(index, 1)
}
// å¤„理部门选择变化
const handleDeptChange = (deptId) => {
  if (deptId) {
    const selectedDept = productOptions.value.find(dept => dept.deptId === deptId);
    if (selectedDept) {
      form.value.approveDeptName = selectedDept.deptName;
    }
  } else {
    form.value.approveDeptName = '';
  }
};
// æ‰“开弹框
const openDialog = (type, row) => {
  operationType.value = type;
  dialogFormVisible.value = true;
    userListNoPageByTenantId().then((res) => {
    userList.value = res.data;
  const dialogFormVisible = ref(false);
  const operationType = ref("");
  const fileList = ref([]);
  const upload = reactive({
    // ä¸Šä¼ çš„地址
    url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
    // è®¾ç½®ä¸Šä¼ çš„请求头部
    headers: { Authorization: "Bearer " + getToken() },
  });
    form.value = {}
    approverNodes.value = [
        { id: 1, userId: null }
    ]
  form.value.approveUser = userStore.id;
  form.value.approveTime = getCurrentDate();
  // èŽ·å–å½“å‰ç”¨æˆ·ä¿¡æ¯å¹¶è®¾ç½®éƒ¨é—¨ID
  form.value.approveDeptId = userStore.currentDeptId
  // åŠ è½½éƒ¨é—¨é€‰é¡¹ï¼Œå¹¶åœ¨åŠ è½½å®ŒæˆåŽè®¾ç½®éƒ¨é—¨åç§°
  getProductOptions();
  if (operationType.value === 'edit') {
    fileList.value = row.commonFileList
    form.value.tempFileIds = fileList.value.map(file => file.id)
        currentApproveStatus.value = row.approveStatus
    approveProcessGetInfo({id: row.approveId,approveReason: '1'}).then(res => {
            form.value = {...res.data}
      // åæ˜¾å®¡æ‰¹äºº
      if (res.data && res.data.approveUserIds) {
        const userIds = res.data.approveUserIds.split(',')
        approverNodes.value = userIds.map((userId, idx) => ({
          id: idx + 1,
          userId: parseInt(userId.trim())
        }))
        nextApproverId = userIds.length + 1
      } else {
        approverNodes.value = [{ id: 1, userId: null }]
        nextApproverId = 2
      }
    })
  const data = reactive({
    form: {
      approveTime: "",
      approveId: "",
      approveUser: "",
      approveDeptId: "",
      approveDeptName: "",
      approveReason: "",
      checkResult: "",
      tempFileIds: [],
      approverList: [], // æ–°å¢žå­—段,存储所有节点的审批人id
      startDate: "", // è¯·å‡å¼€å§‹æ—¶é—´
      endDate: "", // è¯·å‡ç»“束时间
      price: null, // æŠ¥é”€é‡‘额
      location: "", // å‡ºå·®åœ°ç‚¹
    },
    rules: {
      approveTime: [{ required: false, message: "请输入", trigger: "change" }],
      approveId: [{ required: false, message: "请输入", trigger: "blur" }],
      approveUser: [{ required: false, message: "请输入", trigger: "blur" }],
      approveDeptName: [{ required: true, message: "请输入", trigger: "blur" }],
      approveReason: [{ required: true, message: "请输入", trigger: "blur" }],
      checkResult: [{ required: false, message: "请输入", trigger: "blur" }],
      startDate: [
        { required: true, message: "请选择请假开始时间", trigger: "change" },
      ],
      endDate: [
        { required: true, message: "请选择请假结束时间", trigger: "change" },
      ],
      price: [{ required: true, message: "请输入报销金额", trigger: "blur" }],
      location: [{ required: true, message: "请输入出差地点", trigger: "blur" }],
    },
  });
  const { form, rules } = toRefs(data);
  const productOptions = ref([]);
  const currentApproveStatus = ref(0);
  const props = defineProps({
    approveType: {
      type: [Number, String],
      default: 0,
    },
  });
  // å®¡æ‰¹äººèŠ‚ç‚¹ç›¸å…³
  const approverNodes = ref([{ id: 1, userId: null }]);
  let nextApproverId = 2;
  const userList = ref([]);
  const userListApproval = ref([]);
  function addApproverNode() {
    approverNodes.value.push({ id: nextApproverId++, userId: null });
  }
}
const getProductOptions = () => {
  return getDept().then((res) => {
    productOptions.value = res.data;
    // å¦‚果已有部门ID,自动设置部门名称(用于验证)
    if (form.value.approveDeptId && productOptions.value.length > 0) {
      const matchedDept = productOptions.value.find(dept =>
        dept.deptId == form.value.approveDeptId ||
        String(dept.deptId) === String(form.value.approveDeptId)
  function removeApproverNode(index) {
    approverNodes.value.splice(index, 1);
  }
  // å¤„理部门选择变化
  const handleDeptChange = deptId => {
    if (deptId) {
      const selectedDept = productOptions.value.find(
        dept => dept.deptId === deptId
      );
      if (matchedDept) {
        form.value.approveDeptName = matchedDept.deptName;
      if (selectedDept) {
        form.value.approveDeptName = selectedDept.deptName;
      }
    } else {
      form.value.approveDeptName = "";
    }
  });
};
function convertIdToValue(data) {
  return data.map((item) => {
    const { id, children, ...rest } = item;
    const newItem = {
      ...rest,
      value: id, // å°† id æ”¹ä¸º value
    };
    if (children && children.length > 0) {
      newItem.children = convertIdToValue(children);
    }
    return newItem;
  });
}
// æäº¤äº§å“è¡¨å•
const submitForm = () => {
  // æ”¶é›†æ‰€æœ‰èŠ‚ç‚¹çš„å®¡æ‰¹äººid
  form.value.approveUserIds = approverNodes.value.map(node => node.userId).join(',')
  form.value.approveType = props.approveType
  // å®¡æ‰¹äººå¿…填校验
  const hasEmptyApprover = approverNodes.value.some(node => !node.userId)
  if (hasEmptyApprover) {
    proxy.$modal.msgError("请为所有审批节点选择审批人!")
    return
  }
  // å½“ approveType ä¸º 2 æ—¶ï¼Œæ ¡éªŒè¯·å‡æ—¶é—´
  if (props.approveType == 2) {
    if (!form.value.startDate) {
      proxy.$modal.msgError("请选择请假开始时间!")
      return
    }
    if (!form.value.endDate) {
      proxy.$modal.msgError("请选择请假结束时间!")
      return
    }
    // æ ¡éªŒç»“束时间不能早于开始时间
    if (new Date(form.value.endDate) < new Date(form.value.startDate)) {
      proxy.$modal.msgError("请假结束时间不能早于开始时间!")
      return
    }
  }
  // å½“ approveType ä¸º 3 æ—¶ï¼Œæ ¡éªŒå‡ºå·®åœ°ç‚¹
  if (props.approveType == 3) {
    if (!form.value.location || form.value.location.trim() === '') {
      proxy.$modal.msgError("请输入出差地点!")
      return
    }
  }
  // å½“ approveType ä¸º 4 æ—¶ï¼Œæ ¡éªŒæŠ¥é”€é‡‘额
  if (props.approveType == 4) {
    if (!form.value.price || form.value.price <= 0) {
      proxy.$modal.msgError("请输入有效的报销金额!")
      return
    }
  }
  proxy.$refs.formRef.validate(valid => {
    if (valid) {
      if (operationType.value === "add" || currentApproveStatus.value == 3) {
        approveProcessAdd(form.value).then(res => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
        })
      } else {
        approveProcessUpdate(form.value).then(res => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
        })
      }
    }
  })
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  fileList.value = []
  proxy.resetForm("formRef");
  dialogFormVisible.value = false;
  emit('close')
};
  };
  // æ‰“开弹框
  const openDialog = (type, row) => {
    operationType.value = type;
    dialogFormVisible.value = true;
    userListNoPageByTenantId().then(res => {
      userList.value = res.data;
    });
    approveUserList({ approveType: props.approveType }).then(res => {
      userListApproval.value = res.data;
    });
    form.value = {};
    approverNodes.value = [{ id: 1, userId: null }];
    form.value.approveUser = userStore.id;
    form.value.approveTime = getCurrentDate();
// ä¸Šä¼ å‰æ ¡æ£€
function handleBeforeUpload(file) {
  // æ ¡æ£€æ–‡ä»¶å¤§å°
  // if (file.size > 1024 * 1024 * 10) {
  //   proxy.$modal.msgError("上传文件大小不能超过10MB!");
  //   return false;
  // }
  proxy.$modal.loading("正在上传文件,请稍候...");
  return true;
}
// ä¸Šä¼ å¤±è´¥
function handleUploadError(err) {
  proxy.$modal.msgError("上传文件失败");
  proxy.$modal.closeLoading();
}
// ä¸Šä¼ æˆåŠŸå›žè°ƒ
function handleUploadSuccess(res, file, uploadFiles) {
  proxy.$modal.closeLoading();
  if (res.code === 200) {
    // ç¡®ä¿ tempFileIds å­˜åœ¨ä¸”为数组
    if (!form.value.tempFileIds) {
      form.value.tempFileIds = [];
    // èŽ·å–å½“å‰ç”¨æˆ·ä¿¡æ¯å¹¶è®¾ç½®éƒ¨é—¨ID
    form.value.approveDeptId = userStore.currentDeptId;
    // åŠ è½½éƒ¨é—¨é€‰é¡¹ï¼Œå¹¶åœ¨åŠ è½½å®ŒæˆåŽè®¾ç½®éƒ¨é—¨åç§°
    getProductOptions();
    if (operationType.value === "edit") {
      fileList.value = row.commonFileList;
      form.value.tempFileIds = fileList.value.map(file => file.id);
      currentApproveStatus.value = row.approveStatus;
      approveProcessGetInfo({ id: row.approveId, approveReason: "1" }).then(
        res => {
          form.value = { ...res.data };
          // åæ˜¾å®¡æ‰¹äºº
          if (res.data && res.data.approveUserIds) {
            const userIds = res.data.approveUserIds.split(",");
            approverNodes.value = userIds.map((userId, idx) => ({
              id: idx + 1,
              userId: parseInt(userId.trim()),
            }));
            nextApproverId = userIds.length + 1;
          } else {
            approverNodes.value = [{ id: 1, userId: null }];
            nextApproverId = 2;
          }
        }
      );
    }
    form.value.tempFileIds.push(res.data.tempId);
    proxy.$modal.msgSuccess("上传成功");
  } else {
    proxy.$modal.msgError(res.msg);
    proxy.$refs.fileUpload.handleRemove(file);
  }
}
// ç§»é™¤æ–‡ä»¶
function handleRemove(file) {
  if (operationType.value === "edit") {
    let ids = [];
    ids.push(file.id);
    delLedgerFile(ids).then((res) => {
      proxy.$modal.msgSuccess("删除成功");
  };
  const getProductOptions = () => {
    return getDept().then(res => {
      productOptions.value = res.data;
      // å¦‚果已有部门ID,自动设置部门名称(用于验证)
      if (form.value.approveDeptId && productOptions.value.length > 0) {
        const matchedDept = productOptions.value.find(
          dept =>
            dept.deptId == form.value.approveDeptId ||
            String(dept.deptId) === String(form.value.approveDeptId)
        );
        if (matchedDept) {
          form.value.approveDeptName = matchedDept.deptName;
        }
      }
    });
  };
  function convertIdToValue(data) {
    return data.map(item => {
      const { id, children, ...rest } = item;
      const newItem = {
        ...rest,
        value: id, // å°† id æ”¹ä¸º value
      };
      if (children && children.length > 0) {
        newItem.children = convertIdToValue(children);
      }
      return newItem;
    });
  }
}
  // æäº¤äº§å“è¡¨å•
  const submitForm = () => {
    // æ”¶é›†æ‰€æœ‰èŠ‚ç‚¹çš„å®¡æ‰¹äººid
    form.value.approveUserIds = approverNodes.value
      .map(node => node.userId)
      .join(",");
    form.value.approveType = props.approveType;
    // å®¡æ‰¹äººå¿…填校验
    const hasEmptyApprover = approverNodes.value.some(node => !node.userId);
    if (hasEmptyApprover) {
      proxy.$modal.msgError("请为所有审批节点选择审批人!");
      return;
    }
    // å½“ approveType ä¸º 2 æ—¶ï¼Œæ ¡éªŒè¯·å‡æ—¶é—´
    if (props.approveType == 2) {
      if (!form.value.startDate) {
        proxy.$modal.msgError("请选择请假开始时间!");
        return;
      }
      if (!form.value.endDate) {
        proxy.$modal.msgError("请选择请假结束时间!");
        return;
      }
      // æ ¡éªŒç»“束时间不能早于开始时间
      if (new Date(form.value.endDate) < new Date(form.value.startDate)) {
        proxy.$modal.msgError("请假结束时间不能早于开始时间!");
        return;
      }
    }
    // å½“ approveType ä¸º 3 æ—¶ï¼Œæ ¡éªŒå‡ºå·®åœ°ç‚¹
    if (props.approveType == 3) {
      if (!form.value.location || form.value.location.trim() === "") {
        proxy.$modal.msgError("请输入出差地点!");
        return;
      }
    }
    // å½“ approveType ä¸º 4 æ—¶ï¼Œæ ¡éªŒæŠ¥é”€é‡‘额
    if (props.approveType == 4) {
      if (!form.value.price || form.value.price <= 0) {
        proxy.$modal.msgError("请输入有效的报销金额!");
        return;
      }
    }
    proxy.$refs.formRef.validate(valid => {
      if (valid) {
        if (operationType.value === "add" || currentApproveStatus.value == 3) {
          approveProcessAdd(form.value).then(res => {
            proxy.$modal.msgSuccess("提交成功");
            closeDia();
          });
        } else {
          approveProcessUpdate(form.value).then(res => {
            proxy.$modal.msgSuccess("提交成功");
            closeDia();
          });
        }
      }
    });
  };
  // å…³é—­å¼¹æ¡†
  const closeDia = () => {
    fileList.value = [];
    proxy.resetForm("formRef");
    dialogFormVisible.value = false;
    emit("close");
  };
defineExpose({
  openDialog,
});
  // ä¸Šä¼ å‰æ ¡æ£€
  function handleBeforeUpload(file) {
    // æ ¡æ£€æ–‡ä»¶å¤§å°
    // if (file.size > 1024 * 1024 * 10) {
    //   proxy.$modal.msgError("上传文件大小不能超过10MB!");
    //   return false;
    // }
    proxy.$modal.loading("正在上传文件,请稍候...");
    return true;
  }
  // ä¸Šä¼ å¤±è´¥
  function handleUploadError(err) {
    proxy.$modal.msgError("上传文件失败");
    proxy.$modal.closeLoading();
  }
  // ä¸Šä¼ æˆåŠŸå›žè°ƒ
  function handleUploadSuccess(res, file, uploadFiles) {
    proxy.$modal.closeLoading();
    if (res.code === 200) {
      // ç¡®ä¿ tempFileIds å­˜åœ¨ä¸”为数组
      if (!form.value.tempFileIds) {
        form.value.tempFileIds = [];
      }
      form.value.tempFileIds.push(res.data.tempId);
      proxy.$modal.msgSuccess("上传成功");
    } else {
      proxy.$modal.msgError(res.msg);
      proxy.$refs.fileUpload.handleRemove(file);
    }
  }
  // ç§»é™¤æ–‡ä»¶
  function handleRemove(file) {
    if (operationType.value === "edit") {
      let ids = [];
      ids.push(file.id);
      delLedgerFile(ids).then(res => {
        proxy.$modal.msgSuccess("删除成功");
      });
    }
  }
  defineExpose({
    openDialog,
  });
</script>
<style scoped>
</style>
src/views/collaborativeApproval/approvalProcess/index.vue
@@ -1,364 +1,589 @@
<template>
  <div class="app-container">
    <!-- æ ‡ç­¾é¡µåˆ‡æ¢ä¸åŒçš„审批类型 -->
    <el-tabs v-model="activeTab" @tab-change="handleTabChange" class="approval-tabs">
      <el-tab-pane label="公出管理" name="1"></el-tab-pane>
      <el-tab-pane label="请假管理" name="2"></el-tab-pane>
      <el-tab-pane label="出差管理" name="3"></el-tab-pane>
      <el-tab-pane label="报销管理" name="4"></el-tab-pane>
      <el-tab-pane label="采购审批" name="5"></el-tab-pane>
      <el-tab-pane label="报价审批" name="6"></el-tab-pane>
      <el-tab-pane label="发货审批" name="7"></el-tab-pane>
    <el-tabs v-model="activeTab"
             @tab-change="handleTabChange"
             class="approval-tabs">
      <el-tab-pane label="公出管理"
                   name="1"></el-tab-pane>
      <el-tab-pane label="请假管理"
                   name="2"></el-tab-pane>
      <el-tab-pane label="出差管理"
                   name="3"></el-tab-pane>
      <el-tab-pane label="报销管理"
                   name="4"></el-tab-pane>
      <el-tab-pane label="采购审批"
                   name="5"></el-tab-pane>
      <el-tab-pane label="报价审批"
                   name="6"></el-tab-pane>
      <el-tab-pane label="发货审批"
                   name="7"></el-tab-pane>
    </el-tabs>
    <div class="search_form">
      <div>
        <span class="search_title">流程编号:</span>
        <el-input
            v-model="searchForm.approveId"
            style="width: 240px"
            placeholder="请输入流程编号搜索"
            @change="handleQuery"
            clearable
            :prefix-icon="Search"
        />
        <el-input v-model="searchForm.approveId"
                  style="width: 240px"
                  placeholder="请输入流程编号搜索"
                  @change="handleQuery"
                  clearable
                  :prefix-icon="Search" />
        <span class="search_title ml10">审批状态:</span>
                <el-select v-model="searchForm.approveStatus" clearable @change="handleQuery" style="width: 240px">
                    <el-option label="待审核" :value="0" />
                    <el-option label="审核中" :value="1" />
                    <el-option label="审核完成" :value="2" />
                    <el-option label="审核未通过" :value="3" />
                    <el-option label="已重新提交" :value="4" />
                </el-select>
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
        >搜索</el-button
        >
        <el-select v-model="searchForm.approveStatus"
                   clearable
                   @change="handleQuery"
                   style="width: 240px">
          <el-option label="待审核"
                     :value="0" />
          <el-option label="审核中"
                     :value="1" />
          <el-option label="审核完成"
                     :value="2" />
          <el-option label="审核未通过"
                     :value="3" />
          <el-option label="已重新提交"
                     :value="4" />
        </el-select>
        <el-button type="primary"
                   @click="handleQuery"
                   style="margin-left: 10px">搜索</el-button>
      </div>
      <div>
        <el-button
          type="primary"
          @click="openForm('add')"
          v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7"
        >新增</el-button>
        <el-button @click="handleOut">导出</el-button>
        <el-button
          type="danger"
          plain
          @click="handleDelete"
          v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7"
        >删除</el-button>
        <el-button @click="handleOut">审批人维护</el-button>
        <el-button type="primary"
                   @click="openForm('add')"
                   v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7">新增</el-button>
        <el-button @click="handleExport">导出</el-button>
        <el-button type="danger"
                   plain
                   @click="handleDelete"
                   v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7">删除</el-button>
      </div>
    </div>
    <div class="table_list">
      <PIMTable
          rowKey="id"
          :column="tableColumnCopy"
          :tableData="tableData"
          :page="page"
          :isSelection="true"
          @selection-change="handleSelectionChange"
          :tableLoading="tableLoading"
          @pagination="pagination"
          :total="page.total"
      ></PIMTable>
      <PIMTable rowKey="id"
                :column="tableColumnCopy"
                :tableData="tableData"
                :page="page"
                :isSelection="true"
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
                :total="page.total"></PIMTable>
    </div>
    <info-form-dia ref="infoFormDia" @close="handleQuery" :approveType="currentApproveType"></info-form-dia>
    <approval-dia ref="approvalDia" @close="handleQuery" :approveType="currentApproveType"></approval-dia>
    <info-form-dia ref="infoFormDia"
                   @close="handleQuery"
                   :approveType="currentApproveType"></info-form-dia>
    <approval-dia ref="approvalDia"
                  @close="handleQuery"
                  :approveType="currentApproveType"></approval-dia>
    <FileList ref="fileListRef" />
    <!-- å®¡æ‰¹äººç»´æŠ¤å¯¹è¯æ¡† -->
    <el-dialog v-model="approverDialogVisible"
               title="审批人维护"
               width="800px">
      <div class="approver-dialog">
        <div class="selected-info"
             v-if="selectedApprovers.length > 0">
          <div class="info-title">已选择的审批人:</div>
          <div class="selected-list">
            <el-tag v-for="approver in selectedApprovers"
                    :key="approver.id"
                    class="approver-tag">
              {{ approver.userName }}
              <el-icon class="el-tag__close el-icon--close"
                       @click="removeApprover(approver)">
                <CircleClose />
              </el-icon>
            </el-tag>
          </div>
        </div>
        <el-table ref="approverTable"
                  :data="approverList"
                  style="width: 100%"
                  @selection-change="handleApproverSelectionChange"
                  v-loading="approverLoading">
          <el-table-column type="selection"
                           width="55"></el-table-column>
          <el-table-column prop="userId"
                           label="ID"></el-table-column>
          <el-table-column prop="userName"
                           label="姓名"></el-table-column>
          <el-table-column prop="createTime"
                           label="创建时间"></el-table-column>
        </el-table>
      </div>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="approverDialogVisible = false">取消</el-button>
          <el-button type="primary"
                     @click="submitApprovers"
                     :disabled="selectedApprovers.length === 0">
            æäº¤
          </el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import FileList from "./fileList.vue";
import { Search } from "@element-plus/icons-vue";
import {onMounted, ref, computed, reactive, toRefs, nextTick, getCurrentInstance} from "vue";
import {ElMessageBox} from "element-plus";
import { useRoute } from 'vue-router';
import InfoFormDia from "@/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue";
import ApprovalDia from "@/views/collaborativeApproval/approvalProcess/components/approvalDia.vue";
import {approveProcessDelete, approveProcessListPage} from "@/api/collaborativeApproval/approvalProcess.js";
import useUserStore from "@/store/modules/user";
  import FileList from "./fileList.vue";
  import { Search } from "@element-plus/icons-vue";
  import {
    onMounted,
    ref,
    computed,
    reactive,
    toRefs,
    nextTick,
    getCurrentInstance,
  } from "vue";
  import { ElMessageBox } from "element-plus";
  import { useRoute } from "vue-router";
  import InfoFormDia from "@/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue";
  import ApprovalDia from "@/views/collaborativeApproval/approvalProcess/components/approvalDia.vue";
  import {
    approveProcessDelete,
    approveProcessListPage,
    approveUserList,
    addApproveUser,
    deleteApproveUser,
  } from "@/api/collaborativeApproval/approvalProcess.js";
  import { userListNoPageByTenantId } from "@/api/system/user.js";
  import useUserStore from "@/store/modules/user";
const userStore = useUserStore();
const route = useRoute();
  const userStore = useUserStore();
  const route = useRoute();
// å½“前选中的标签页,默认为公出管理
const activeTab = ref('1');
  // å½“前选中的标签页,默认为公出管理
  const activeTab = ref("1");
// å½“前审批类型,根据选中的标签页计算
const currentApproveType = computed(() => {
  return Number(activeTab.value);
});
  // å½“前审批类型,根据选中的标签页计算
  const currentApproveType = computed(() => {
    return Number(activeTab.value);
  });
// æ ‡ç­¾é¡µåˆ‡æ¢å¤„理
const handleTabChange = (tabName) => {
  // åˆ‡æ¢æ ‡ç­¾é¡µæ—¶é‡ç½®æœç´¢æ¡ä»¶å’Œåˆ†é¡µï¼Œå¹¶é‡æ–°åŠ è½½æ•°æ®
  searchForm.value.approveId = '';
  searchForm.value.approveStatus = '';
  page.current = 1;
  getList();
};
  // æ ‡ç­¾é¡µåˆ‡æ¢å¤„理
  const handleTabChange = tabName => {
    // åˆ‡æ¢æ ‡ç­¾é¡µæ—¶é‡ç½®æœç´¢æ¡ä»¶å’Œåˆ†é¡µï¼Œå¹¶é‡æ–°åŠ è½½æ•°æ®
    searchForm.value.approveId = "";
    searchForm.value.approveStatus = "";
    page.current = 1;
    getList();
  };
  const data = reactive({
    searchForm: {
      approveId: "",
      approveStatus: "",
    },
  });
  const { searchForm } = toRefs(data);
const data = reactive({
  searchForm: {
        approveId: "",
        approveStatus: "",
  },
});
const { searchForm } = toRefs(data);
  // åŠ¨æ€è¡¨æ ¼åˆ—é…ç½®ï¼Œæ ¹æ®å®¡æ‰¹ç±»åž‹ç”Ÿæˆåˆ—
  const tableColumnCopy = computed(() => {
    const isLeaveType = currentApproveType.value === 2; // è¯·å‡ç®¡ç†
    const isReimburseType = currentApproveType.value === 4; // æŠ¥é”€ç®¡ç†
    const isQuotationType = currentApproveType.value === 6; // æŠ¥ä»·å®¡æ‰¹
    const isPurchaseType = currentApproveType.value === 5; // é‡‡è´­å®¡æ‰¹
// åŠ¨æ€è¡¨æ ¼åˆ—é…ç½®ï¼Œæ ¹æ®å®¡æ‰¹ç±»åž‹ç”Ÿæˆåˆ—
const tableColumnCopy = computed(() => {
  const isLeaveType = currentApproveType.value === 2; // è¯·å‡ç®¡ç†
  const isReimburseType = currentApproveType.value === 4; // æŠ¥é”€ç®¡ç†
  const isQuotationType = currentApproveType.value === 6; // æŠ¥ä»·å®¡æ‰¹
  const isPurchaseType = currentApproveType.value === 5; // é‡‡è´­å®¡æ‰¹
  // åŸºç¡€åˆ—配置
  const baseColumns = [
    {
      label: "审批状态",
      prop: "approveStatus",
      dataType: "tag",
      width: 100,
      formatData: (params) => {
        if (params == 0) {
          return "待审核";
        } else if (params == 1) {
          return "审核中";
        } else if (params == 2) {
          return "审核完成";
        } else if (params == 4) {
          return "已重新提交";
        } else {
          return '不通过';
        }
    // åŸºç¡€åˆ—配置
    const baseColumns = [
      {
        label: "审批状态",
        prop: "approveStatus",
        dataType: "tag",
        width: 100,
        formatData: params => {
          if (params == 0) {
            return "待审核";
          } else if (params == 1) {
            return "审核中";
          } else if (params == 2) {
            return "审核完成";
          } else if (params == 4) {
            return "已重新提交";
          } else {
            return "不通过";
          }
        },
        formatType: params => {
          if (params == 0) {
            return "warning";
          } else if (params == 1) {
            return "primary";
          } else if (params == 2) {
            return "success";
          } else if (params == 4) {
            return "info";
          } else {
            return "danger";
          }
        },
      },
      formatType: (params) => {
        if (params == 0) {
          return "warning";
        } else if (params == 1) {
          return "primary";
        } else if (params == 2) {
          return "success";
        } else if (params == 4) {
          return "info";
        } else {
          return 'danger';
        }
      {
        label: "流程编号",
        prop: "approveId",
        width: 170,
      },
    },
    {
      label: "流程编号",
      prop: "approveId",
      width: 170
    },
    {
      label: "申请部门",
      prop: "approveDeptName",
      width: 220
    },
    {
      label: isQuotationType ? "报价单号" : isPurchaseType ? "采购合同号" : "审批事由",
      prop: "approveReason",
    },
    {
      label: "申请人",
      prop: "approveUserName",
      width: 120
      {
        label: "申请部门",
        prop: "approveDeptName",
        width: 220,
      },
      {
        label: isQuotationType
          ? "报价单号"
          : isPurchaseType
          ? "采购合同号"
          : "审批事由",
        prop: "approveReason",
      },
      {
        label: "申请人",
        prop: "approveUserName",
        width: 120,
      },
    ];
    // é‡‘额列(仅报销管理显示)
    if (isReimburseType) {
      baseColumns.push({
        label: "金额(元)",
        prop: "price",
        width: 120,
      });
    }
  ];
  // é‡‘额列(仅报销管理显示)
  if (isReimburseType) {
    // æ—¥æœŸåˆ—(根据类型动态配置)
    baseColumns.push(
      {
        label: isLeaveType ? "开始日期" : "申请日期",
        prop: isLeaveType ? "startDate" : "approveTime",
        width: 200,
      },
      {
        label: "结束日期",
        prop: isLeaveType ? "endDate" : "approveOverTime",
        width: 120,
      }
    );
    // å½“前审批人列
    baseColumns.push({
      label: "金额(元)",
      prop: "price",
      width: 120
      label: "当前审批人",
      prop: "approveUserCurrentName",
      width: 120,
    });
  }
  // æ—¥æœŸåˆ—(根据类型动态配置)
  baseColumns.push(
    {
      label: isLeaveType ? "开始日期" : "申请日期",
      prop: isLeaveType ? "startDate" : "approveTime",
      width: 200
    },
    {
      label: "结束日期",
      prop: isLeaveType ? "endDate" : "approveOverTime",
      width: 120
    // æ“ä½œåˆ—
    const actionOperations = [
      {
        name: "编辑",
        type: "text",
        clickFun: row => {
          openForm("edit", row);
        },
        disabled: row =>
          currentApproveType.value === 5 ||
          currentApproveType.value === 6 ||
          currentApproveType.value === 7 ||
          row.approveStatus == 2 ||
          row.approveStatus == 1 ||
          row.approveStatus == 4,
      },
      {
        name: "审核",
        type: "text",
        clickFun: row => {
          openApprovalDia("approval", row);
        },
        disabled: row =>
          row.approveUserCurrentId == null ||
          row.approveStatus == 2 ||
          row.approveStatus == 3 ||
          row.approveStatus == 4 ||
          row.approveUserCurrentId !== userStore.id,
      },
      {
        name: "详情",
        type: "text",
        clickFun: row => {
          openApprovalDia("view", row);
        },
      },
    ];
    // æŠ¥ä»·å®¡æ‰¹ï¼ˆç±»åž‹ 6)不展示“附件”操作
    if (!isQuotationType) {
      actionOperations.push({
        name: "附件",
        type: "text",
        clickFun: row => {
          downLoadFile(row);
        },
      });
    }
  );
  // å½“前审批人列
  baseColumns.push({
    label: "当前审批人",
    prop: "approveUserCurrentName",
    width: 120
  });
  // æ“ä½œåˆ—
  const actionOperations = [
    {
      name: "编辑",
      type: "text",
      clickFun: (row) => {
        openForm("edit", row);
      },
      disabled: (row) =>
        currentApproveType.value === 5 ||
        currentApproveType.value === 6 ||
        currentApproveType.value === 7 ||
        row.approveStatus == 2 ||
        row.approveStatus == 1 ||
        row.approveStatus == 4
    },
    {
      name: "审核",
      type: "text",
      clickFun: (row) => {
        openApprovalDia("approval", row);
      },
      disabled: (row) =>
        row.approveUserCurrentId == null ||
        row.approveStatus == 2 ||
        row.approveStatus == 3 ||
        row.approveStatus == 4 ||
        row.approveUserCurrentId !== userStore.id
    },
    {
      name: "详情",
      type: "text",
      clickFun: (row) => {
        openApprovalDia("view", row);
      },
    },
  ];
  // æŠ¥ä»·å®¡æ‰¹ï¼ˆç±»åž‹ 6)不展示“附件”操作
  if (!isQuotationType) {
    actionOperations.push({
      name: "附件",
      type: "text",
      clickFun: (row) => {
        downLoadFile(row);
      },
    baseColumns.push({
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
      width: 230,
      operation: actionOperations,
    });
  }
  baseColumns.push({
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 230,
    operation: actionOperations,
    return baseColumns;
  });
  return baseColumns;
});
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const page = reactive({
  current: 1,
  size: 100,
  total: 0
});
const infoFormDia = ref()
const approvalDia = ref()
const { proxy } = getCurrentInstance()
  const tableData = ref([]);
  const selectedRows = ref([]);
  const tableLoading = ref(false);
  const page = reactive({
    current: 1,
    size: 100,
    total: 0,
  });
  const infoFormDia = ref();
  const approvalDia = ref();
  const { proxy } = getCurrentInstance();
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
const fileListRef = ref(null)
const downLoadFile = (row) => {
  fileListRef.value.open(row.commonFileList)
  // å®¡æ‰¹äººç»´æŠ¤å¯¹è¯æ¡†
  const approverDialogVisible = ref(false);
  const selectedApprovers = ref([]);
  const existingApprovers = ref([]); // å·²æœ‰çš„审批人列表
  const approverLoading = ref(false); // åŠ è½½çŠ¶æ€
}
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  approveProcessListPage({...page, ...searchForm.value, approveType: currentApproveType.value}).then(res => {
    tableLoading.value = false;
    tableData.value = res.data.records
    page.total = res.data.total;
  }).catch(err => {
    tableLoading.value = false;
  })
};
// å¯¼å‡º
const handleOut = () => {
  const type = currentApproveType.value
  const urlMap = {
    0: "/approveProcess/exportZero",
    1: "/approveProcess/exportOne",
    2: "/approveProcess/exportTwo",
    3: "/approveProcess/exportThree",
    4: "/approveProcess/exportFour",
    5: "/approveProcess/exportFive",
    6: "/approveProcess/exportSix",
    7: "/approveProcess/exportSeven",
  }
  const url = urlMap[type] || urlMap[0]
  const nameMap = {
    0: "协同审批管理表",
    1: "公出管理审批表",
    2: "请假管理审批表",
    3: "出差管理审批表",
    4: "报销管理审批表",
    5: "采购申请审批表",
    6: "报价审批表",
    7: "发货审批表",
  }
  const fileName = nameMap[type] || nameMap[0]
  proxy.download(url, {}, `${fileName}.xlsx`)
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
  // å®¡æ‰¹äººåˆ—表数据
  const approverList = ref([]);
  const approverTable = ref(null);
// æ‰“开新增、编辑弹框
const openForm = (type, row) => {
  nextTick(() => {
    infoFormDia.value?.openDialog(type, row)
  })
};
// æ‰“开新增检验弹框
const openApprovalDia = (type, row) => {
  nextTick(() => {
    approvalDia.value?.openDialog(type, row)
  })
};
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
  const handleQuery = () => {
    page.current = 1;
    getList();
  };
  const fileListRef = ref(null);
  const downLoadFile = row => {
    fileListRef.value.open(row.commonFileList);
  };
  const pagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
  };
  const getList = () => {
    tableLoading.value = true;
    approveProcessListPage({
      ...page,
      ...searchForm.value,
      approveType: currentApproveType.value,
    })
      .then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records;
        page.total = res.data.total;
      })
      .catch(err => {
        tableLoading.value = false;
      });
  };
  // å¯¼å‡º
  const handleExport = () => {
    const type = currentApproveType.value;
    const urlMap = {
      0: "/approveProcess/exportZero",
      1: "/approveProcess/exportOne",
      2: "/approveProcess/exportTwo",
      3: "/approveProcess/exportThree",
      4: "/approveProcess/exportFour",
      5: "/approveProcess/exportFive",
      6: "/approveProcess/exportSix",
      7: "/approveProcess/exportSeven",
    };
    const url = urlMap[type] || urlMap[0];
    const nameMap = {
      0: "协同审批管理表",
      1: "公出管理审批表",
      2: "请假管理审批表",
      3: "出差管理审批表",
      4: "报销管理审批表",
      5: "采购申请审批表",
      6: "报价审批表",
      7: "发货审批表",
    };
    const fileName = nameMap[type] || nameMap[0];
    proxy.download(url, {}, `${fileName}.xlsx`);
  };
// åˆ é™¤
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map((item) => item.approveId);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
  // å®¡æ‰¹äººç»´æŠ¤
  const handleOut = () => {
    approverLoading.value = true;
    // ä»Ž API èŽ·å–æ‰€æœ‰ç”¨æˆ·åˆ—è¡¨
    userListNoPageByTenantId()
      .then(res => {
        // è½¬æ¢ API è¿”回的数据结构为表格需要的格式
        approverList.value = res.data.map(user => ({
          userId: user.userId,
          userName: user.nickName,
          createTime: user.createTime || "",
        }));
        // èŽ·å–å½“å‰å®¡æ‰¹ç±»åž‹å·²æœ‰çš„å®¡æ‰¹äººåˆ—è¡¨
        const currentType = currentApproveType.value;
        approveUserList({ approveType: currentType })
          .then(approversRes => {
            existingApprovers.value = approversRes.data || [];
            // approverList.value = approversRes.data;
            selectedApprovers.value = existingApprovers.value;
            // approverList.value = ;
            // æ ‡è®°å·²æœ‰çš„审批人
            // approverList.value = allUsers.map(user => ({
            //   ...user,
            //   id:
            //     existingApprovers.value.find(
            //       approver => approver.userId === user.userId
            //     )?.id || 0,
            //   isExisting: existingApprovers.value.some(
            //     approver => approver.userId === user.userId
            //   ),
            // }));
            console.log(approverList.value, "==approverList.value==");
            approverDialogVisible.value = true;
            approverLoading.value = false;
            // æ›´æ–°è¡¨æ ¼å‹¾é€‰çŠ¶æ€
            nextTick(() => {
              if (approverTable.value) {
                // å…ˆæ¸…空所有勾选
                approverList.value.forEach(row => {
                  approverTable.value.toggleRowSelection(row, false);
                });
                // å†å‹¾é€‰å·²æœ‰çš„审批人
                existingApprovers.value.forEach(existingApprover => {
                  const row = approverList.value.find(
                    user => user.userId === existingApprover.userId
                  );
                  if (row) {
                    approverTable.value.toggleRowSelection(row, true);
                  }
                });
              }
            });
          })
          .catch(err => {
            console.error("获取已有审批人列表失败:", err);
            proxy.$modal.msgError("获取已有审批人列表失败");
            approverLoading.value = false;
          });
      })
      .catch(err => {
        console.error("获取用户列表失败:", err);
        proxy.$modal.msgError("获取用户列表失败");
        approverLoading.value = false;
      });
  };
  // å¤„理审批人选择
  const handleApproverSelectionChange = selection => {
    selectedApprovers.value = selection;
  };
  // ç§»é™¤å®¡æ‰¹äºº
  const removeApprover = approver => {
    selectedApprovers.value = selectedApprovers.value.filter(
      item => item.id !== approver.id
    );
    approverTable.value.toggleRowSelection(approver, false);
  };
  // æäº¤å®¡æ‰¹äºº
  const submitApprovers = () => {
    if (selectedApprovers.value.length === 0) {
      proxy.$modal.msgWarning("请选择审批人");
      return;
    }
    const currentType = currentApproveType.value;
    const selectedIds = selectedApprovers.value.map(approver => approver.userId);
    const existingIds = existingApprovers.value.map(approver => approver.userId);
    // éœ€è¦åˆ é™¤çš„审批人(原有的但未被选择的)
    const toDelete = existingApprovers.value
      .filter(approver => !selectedIds.includes(approver.userId))
      .map(approver => approver.id);
    // éœ€è¦æ·»åŠ çš„å®¡æ‰¹äººï¼ˆæ–°é€‰æ‹©çš„ä½†ä¸åœ¨çŽ°æœ‰åˆ—è¡¨ä¸­çš„ï¼‰
    const toAdd = selectedApprovers.value
      .filter(approver => !existingIds.includes(approver.userId))
      .map(approver => ({
        approveType: currentType,
        id: 0,
        userId: approver.userId,
        userName: approver.userName,
      }));
    console.log(toDelete, "==删除==");
    console.log(toAdd, "==添加==");
    // å…ˆåˆ é™¤ä¸éœ€è¦çš„审批人
    const deletePromise =
      toDelete.length > 0 ? deleteApproveUser(toDelete) : Promise.resolve();
    deletePromise
      .then(() => {
        approveProcessDelete(ids).then((res) => {
        // ç„¶åŽæ·»åŠ æ–°çš„å®¡æ‰¹äºº
        if (toAdd.length === 0) {
          return Promise.resolve();
        }
        // é€ä¸ªæ·»åŠ å®¡æ‰¹äºº
        return Promise.all(toAdd.map(user => addApproveUser(user)));
      })
      .then(() => {
        proxy.$modal.msgSuccess("审批人维护成功");
        approverDialogVisible.value = false;
        selectedApprovers.value = [];
      })
      .catch(err => {
        console.error("审批人维护失败:", err);
        proxy.$modal.msgError("审批人维护失败");
      });
  };
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = selection => {
    selectedRows.value = selection;
  };
  // æ‰“开新增、编辑弹框
  const openForm = (type, row) => {
    nextTick(() => {
      infoFormDia.value?.openDialog(type, row);
    });
  };
  // æ‰“开新增检验弹框
  const openApprovalDia = (type, row) => {
    nextTick(() => {
      approvalDia.value?.openDialog(type, row);
    });
  };
  // åˆ é™¤
  const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
      ids = selectedRows.value.map(item => item.approveId);
    } else {
      proxy.$modal.msgWarning("请选择数据");
      return;
    }
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        approveProcessDelete(ids).then(res => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        });
@@ -366,29 +591,65 @@
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
onMounted(() => {
  // æ ¹æ®URL参数设置标签页和查询条件
  const approveType = route.query.approveType;
  const approveId = route.query.approveId;
  if (approveType) {
    // è®¾ç½®æ ‡ç­¾é¡µï¼ˆapproveType å¯¹åº” activeTab çš„ name)
    activeTab.value = String(approveType);
  }
  if (approveId) {
    // è®¾ç½®æµç¨‹ç¼–号查询条件
    searchForm.value.approveId = String(approveId);
  }
  // æŸ¥è¯¢åˆ—表
  getList();
});
  };
  onMounted(() => {
    // æ ¹æ®URL参数设置标签页和查询条件
    const approveType = route.query.approveType;
    const approveId = route.query.approveId;
    if (approveType) {
      // è®¾ç½®æ ‡ç­¾é¡µï¼ˆapproveType å¯¹åº” activeTab çš„ name)
      activeTab.value = String(approveType);
    }
    if (approveId) {
      // è®¾ç½®æµç¨‹ç¼–号查询条件
      searchForm.value.approveId = String(approveId);
    }
    // æŸ¥è¯¢åˆ—表
    getList();
  });
</script>
<style scoped>
.approval-tabs {
  margin-bottom: 10px;
}
  .approval-tabs {
    margin-bottom: 10px;
  }
  /* å®¡æ‰¹äººç»´æŠ¤å¯¹è¯æ¡†æ ·å¼ */
  .approver-dialog {
    display: flex;
    flex-direction: column;
    gap: 20px;
  }
  .selected-info {
    /* margin-top: 20px; */
    padding: 15px;
    background: #f5f7fa;
    border-radius: 4px;
  }
  .info-title {
    font-weight: 600;
    margin-bottom: 10px;
    color: #303133;
  }
  .selected-list {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
  }
  .approver-tag {
    margin-right: 10px;
  }
  .dialog-footer {
    width: 100%;
    display: flex;
    justify-content: flex-end;
  }
</style>
src/views/procurementManagement/procurementLedger/index.vue
@@ -53,7 +53,9 @@
      <div style="display: flex;justify-content: flex-end;margin-bottom: 20px;">
        <el-button type="primary"
                   @click="openForm('add')">新增台账</el-button>
        <el-button type="primary" plain @click="handleImport">导入</el-button>
        <el-button type="primary"
                   plain
                   @click="handleImport">导入</el-button>
        <el-button @click="handleOut">导出</el-button>
        <el-button type="danger"
                   plain
@@ -94,7 +96,6 @@
                               prop="availableQuality" />
              <el-table-column label="退货数量"
                               prop="returnQuality" />
              <el-table-column label="税率(%)"
                               prop="taxRate" />
              <el-table-column label="含税单价(元)"
@@ -119,11 +120,11 @@
                         show-overflow-tooltip />
        <el-table-column label="销售合同号"
                         prop="salesContractNo"
                          width="160"
                         width="160"
                         show-overflow-tooltip />
        <el-table-column label="供应商名称"
                         prop="supplierName"
                          width="160"
                         width="160"
                         show-overflow-tooltip />
        <el-table-column label="项目名称"
                         prop="projectName"
@@ -134,9 +135,8 @@
                         width="100"
                         show-overflow-tooltip>
          <template #default="scope">
            <el-tag
              :type="getApprovalStatusType(scope.row.approvalStatus)"
              size="small">
            <el-tag :type="getApprovalStatusType(scope.row.approvalStatus)"
                    size="small">
              {{ approvalStatusText[scope.row.approvalStatus] || '未知状态' }}
            </el-tag>
          </template>
@@ -189,12 +189,12 @@
                  @pagination="paginationChange" />
    </div>
    <FormDialog v-model="dialogFormVisible"
               :title="operationType === 'add' ? '新增采购台账页面' : '编辑采购台账页面'"
               :width="'70%'"
               :operation-type="operationType"
               @close="closeDia"
               @confirm="submitForm"
               @cancel="closeDia">
                :title="operationType === 'add' ? '新增采购台账页面' : '编辑采购台账页面'"
                :width="'70%'"
                :operation-type="operationType"
                @close="closeDia"
                @confirm="submitForm"
                @cancel="closeDia">
      <el-form :model="form"
               label-width="140px"
               label-position="top"
@@ -236,7 +236,7 @@
                <el-option v-for="item in supplierList"
                           :key="item.id"
                           :label="item.supplierName"
                                                     :value="item.id" >{{item.supplierName + '---' + item.supplierType}}</el-option>
                           :value="item.id">{{item.supplierName + '---' + item.supplierType}}</el-option>
              </el-select>
            </el-form-item>
          </el-col>
@@ -304,38 +304,33 @@
              <template #label>
                <div style="display: flex; align-items: center; justify-content: space-between; width: 100%;">
                  <span>审批人选择:</span>
                  <el-button type="primary" size="small" @click="addApproverNode" icon="Plus">新增节点</el-button>
                  <el-button type="primary"
                             size="small"
                             @click="addApproverNode"
                             icon="Plus">新增节点</el-button>
                </div>
              </template>
              <div class="approver-nodes-container">
                <div
                  v-for="(node, index) in approverNodes"
                  :key="node.id"
                  class="approver-node-item"
                >
                <div v-for="(node, index) in approverNodes"
                     :key="node.id"
                     class="approver-node-item">
                  <div class="approver-node-header">
                    <span class="approver-node-label">审批节点 {{ index + 1 }}</span>
                    <el-button
                      v-if="approverNodes.length > 1"
                      type="danger"
                      size="small"
                      text
                      @click="removeApproverNode(index)"
                      icon="Delete"
                    >删除</el-button>
                    <el-button v-if="approverNodes.length > 1"
                               type="danger"
                               size="small"
                               text
                               @click="removeApproverNode(index)"
                               icon="Delete">删除</el-button>
                  </div>
                  <el-select
                    v-model="node.userId"
                    placeholder="请选择审批人"
                    filterable
                    style="width: 100%;"
                  >
                    <el-option
                      v-for="user in userList"
                      :key="user.userId"
                      :label="user.nickName"
                      :value="user.userId"
                    />
                  <el-select v-model="node.userId"
                             placeholder="请选择审批人"
                             filterable
                             style="width: 100%;">
                    <el-option v-for="user in userListApprove"
                               :key="user.userId"
                               :label="user.userName"
                               :value="user.userId" />
                  </el-select>
                </div>
              </div>
@@ -373,11 +368,10 @@
                         :value="item.templateName">
                <div style="display: flex; justify-content: space-between; align-items: center;">
                  <span>{{ item.templateName }}</span>
                  <el-icon
                    v-if="item.id"
                    class="delete-icon"
                    @click.stop="handleDeleteTemplate(item)"
                    style="cursor: pointer; color: #f56c6c; font-size: 14px; margin-left: 8px;">
                  <el-icon v-if="item.id"
                           class="delete-icon"
                           @click.stop="handleDeleteTemplate(item)"
                           style="cursor: pointer; color: #f56c6c; font-size: 14px; margin-left: 8px;">
                    <Delete />
                  </el-icon>
                </div>
@@ -493,28 +487,24 @@
      </el-form>
    </FormDialog>
    <!-- å¯¼å…¥å¼¹çª— -->
    <FormDialog
      v-model="importUpload.open"
      :title="importUpload.title"
      :width="'600px'"
      @close="importUpload.open = false"
      @confirm="submitImportFile"
      @cancel="importUpload.open = false"
    >
      <el-upload
        ref="importUploadRef"
        :limit="1"
        accept=".xlsx,.xls"
        :action="importUpload.url"
        :headers="importUpload.headers"
        :before-upload="importUpload.beforeUpload"
        :on-success="importUpload.onSuccess"
        :on-error="importUpload.onError"
        :on-progress="importUpload.onProgress"
        :on-change="importUpload.onChange"
        :auto-upload="false"
        drag
      >
    <FormDialog v-model="importUpload.open"
                :title="importUpload.title"
                :width="'600px'"
                @close="importUpload.open = false"
                @confirm="submitImportFile"
                @cancel="importUpload.open = false">
      <el-upload ref="importUploadRef"
                 :limit="1"
                 accept=".xlsx,.xls"
                 :action="importUpload.url"
                 :headers="importUpload.headers"
                 :before-upload="importUpload.beforeUpload"
                 :on-success="importUpload.onSuccess"
                 :on-error="importUpload.onError"
                 :on-progress="importUpload.onProgress"
                 :on-change="importUpload.onChange"
                 :auto-upload="false"
                 drag>
        <i class="el-icon-upload"></i>
        <div class="el-upload__text">
          å°†æ–‡ä»¶æ‹–到此处,或<em>点击上传</em>
@@ -522,18 +512,20 @@
        <template #tip>
          <div class="el-upload__tip">
            ä»…支持 xls/xlsx,大小不超过 10MB。
            <el-button link type="primary" @click="downloadTemplate">下载导入模板</el-button>
            <el-button link
                       type="primary"
                       @click="downloadTemplate">下载导入模板</el-button>
          </div>
        </template>
      </el-upload>
    </FormDialog>
    <FormDialog v-model="productFormVisible"
               :title="productOperationType === 'add' ? '新增产品' : '编辑产品'"
               :width="'40%'"
               :operation-type="productOperationType"
               @close="closeProductDia"
               @confirm="submitProduct"
               @cancel="closeProductDia">
                :title="productOperationType === 'add' ? '新增产品' : '编辑产品'"
                :width="'40%'"
                :operation-type="productOperationType"
                @close="closeProductDia"
                @confirm="submitProduct"
                @cancel="closeProductDia">
      <el-form :model="productForm"
               label-width="140px"
               label-position="top"
@@ -694,11 +686,9 @@
        </el-row>
      </el-form>
    </FormDialog>
    <FileListDialog
      ref="fileListRef"
      v-model="fileListDialogVisible"
      title="附件列表"
    />
    <FileListDialog ref="fileListRef"
                    v-model="fileListDialogVisible"
                    title="附件列表" />
  </div>
</template>
@@ -716,8 +706,10 @@
  import { Search, Delete } from "@element-plus/icons-vue";
  import { ElMessageBox, ElMessage } from "element-plus";
  import { userListNoPage } from "@/api/system/user.js";
  import FormDialog from '@/components/Dialog/FormDialog.vue';
  import FileListDialog from '@/components/Dialog/FileListDialog.vue';
  import { approveUserList } from "@/api/collaborativeApproval/approvalProcess.js";
  import FormDialog from "@/components/Dialog/FormDialog.vue";
  import FileListDialog from "@/components/Dialog/FileListDialog.vue";
  import {
    getSalesLedgerWithProducts,
    addOrUpdateSalesLedgerProduct,
@@ -748,6 +740,7 @@
  const productSelectedRows = ref([]);
  const modelOptions = ref([]);
  const userList = ref([]);
  const userListApprove = ref([]);
  const productOptions = ref([]);
  const salesContractList = ref([]);
  const supplierList = ref([]);
@@ -770,7 +763,7 @@
  const addApproverNode = () => {
    approverNodes.value.push({ id: nextApproverId++, userId: null });
  };
  const removeApproverNode = (index) => {
  const removeApproverNode = index => {
    approverNodes.value.splice(index, 1);
  };
@@ -783,12 +776,12 @@
  };
  // èŽ·å–å®¡æ‰¹çŠ¶æ€æ ‡ç­¾ç±»åž‹
  const getApprovalStatusType = (status) => {
  const getApprovalStatusType = status => {
    const typeMap = {
      1: "info",      // å¾…审核 - ç°è‰²
      2: "warning",   // å®¡æ‰¹ä¸­ - æ©™è‰²
      3: "success",   // å®¡æ‰¹é€šè¿‡ - ç»¿è‰²
      4: "danger",    // å®¡æ‰¹å¤±è´¥ - çº¢è‰²
      1: "info", // å¾…审核 - ç°è‰²
      2: "warning", // å®¡æ‰¹ä¸­ - æ©™è‰²
      3: "success", // å®¡æ‰¹é€šè¿‡ - ç»¿è‰²
      4: "danger", // å®¡æ‰¹å¤±è´¥ - çº¢è‰²
    };
    return typeMap[status] || "";
  };
@@ -870,7 +863,8 @@
        form.value.paymentMethod = matchedTemplate.paymentMethod;
      }
      // æ¨¡æ¿æ•°æ®ä¸­çš„产品字段是 productList,需要转换为 productData
      productData.value = matchedTemplate.productList || matchedTemplate.productData || [];
      productData.value =
        matchedTemplate.productList || matchedTemplate.productData || [];
    } else {
      // æœªåŒ¹é…åˆ°å·²æœ‰æ¨¡æ¿ï¼Œè§†ä¸ºæ–°æ¨¡æ¿
      currentTemplateId.value = null;
@@ -1004,7 +998,7 @@
    url: import.meta.env.VITE_APP_BASE_API + "/purchase/ledger/import",
    headers: { Authorization: "Bearer " + getToken() },
    isUploading: false,
    beforeUpload: (file) => {
    beforeUpload: file => {
      const isExcel = file.name.endsWith(".xlsx") || file.name.endsWith(".xls");
      const isLt10M = file.size / 1024 / 1024 < 10;
      if (!isExcel) {
@@ -1053,7 +1047,11 @@
  // ä¸‹è½½å¯¼å…¥æ¨¡æ¿ï¼ˆå¦‚后端路径不同,可在此处调整)
  const downloadTemplate = () => {
    proxy.download("/purchase/ledger/exportTemplate", {}, "采购台账导入模板.xlsx");
    proxy.download(
      "/purchase/ledger/exportTemplate",
      {},
      "采购台账导入模板.xlsx"
    );
  };
  const submitImportFile = () => {
@@ -1118,8 +1116,8 @@
    // æ£€æŸ¥æ˜¯å¦æœ‰äº§å“æ•°æ®
    if (!productData.value || productData.value.length === 0) {
      ElMessage({
        message: '请先添加产品信息',
        type: 'warning',
        message: "请先添加产品信息",
        type: "warning",
      });
      return;
    }
@@ -1130,7 +1128,7 @@
        .filter(node => node.userId)
        .map(node => node.userId)
        .join(",");
      let params = {
        productData: proxy.HaveJson(productData.value),
        supplierId: form.value.supplierId,
@@ -1140,7 +1138,12 @@
        approveUserIds: approveUserIds,
        templateName: templateName.value.trim(),
      };
      console.log("template params ===>", params, "currentTemplateId:", currentTemplateId.value);
      console.log(
        "template params ===>",
        params,
        "currentTemplateId:",
        currentTemplateId.value
      );
      // å¦‚æžœ currentTemplateId æœ‰å€¼ï¼Œè¯´æ˜Žå½“前是“编辑已有模板” â†’ è°ƒç”¨æ›´æ–°æŽ¥å£
      // å¦åˆ™ä¸ºâ€œæ–°å»ºæ¨¡æ¿â€ â†’ è°ƒç”¨æ–°å¢žæŽ¥å£
@@ -1279,7 +1282,7 @@
        return;
      }
    }
    await getTemplateList();
    operationType.value = type;
    form.value = {};
@@ -1298,7 +1301,9 @@
        getSalesNo(),
        getOptions(),
      ]);
      approveUserList({ approveType: 5 }).then(res => {
        userListApprove.value = res.data;
      });
      userList.value = userRes.data || [];
      salesContractList.value = salesRes || [];
      // ä¾›åº”商过滤出isWhite=0 çš„æ•°æ®
@@ -1334,7 +1339,7 @@
            const approverIds = purchaseRes.approveUserIds.split(",");
            approverNodes.value = approverIds.map((id, index) => ({
              id: index + 1,
              userId: Number(id)
              userId: Number(id),
            }));
            nextApproverId = approverIds.length + 1;
          }
@@ -1412,8 +1417,10 @@
          proxy.$modal.msgError("请为所有审批节点选择审批人!");
          return;
        }
        const approveUserIds = approverNodes.value.map(node => node.userId).join(",");
        const approveUserIds = approverNodes.value
          .map(node => node.userId)
          .join(",");
        if (productData.value.length > 0) {
          // æ–°å¢žæ—¶ï¼Œéœ€è¦ä»Žæ¯ä¸ªäº§å“å¯¹è±¡ä¸­åˆ é™¤ id å­—段
          let processedProductData = productData.value;
@@ -1470,17 +1477,17 @@
    productForm.value = {};
    proxy.resetForm("productFormRef");
    productFormVisible.value = true;
    // å…ˆèŽ·å–äº§å“é€‰é¡¹ï¼Œç¡®ä¿æ•°æ®åŠ è½½å®Œæˆ
    await getProductOptions();
    // ç­‰å¾… DOM æ›´æ–°
    await nextTick();
    if (type === "edit") {
      // å¤åˆ¶è¡Œæ•°æ®
      productForm.value = { ...row };
      // å¦‚果是从模板加载的数据,可能没有 productId å’Œ productModelId
      // éœ€è¦æ ¹æ® productCategory å’Œ specificationModel æ¥æŸ¥æ‰¾å¯¹åº”çš„ ID
      if (!productForm.value.productId && productForm.value.productCategory) {
@@ -1491,25 +1498,34 @@
              return nodes[i].value;
            }
            if (nodes[i].children && nodes[i].children.length > 0) {
              const found = findProductIdByCategory(nodes[i].children, categoryName);
              const found = findProductIdByCategory(
                nodes[i].children,
                categoryName
              );
              if (found) return found;
            }
          }
          return null;
        };
        const productId = findProductIdByCategory(productOptions.value, productForm.value.productCategory);
        const productId = findProductIdByCategory(
          productOptions.value,
          productForm.value.productCategory
        );
        if (productId) {
          productForm.value.productId = productId;
          // èŽ·å–åž‹å·åˆ—è¡¨å¹¶ç­‰å¾…å®Œæˆ
          const modelRes = await modelList({ id: productId });
          modelOptions.value = modelRes;
          // ç­‰å¾… DOM æ›´æ–°
          await nextTick();
          // æ ¹æ® specificationModel æŸ¥æ‰¾ productModelId
          if (productForm.value.specificationModel && modelOptions.value.length > 0) {
          if (
            productForm.value.specificationModel &&
            modelOptions.value.length > 0
          ) {
            const modelItem = modelOptions.value.find(
              item => item.model === productForm.value.specificationModel
            );
@@ -1523,15 +1539,15 @@
      } else if (productForm.value.productId) {
        // å¦‚果有 productId,正常加载型号列表
        await getModels(productForm.value.productId);
        // ç­‰å¾… DOM æ›´æ–°
        await nextTick();
        if (productForm.value.productModelId) {
          getProductModel(productForm.value.productModelId);
        }
      }
      // æœ€åŽå†ç­‰å¾…一次 DOM æ›´æ–°ï¼Œç¡®ä¿æ‰€æœ‰æ•°æ®éƒ½å·²è®¾ç½®
      await nextTick();
    }
@@ -1654,11 +1670,9 @@
          delProduct(ids).then(res => {
            proxy.$modal.msgSuccess("删除成功");
            closeProductDia();
            getPurchaseById({ id: currentId.value, type: 2 }).then(
              res => {
                productData.value = res.productData;
              }
            );
            getPurchaseById({ id: currentId.value, type: 2 }).then(res => {
              productData.value = res.productData;
            });
          });
        })
        .catch(() => {
@@ -1866,12 +1880,12 @@
  };
  // åˆ é™¤æ¨¡æ¿
  const handleDeleteTemplate = async (item) => {
  const handleDeleteTemplate = async item => {
    if (!item.id) {
      proxy.$modal.msgWarning("无法删除该模板");
      return;
    }
    try {
      await ElMessageBox.confirm(
        `确定要删除模板"${item.templateName}"吗?`,
@@ -1882,7 +1896,7 @@
          type: "warning",
        }
      );
      const res = await delPurchaseTemplate([item.id]);
      if (res && res.code === 200) {
        ElMessage({
@@ -1935,7 +1949,7 @@
    display: flex;
    align-items: center;
  }
  // å®¡æ‰¹äººèŠ‚ç‚¹å®¹å™¨æ ·å¼
  .approver-nodes-container {
    display: flex;
@@ -1946,7 +1960,7 @@
    border-radius: 4px;
    border: 1px solid #e4e7ed;
  }
  .approver-node-item {
    flex: 0 0 calc(33.333% - 12px);
    min-width: 200px;
@@ -1955,38 +1969,38 @@
    border-radius: 4px;
    border: 1px solid #dcdfe6;
    transition: all 0.3s;
    &:hover {
      border-color: #409eff;
      box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
    }
  }
  .approver-node-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 8px;
  }
  .approver-node-label {
    font-size: 13px;
    font-weight: 500;
    color: #606266;
  }
  @media (max-width: 1200px) {
    .approver-node-item {
      flex: 0 0 calc(50% - 8px);
    }
  }
  @media (max-width: 768px) {
    .approver-node-item {
      flex: 0 0 100%;
    }
  }
  // åˆ é™¤å›¾æ ‡æ ·å¼
  .delete-icon {
    transition: all 0.3s;
src/views/salesManagement/salesLedger/index.vue
ÎļþÌ«´ó
src/views/salesManagement/salesQuotation/index.vue
@@ -2,350 +2,444 @@
  <div class="app-container">
    <el-card class="box-card">
      <!-- æœç´¢åŒºåŸŸ -->
      <el-row :gutter="20" class="search-row">
      <el-row :gutter="20"
              class="search-row">
        <el-col :span="8">
          <el-input
            v-model="searchForm.quotationNo"
            placeholder="请输入报价单号"
            clearable
            @keyup.enter="handleSearch"
          >
          <el-input v-model="searchForm.quotationNo"
                    placeholder="请输入报价单号"
                    clearable
                    @keyup.enter="handleSearch">
            <template #prefix>
              <el-icon><Search /></el-icon>
              <el-icon>
                <Search />
              </el-icon>
            </template>
          </el-input>
        </el-col>
        <el-col :span="8">
          <el-select v-model="searchForm.customer" placeholder="请选择客户" clearable>
                        <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.customerName">
                            {{
          <el-select v-model="searchForm.customer"
                     placeholder="请选择客户"
                     clearable>
            <el-option v-for="item in customerOption"
                       :key="item.id"
                       :label="item.customerName"
                       :value="item.customerName">
              {{
                                item.customerName + "——" + item.taxpayerIdentificationNumber
                            }}
                        </el-option>
            </el-option>
          </el-select>
        </el-col>
<!--        <el-col :span="6">-->
<!--          <el-select v-model="searchForm.status" placeholder="请选择报价状态" clearable>-->
<!--            <el-option label="草稿" value="草稿"></el-option>-->
<!--            <el-option label="已发送" value="已发送"></el-option>-->
<!--            <el-option label="客户确认" value="客户确认"></el-option>-->
<!--            <el-option label="已过期" value="已过期"></el-option>-->
<!--          </el-select>-->
<!--        </el-col>-->
        <!--        <el-col :span="6">-->
        <!--          <el-select v-model="searchForm.status" placeholder="请选择报价状态" clearable>-->
        <!--            <el-option label="草稿" value="草稿"></el-option>-->
        <!--            <el-option label="已发送" value="已发送"></el-option>-->
        <!--            <el-option label="客户确认" value="客户确认"></el-option>-->
        <!--            <el-option label="已过期" value="已过期"></el-option>-->
        <!--          </el-select>-->
        <!--        </el-col>-->
        <el-col :span="8">
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button type="primary"
                     @click="handleSearch">搜索</el-button>
          <el-button @click="resetSearch">重置</el-button>
          <el-button style="float: right;" type="primary" @click="handleAdd">
          <el-button style="float: right;"
                     type="primary"
                     @click="handleAdd">
            æ–°å¢žæŠ¥ä»·
          </el-button>
        </el-col>
      </el-row>
      <!-- æŠ¥ä»·åˆ—表 -->
      <el-table
        :data="filteredList"
        style="width: 100%"
        v-loading="loading"
        border
        stripe
        height="calc(100vh - 22em)"
      >
                <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column prop="quotationNo" label="报价单号" />
        <el-table-column prop="customer" label="客户名称" />
        <el-table-column prop="salesperson" label="业务员" width="100" />
        <el-table-column prop="quotationDate" label="报价日期" width="120" />
        <el-table-column prop="validDate" label="有效期至" width="120" />
        <el-table-column prop="status" label="审批状态" width="120" align="center">
      <el-table :data="filteredList"
                style="width: 100%"
                v-loading="loading"
                border
                stripe
                height="calc(100vh - 22em)">
        <el-table-column align="center"
                         label="序号"
                         type="index"
                         width="60" />
        <el-table-column prop="quotationNo"
                         label="报价单号" />
        <el-table-column prop="customer"
                         label="客户名称" />
        <el-table-column prop="salesperson"
                         label="业务员"
                         width="100" />
        <el-table-column prop="quotationDate"
                         label="报价日期"
                         width="120" />
        <el-table-column prop="validDate"
                         label="有效期至"
                         width="120" />
        <el-table-column prop="status"
                         label="审批状态"
                         width="120"
                         align="center">
          <template #default="{ row }">
            <el-tag :type="getStatusType(row.status)" disable-transitions>
            <el-tag :type="getStatusType(row.status)"
                    disable-transitions>
              {{ row.status || '--' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="totalAmount" label="报价金额" width="120">
        <el-table-column prop="totalAmount"
                         label="报价金额"
                         width="120">
          <template #default="scope">
            Â¥{{ scope.row.totalAmount.toFixed(2) }}
          </template>
        </el-table-column>
        <el-table-column label="操作" width="200" fixed="right" align="center">
        <el-table-column label="操作"
                         width="200"
                         fixed="right"
                         align="center">
          <template #default="scope">
            <el-button link type="primary" @click="handleEdit(scope.row)" :disabled="!['待审批','拒绝'].includes(scope.row.status)">编辑</el-button>
            <el-button link type="primary" @click="handleView(scope.row)" style="color: #67C23A">查看</el-button>
            <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
            <el-button link
                       type="primary"
                       @click="handleEdit(scope.row)"
                       :disabled="!['待审批','拒绝'].includes(scope.row.status)">编辑</el-button>
            <el-button link
                       type="primary"
                       @click="handleView(scope.row)"
                       style="color: #67C23A">查看</el-button>
            <el-button link
                       type="danger"
                       @click="handleDelete(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <pagination
        :total="pagination.total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="pagination.currentPage"
        :limit="pagination.pageSize"
        @pagination="handleCurrentChange"
      />
      <pagination :total="pagination.total"
                  layout="total, sizes, prev, pager, next, jumper"
                  :page="pagination.currentPage"
                  :limit="pagination.pageSize"
                  @pagination="handleCurrentChange" />
    </el-card>
    <!-- æ–°å¢ž/编辑对话框 -->
    <FormDialog v-model="dialogVisible" :title="dialogTitle" width="85%" :close-on-click-modal="false" @close="dialogVisible = false" @confirm="handleSubmit" @cancel="dialogVisible = false">
    <FormDialog v-model="dialogVisible"
                :title="dialogTitle"
                width="85%"
                :close-on-click-modal="false"
                @close="dialogVisible = false"
                @confirm="handleSubmit"
                @cancel="dialogVisible = false">
      <div class="quotation-form-container">
        <el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="quotation-form">
        <!-- åŸºæœ¬ä¿¡æ¯ -->
        <el-card class="form-card" shadow="hover">
          <template #header>
            <div class="card-header-wrapper">
              <el-icon class="card-icon"><Document /></el-icon>
              <span class="card-title">基本信息</span>
            </div>
          </template>
          <div class="form-content">
            <el-row :gutter="24">
              <el-col :span="12">
                <el-form-item label="客户名称" prop="customer">
                  <el-select v-model="form.customer" placeholder="请选择客户" style="width: 100%" @change="handleCustomerChange" clearable>
                    <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.customerName">
                      {{
        <el-form :model="form"
                 :rules="rules"
                 ref="formRef"
                 label-width="120px"
                 class="quotation-form">
          <!-- åŸºæœ¬ä¿¡æ¯ -->
          <el-card class="form-card"
                   shadow="hover">
            <template #header>
              <div class="card-header-wrapper">
                <el-icon class="card-icon">
                  <Document />
                </el-icon>
                <span class="card-title">基本信息</span>
              </div>
            </template>
            <div class="form-content">
              <el-row :gutter="24">
                <el-col :span="12">
                  <el-form-item label="客户名称"
                                prop="customer">
                    <el-select v-model="form.customer"
                               placeholder="请选择客户"
                               style="width: 100%"
                               @change="handleCustomerChange"
                               clearable>
                      <el-option v-for="item in customerOption"
                                 :key="item.id"
                                 :label="item.customerName"
                                 :value="item.customerName">
                        {{
                        item.customerName + "——" + item.taxpayerIdentificationNumber
                      }}
                    </el-option>
                  </el-select>
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="业务员" prop="salesperson">
                  <el-select v-model="form.salesperson" placeholder="请选择业务员" style="width: 100%" clearable>
                    <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
                      :value="item.nickName" />
                  </el-select>
                </el-form-item>
              </el-col>
            </el-row>
            <el-row :gutter="24">
              <el-col :span="12">
                <el-form-item label="报价日期" prop="quotationDate">
                  <el-date-picker
                    v-model="form.quotationDate"
                    type="date"
                    placeholder="选择报价日期"
                    style="width: 100%"
                    format="YYYY-MM-DD"
                    value-format="YYYY-MM-DD"
                    clearable
                  />
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="有效期至" prop="validDate">
                  <el-date-picker
                    v-model="form.validDate"
                    type="date"
                    placeholder="选择有效期"
                    style="width: 100%"
                    format="YYYY-MM-DD"
                    value-format="YYYY-MM-DD"
                    clearable
                  />
                </el-form-item>
              </el-col>
            </el-row>
            <el-row :gutter="24">
              <el-col :span="12">
                <el-form-item label="付款方式" prop="paymentMethod">
                  <el-input v-model="form.paymentMethod" placeholder="请输入付款方式" clearable />
                </el-form-item>
              </el-col>
            </el-row>
          </div>
        </el-card>
        <!-- å®¡æ‰¹äººä¿¡æ¯ -->
        <el-card class="form-card" shadow="hover">
          <template #header>
            <div class="card-header-wrapper">
              <el-icon class="card-icon"><UserFilled /></el-icon>
              <span class="card-title">审批人选择</span>
              <el-button type="primary" size="small" @click="addApproverNode" class="header-btn">
                <el-icon><Plus /></el-icon>
                æ–°å¢žèŠ‚ç‚¹
              </el-button>
                      </el-option>
                    </el-select>
                  </el-form-item>
                </el-col>
                <el-col :span="12">
                  <el-form-item label="业务员"
                                prop="salesperson">
                    <el-select v-model="form.salesperson"
                               placeholder="请选择业务员"
                               style="width: 100%"
                               clearable>
                      <el-option v-for="item in userList"
                                 :key="item.nickName"
                                 :label="item.nickName"
                                 :value="item.nickName" />
                    </el-select>
                  </el-form-item>
                </el-col>
              </el-row>
              <el-row :gutter="24">
                <el-col :span="12">
                  <el-form-item label="报价日期"
                                prop="quotationDate">
                    <el-date-picker v-model="form.quotationDate"
                                    type="date"
                                    placeholder="选择报价日期"
                                    style="width: 100%"
                                    format="YYYY-MM-DD"
                                    value-format="YYYY-MM-DD"
                                    clearable />
                  </el-form-item>
                </el-col>
                <el-col :span="12">
                  <el-form-item label="有效期至"
                                prop="validDate">
                    <el-date-picker v-model="form.validDate"
                                    type="date"
                                    placeholder="选择有效期"
                                    style="width: 100%"
                                    format="YYYY-MM-DD"
                                    value-format="YYYY-MM-DD"
                                    clearable />
                  </el-form-item>
                </el-col>
              </el-row>
              <el-row :gutter="24">
                <el-col :span="12">
                  <el-form-item label="付款方式"
                                prop="paymentMethod">
                    <el-input v-model="form.paymentMethod"
                              placeholder="请输入付款方式"
                              clearable />
                  </el-form-item>
                </el-col>
              </el-row>
            </div>
          </template>
          <div class="form-content">
            <el-row>
              <el-col :span="24">
                <el-form-item>
                  <div class="approver-nodes-container">
                    <div
                      v-for="(node, index) in approverNodes"
                      :key="node.id"
                      class="approver-node-item"
                    >
                      <div class="approver-node-label">
                        <span class="node-step">{{ index + 1 }}</span>
                        <span class="node-text">审批人</span>
                        <el-icon class="arrow-icon"><ArrowRight /></el-icon>
          </el-card>
          <!-- å®¡æ‰¹äººä¿¡æ¯ -->
          <el-card class="form-card"
                   shadow="hover">
            <template #header>
              <div class="card-header-wrapper">
                <el-icon class="card-icon">
                  <UserFilled />
                </el-icon>
                <span class="card-title">审批人选择</span>
                <el-button type="primary"
                           size="small"
                           @click="addApproverNode"
                           class="header-btn">
                  <el-icon>
                    <Plus />
                  </el-icon>
                  æ–°å¢žèŠ‚ç‚¹
                </el-button>
              </div>
            </template>
            <div class="form-content">
              <el-row>
                <el-col :span="24">
                  <el-form-item>
                    <div class="approver-nodes-container">
                      <div v-for="(node, index) in approverNodes"
                           :key="node.id"
                           class="approver-node-item">
                        <div class="approver-node-label">
                          <span class="node-step">{{ index + 1 }}</span>
                          <span class="node-text">审批人</span>
                          <el-icon class="arrow-icon">
                            <ArrowRight />
                          </el-icon>
                        </div>
                        <el-select v-model="node.userId"
                                   placeholder="选择人员"
                                   class="approver-select"
                                   clearable>
                          <el-option v-for="user in userListApprove"
                                     :key="user.userId"
                                     :label="user.userName"
                                     :value="user.userId" />
                        </el-select>
                        <el-button type="danger"
                                   size="small"
                                   :icon="Delete"
                                   @click="removeApproverNode(index)"
                                   v-if="approverNodes.length > 1"
                                   class="remove-btn">删除</el-button>
                      </div>
                      <el-select
                        v-model="node.userId"
                        placeholder="选择人员"
                        class="approver-select"
                        clearable
                      >
                        <el-option
                          v-for="user in userList"
                          :key="user.userId"
                          :label="user.nickName"
                          :value="user.userId"
                        />
                      </el-select>
                      <el-button
                        type="danger"
                        size="small"
                        :icon="Delete"
                        @click="removeApproverNode(index)"
                        v-if="approverNodes.length > 1"
                        class="remove-btn"
                      >删除</el-button>
                    </div>
                  </div>
                </el-form-item>
              </el-col>
            </el-row>
          </div>
        </el-card>
        <!-- äº§å“ä¿¡æ¯ -->
        <el-card class="form-card" shadow="hover">
          <template #header>
            <div class="card-header-wrapper">
              <el-icon class="card-icon"><Box /></el-icon>
              <span class="card-title">产品信息</span>
              <el-button type="primary" size="small" @click="addProduct" class="header-btn">
                <el-icon><Plus /></el-icon>
                æ·»åŠ äº§å“
              </el-button>
                  </el-form-item>
                </el-col>
              </el-row>
            </div>
          </template>
          <div class="form-content">
            <el-table :data="form.products" border style="width: 100%" class="product-table" v-if="form.products.length > 0">
            <el-table-column prop="product" label="产品名称" width="200">
              <template #default="scope">
                <el-form-item :prop="`products.${scope.$index}.productId`" class="product-table-form-item">
                  <el-tree-select
                    v-model="scope.row.productId"
                    placeholder="请选择"
                    clearable
                    check-strictly
                    @change="getModels($event, scope.row)"
                    :data="productOptions"
                    :render-after-expand="false"
                    style="width: 100%"
                  />
                </el-form-item>
              </template>
            </el-table-column>
            <el-table-column prop="specification" label="规格型号" width="200">
              <template #default="scope">
                <el-form-item :prop="`products.${scope.$index}.specificationId`" class="product-table-form-item">
                  <el-select
                    v-model="scope.row.specificationId"
                    placeholder="请选择"
                    clearable
                    @change="getProductModel($event, scope.row)"
                    style="width: 100%"
                  >
                    <el-option
                      v-for="item in scope.row.modelOptions || []"
                      :key="item.id"
                      :label="item.model"
                      :value="item.id"
                    />
                  </el-select>
                </el-form-item>
              </template>
            </el-table-column>
            <el-table-column prop="unit" label="单位">
              <template #default="scope">
                <el-form-item :prop="`products.${scope.$index}.unit`" class="product-table-form-item">
                  <el-input v-model="scope.row.unit" placeholder="单位" clearable/>
                </el-form-item>
              </template>
            </el-table-column>
            <el-table-column prop="unitPrice" label="单价">
              <template #default="scope">
                <el-form-item :prop="`products.${scope.$index}.unitPrice`" class="product-table-form-item">
                  <el-input-number v-model="scope.row.unitPrice" :min="0" :precision="2" style="width: 100%" />
                </el-form-item>
              </template>
            </el-table-column>
            <el-table-column label="操作" width="80" align="center">
              <template #default="scope">
                <el-button link type="danger" @click="removeProduct(scope.$index)">删除</el-button>
              </template>
            </el-table-column>
          </el-table>
          <el-empty v-else description="暂无产品,请点击添加产品" :image-size="80" />
          </div>
        </el-card>
        <!-- å¤‡æ³¨ä¿¡æ¯ -->
        <el-card class="form-card" shadow="hover">
          <template #header>
            <div class="card-header-wrapper">
              <el-icon class="card-icon"><EditPen /></el-icon>
              <span class="card-title">备注信息</span>
          </el-card>
          <!-- äº§å“ä¿¡æ¯ -->
          <el-card class="form-card"
                   shadow="hover">
            <template #header>
              <div class="card-header-wrapper">
                <el-icon class="card-icon">
                  <Box />
                </el-icon>
                <span class="card-title">产品信息</span>
                <el-button type="primary"
                           size="small"
                           @click="addProduct"
                           class="header-btn">
                  <el-icon>
                    <Plus />
                  </el-icon>
                  æ·»åŠ äº§å“
                </el-button>
              </div>
            </template>
            <div class="form-content">
              <el-table :data="form.products"
                        border
                        style="width: 100%"
                        class="product-table"
                        v-if="form.products.length > 0">
                <el-table-column prop="product"
                                 label="产品名称"
                                 width="200">
                  <template #default="scope">
                    <el-form-item :prop="`products.${scope.$index}.productId`"
                                  class="product-table-form-item">
                      <el-tree-select v-model="scope.row.productId"
                                      placeholder="请选择"
                                      clearable
                                      check-strictly
                                      @change="getModels($event, scope.row)"
                                      :data="productOptions"
                                      :render-after-expand="false"
                                      style="width: 100%" />
                    </el-form-item>
                  </template>
                </el-table-column>
                <el-table-column prop="specification"
                                 label="规格型号"
                                 width="200">
                  <template #default="scope">
                    <el-form-item :prop="`products.${scope.$index}.specificationId`"
                                  class="product-table-form-item">
                      <el-select v-model="scope.row.specificationId"
                                 placeholder="请选择"
                                 clearable
                                 @change="getProductModel($event, scope.row)"
                                 style="width: 100%">
                        <el-option v-for="item in scope.row.modelOptions || []"
                                   :key="item.id"
                                   :label="item.model"
                                   :value="item.id" />
                      </el-select>
                    </el-form-item>
                  </template>
                </el-table-column>
                <el-table-column prop="unit"
                                 label="单位">
                  <template #default="scope">
                    <el-form-item :prop="`products.${scope.$index}.unit`"
                                  class="product-table-form-item">
                      <el-input v-model="scope.row.unit"
                                placeholder="单位"
                                clearable />
                    </el-form-item>
                  </template>
                </el-table-column>
                <el-table-column prop="unitPrice"
                                 label="单价">
                  <template #default="scope">
                    <el-form-item :prop="`products.${scope.$index}.unitPrice`"
                                  class="product-table-form-item">
                      <el-input-number v-model="scope.row.unitPrice"
                                       :min="0"
                                       :precision="2"
                                       style="width: 100%" />
                    </el-form-item>
                  </template>
                </el-table-column>
                <el-table-column label="操作"
                                 width="80"
                                 align="center">
                  <template #default="scope">
                    <el-button link
                               type="danger"
                               @click="removeProduct(scope.$index)">删除</el-button>
                  </template>
                </el-table-column>
              </el-table>
              <el-empty v-else
                        description="暂无产品,请点击添加产品"
                        :image-size="80" />
            </div>
          </template>
          <div class="form-content">
            <el-form-item label="备注" prop="remark">
              <el-input
                type="textarea"
                v-model="form.remark"
                placeholder="请输入备注信息(选填)"
                :rows="4"
                maxlength="500"
                show-word-limit
              ></el-input>
            </el-form-item>
          </div>
        </el-card>
      </el-form>
          </el-card>
          <!-- å¤‡æ³¨ä¿¡æ¯ -->
          <el-card class="form-card"
                   shadow="hover">
            <template #header>
              <div class="card-header-wrapper">
                <el-icon class="card-icon">
                  <EditPen />
                </el-icon>
                <span class="card-title">备注信息</span>
              </div>
            </template>
            <div class="form-content">
              <el-form-item label="备注"
                            prop="remark">
                <el-input type="textarea"
                          v-model="form.remark"
                          placeholder="请输入备注信息(选填)"
                          :rows="4"
                          maxlength="500"
                          show-word-limit></el-input>
              </el-form-item>
            </div>
          </el-card>
        </el-form>
      </div>
    </FormDialog>
    <!-- æŸ¥çœ‹è¯¦æƒ…对话框 -->
    <el-dialog v-model="viewDialogVisible" title="报价详情" width="800px">
      <el-descriptions :column="2" border>
    <el-dialog v-model="viewDialogVisible"
               title="报价详情"
               width="800px">
      <el-descriptions :column="2"
                       border>
        <el-descriptions-item label="报价单号">{{ currentQuotation.quotationNo }}</el-descriptions-item>
        <el-descriptions-item label="客户名称">{{ currentQuotation.customer }}</el-descriptions-item>
        <el-descriptions-item label="业务员">{{ currentQuotation.salesperson }}</el-descriptions-item>
        <el-descriptions-item label="报价日期">{{ currentQuotation.quotationDate }}</el-descriptions-item>
        <el-descriptions-item label="有效期至">{{ currentQuotation.validDate }}</el-descriptions-item>
        <el-descriptions-item label="付款方式">{{ currentQuotation.paymentMethod }}</el-descriptions-item>
<!--        <el-descriptions-item label="报价状态">-->
<!--          <el-tag :type="getStatusType(currentQuotation.status)">{{ currentQuotation.status }}</el-tag>-->
<!--        </el-descriptions-item>-->
        <el-descriptions-item label="报价总额" :span="2">
        <!--        <el-descriptions-item label="报价状态">-->
        <!--          <el-tag :type="getStatusType(currentQuotation.status)">{{ currentQuotation.status }}</el-tag>-->
        <!--        </el-descriptions-item>-->
        <el-descriptions-item label="报价总额"
                              :span="2">
          <span style="font-size: 18px; color: #e6a23c; font-weight: bold;">Â¥{{ currentQuotation.totalAmount?.toFixed(2) }}</span>
        </el-descriptions-item>
      </el-descriptions>
      <div style="margin: 20px 0;">
        <h4>产品明细</h4>
        <el-table :data="currentQuotation.products" border style="width: 100%">
          <el-table-column prop="product" label="产品名称" />
          <el-table-column prop="specification" label="规格型号" />
          <el-table-column prop="unit" label="单位" />
          <el-table-column prop="unitPrice" label="单价">
        <el-table :data="currentQuotation.products"
                  border
                  style="width: 100%">
          <el-table-column prop="product"
                           label="产品名称" />
          <el-table-column prop="specification"
                           label="规格型号" />
          <el-table-column prop="unit"
                           label="单位" />
          <el-table-column prop="unitPrice"
                           label="单价">
            <template #default="scope">
              Â¥{{ scope.row.unitPrice.toFixed(2) }}
            </template>
          </el-table-column>
        </el-table>
      </div>
      <div v-if="currentQuotation.remark" style="margin-top: 20px;">
      <div v-if="currentQuotation.remark"
           style="margin-top: 20px;">
        <h4>备注</h4>
        <p>{{ currentQuotation.remark }}</p>
      </div>
@@ -354,742 +448,782 @@
</template>
<script setup>
import { ref, reactive, computed, onMounted, markRaw, shallowRef } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Document, UserFilled, Box, EditPen, Plus, ArrowRight, Delete } from '@element-plus/icons-vue'
import Pagination from '@/components/PIMTable/Pagination.vue'
import FormDialog from '@/components/Dialog/FormDialog.vue'
import {getQuotationList,addQuotation,updateQuotation,deleteQuotation} from '@/api/salesManagement/salesQuotation.js'
import {userListNoPage} from "@/api/system/user.js";
import {customerList} from "@/api/salesManagement/salesLedger.js";
import {modelList, productTreeList} from "@/api/basicData/product.js";
  import { ref, reactive, computed, onMounted, markRaw, shallowRef } from "vue";
  import { ElMessage, ElMessageBox } from "element-plus";
  import {
    Search,
    Document,
    UserFilled,
    Box,
    EditPen,
    Plus,
    ArrowRight,
    Delete,
  } from "@element-plus/icons-vue";
  import Pagination from "@/components/PIMTable/Pagination.vue";
  import FormDialog from "@/components/Dialog/FormDialog.vue";
  import {
    getQuotationList,
    addQuotation,
    updateQuotation,
    deleteQuotation,
  } from "@/api/salesManagement/salesQuotation.js";
  import { userListNoPage } from "@/api/system/user.js";
  import { approveUserList } from "@/api/collaborativeApproval/approvalProcess.js";
// å“åº”式数据
const loading = ref(false)
const searchForm = reactive({
  quotationNo: '',
  customer: '',
  status: ''
})
  import { customerList } from "@/api/salesManagement/salesLedger.js";
  import { modelList, productTreeList } from "@/api/basicData/product.js";
const quotationList = ref([])
const productOptions = ref([]);
const modelOptions = ref([]);
const pagination = reactive({
  total: 3,
  currentPage: 1,
  pageSize: 100
})
  // å“åº”式数据
  const loading = ref(false);
  const searchForm = reactive({
    quotationNo: "",
    customer: "",
    status: "",
  });
const dialogVisible = ref(false)
const viewDialogVisible = ref(false)
const dialogTitle = ref('新增报价')
const form = reactive({
  quotationNo: '',
  customer: '',
  salesperson: '',
  quotationDate: '',
  validDate: '',
  paymentMethod: '',
  status: '草稿',
  remark: '',
  products: [],
  subtotal: 0,
  freight: 0,
  otherFee: 0,
  discountRate: 0,
  discountAmount: 0,
  totalAmount: 0
})
  const quotationList = ref([]);
  const productOptions = ref([]);
  const modelOptions = ref([]);
  const pagination = reactive({
    total: 3,
    currentPage: 1,
    pageSize: 100,
  });
const baseRules = {
  customer: [{ required: true, message: '请选择客户', trigger: 'change' }],
  salesperson: [{ required: true, message: '请选择业务员', trigger: 'change' }],
  quotationDate: [{ required: true, message: '请选择报价日期', trigger: 'change' }],
  validDate: [{ required: true, message: '请选择有效期', trigger: 'change' }],
  paymentMethod: [{ required: true, message: '请输入付款方式', trigger: 'blur' }]
}
  const dialogVisible = ref(false);
  const viewDialogVisible = ref(false);
  const dialogTitle = ref("新增报价");
  const form = reactive({
    quotationNo: "",
    customer: "",
    salesperson: "",
    quotationDate: "",
    validDate: "",
    paymentMethod: "",
    status: "草稿",
    remark: "",
    products: [],
    subtotal: 0,
    freight: 0,
    otherFee: 0,
    discountRate: 0,
    discountAmount: 0,
    totalAmount: 0,
  });
const productRowRules = {
  productId: [{ required: true, message: '请选择产品名称', trigger: 'change' }],
  specificationId: [{ required: true, message: '请选择规格型号', trigger: 'change' }],
  unit: [{ required: true, message: '请填写单位', trigger: 'blur' }],
  unitPrice: [{ required: true, message: '请填写单价', trigger: 'change' }]
}
const rules = computed(() => {
  const r = { ...baseRules }
  ;(form.products || []).forEach((_, i) => {
    r[`products.${i}.productId`] = productRowRules.productId
    r[`products.${i}.specificationId`] = productRowRules.specificationId
    r[`products.${i}.unit`] = productRowRules.unit
    r[`products.${i}.unitPrice`] = productRowRules.unitPrice
  })
  return r
})
const userList = ref([]);
const customerOption = ref([]);
  const baseRules = {
    customer: [{ required: true, message: "请选择客户", trigger: "change" }],
    salesperson: [{ required: true, message: "请选择业务员", trigger: "change" }],
    quotationDate: [
      { required: true, message: "请选择报价日期", trigger: "change" },
    ],
    validDate: [{ required: true, message: "请选择有效期", trigger: "change" }],
    paymentMethod: [
      { required: true, message: "请输入付款方式", trigger: "blur" },
    ],
  };
// å®¡æ‰¹äººèŠ‚ç‚¹ç›¸å…³
const approverNodes = ref([
  { id: 1, userId: null }
])
let nextApproverId = 2
  const productRowRules = {
    productId: [{ required: true, message: "请选择产品名称", trigger: "change" }],
    specificationId: [
      { required: true, message: "请选择规格型号", trigger: "change" },
    ],
    unit: [{ required: true, message: "请填写单位", trigger: "blur" }],
    unitPrice: [{ required: true, message: "请填写单价", trigger: "change" }],
  };
  const rules = computed(() => {
    const r = { ...baseRules };
    (form.products || []).forEach((_, i) => {
      r[`products.${i}.productId`] = productRowRules.productId;
      r[`products.${i}.specificationId`] = productRowRules.specificationId;
      r[`products.${i}.unit`] = productRowRules.unit;
      r[`products.${i}.unitPrice`] = productRowRules.unitPrice;
    });
    return r;
  });
  const userList = ref([]);
  const userListApprove = ref([]);
  const customerOption = ref([]);
const isEdit = ref(false)
const editId = ref(null)
const currentQuotation = ref({})
const formRef = ref()
  // å®¡æ‰¹äººèŠ‚ç‚¹ç›¸å…³
  const approverNodes = ref([{ id: 1, userId: null }]);
  let nextApproverId = 2;
// æ·»åŠ å®¡æ‰¹äººèŠ‚ç‚¹
function addApproverNode() {
  approverNodes.value.push({ id: nextApproverId++, userId: null })
}
  const isEdit = ref(false);
  const editId = ref(null);
  const currentQuotation = ref({});
  const formRef = ref();
// åˆ é™¤å®¡æ‰¹äººèŠ‚ç‚¹
function removeApproverNode(index) {
  approverNodes.value.splice(index, 1)
}
// è®¡ç®—属性
const filteredList = computed(() => {
  let list = quotationList.value
  return list
})
// æ–¹æ³•
const getStatusType = (status) => {
  const statusMap = {
    '待审批': 'info',
    '审核中': 'primary',
    '通过': 'success',
    '拒绝': 'danger'
  // æ·»åŠ å®¡æ‰¹äººèŠ‚ç‚¹
  function addApproverNode() {
    approverNodes.value.push({ id: nextApproverId++, userId: null });
  }
  return statusMap[status] || 'info'
}
const resetSearch = () => {
  searchForm.quotationNo = ''
  searchForm.customer = ''
  searchForm.status = ''
  // é‡ç½®åˆ°ç¬¬ä¸€é¡µå¹¶é‡æ–°æŸ¥è¯¢
  pagination.currentPage = 1
  handleSearch()
}
const handleAdd = async () => {
  dialogTitle.value = '新增报价'
  isEdit.value = false
  resetForm()
  // é‡ç½®å®¡æ‰¹äººèŠ‚ç‚¹
  approverNodes.value = [{ id: 1, userId: null }]
  nextApproverId = 2
  dialogVisible.value = true
    let userLists = await userListNoPage();
    // åªå¤åˆ¶éœ€è¦çš„字段,避免将组件引用放入响应式对象
    userList.value = (userLists.data || []).map(item => ({
    userId: item.userId,
    nickName: item.nickName || '',
    userName: item.userName || ''
  }));
    getProductOptions();
    customerList().then((res) => {
        // åªå¤åˆ¶éœ€è¦çš„字段,避免将组件引用放入响应式对象
        customerOption.value = (Array.isArray(res) ? res : []).map(item => ({
      id: item.id,
      customerName: item.customerName || '',
      taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || ''
    }))
    });
}
const getProductOptions = () => {
    // è¿”回 Promise,便于编辑时 await ç¡®ä¿èƒ½åæ˜¾
    return productTreeList().then((res) => {
        productOptions.value = convertIdToValue(res);
        return productOptions.value
    });
};
function convertIdToValue(data) {
    return data.map((item) => {
        const { id, children, ...rest } = item;
        const newItem = {
            ...rest,
            value: id, // å°† id æ”¹ä¸º value
        };
        if (children && children.length > 0) {
            newItem.children = convertIdToValue(children);
        }
        return newItem;
    });
}
// æ ¹æ®åç§°åæŸ¥èŠ‚ç‚¹ id,便于仅存名称时的反显
function findNodeIdByLabel(nodes, label) {
    if (!label) return null;
    for (let i = 0; i < nodes.length; i++) {
        const node = nodes[i];
        if (node.label === label) return node.value;
        if (node.children && node.children.length > 0) {
            const found = findNodeIdByLabel(node.children, label);
            if (found !== null && found !== undefined) return found;
        }
    }
    return null;
}
const getModels = (value, row) => {
    if (!row) return;
    // å¦‚果清空选择,则清空相关字段
    if (!value) {
        row.productId = '';
        row.product = '';
        row.modelOptions = [];
        row.specificationId = '';
        row.specification = '';
        row.unit = '';
        return;
    }
    // æ›´æ–° productId(v-model å·²ç»è‡ªåŠ¨æ›´æ–°ï¼Œè¿™é‡Œç¡®ä¿ä¸€è‡´æ€§ï¼‰
    row.productId = value;
    // æ‰¾åˆ°å¯¹åº”çš„ label å¹¶èµ‹å€¼ç»™ row.product
    const label = findNodeById(productOptions.value, value);
    if (label) {
        row.product = label;
    }
    // èŽ·å–è§„æ ¼åž‹å·åˆ—è¡¨ï¼Œè®¾ç½®åˆ°å½“å‰è¡Œçš„ modelOptions
    modelList({ id: value }).then((res) => {
        row.modelOptions = res || [];
    });
};
const getProductModel = (value, row) => {
    if (!row) return;
    // å¦‚果清空选择,则清空相关字段
    if (!value) {
        row.specificationId = '';
        row.specification = '';
        row.unit = '';
        return;
    }
    // æ›´æ–° specificationId(v-model å·²ç»è‡ªåŠ¨æ›´æ–°ï¼Œè¿™é‡Œç¡®ä¿ä¸€è‡´æ€§ï¼‰
    row.specificationId = value;
    const modelOptions = row.modelOptions || [];
    const index = modelOptions.findIndex((item) => item.id === value);
    if (index !== -1) {
        row.specification = modelOptions[index].model;
        row.unit = modelOptions[index].unit;
    } else {
        row.specification = '';
        row.unit = '';
    }
};
const findNodeById = (nodes, productId) => {
    for (let i = 0; i < nodes.length; i++) {
        if (nodes[i].value === productId) {
            return nodes[i].label; // æ‰¾åˆ°èŠ‚ç‚¹ï¼Œè¿”å›ž label
        }
        if (nodes[i].children && nodes[i].children.length > 0) {
            const foundLabel = findNodeById(nodes[i].children, productId);
            if (foundLabel) {
                return foundLabel; // åœ¨å­èŠ‚ç‚¹ä¸­æ‰¾åˆ°ï¼Œè¿”å›ž label
            }
        }
    }
    return null; // æ²¡æœ‰æ‰¾åˆ°èŠ‚ç‚¹ï¼Œè¿”å›žnull
};
const handleView = (row) => {
  // åªå¤åˆ¶éœ€è¦çš„字段,避免将组件引用放入响应式对象
  currentQuotation.value = {
    quotationNo: row.quotationNo || '',
    customer: row.customer || '',
    salesperson: row.salesperson || '',
    quotationDate: row.quotationDate || '',
    validDate: row.validDate || '',
    paymentMethod: row.paymentMethod || '',
    status: row.status || '',
    remark: row.remark || '',
    products: row.products ? row.products.map(product => ({
      productId: product.productId || '',
      product: product.product || product.productName || '',
      specificationId: product.specificationId || '',
      specification: product.specification || '',
      quantity: product.quantity || 0,
      unit: product.unit || '',
      unitPrice: product.unitPrice || 0,
      amount: product.amount || 0
    })) : [],
    totalAmount: row.totalAmount || 0
  // åˆ é™¤å®¡æ‰¹äººèŠ‚ç‚¹
  function removeApproverNode(index) {
    approverNodes.value.splice(index, 1);
  }
  viewDialogVisible.value = true
}
const handleEdit = async (row) => {
  dialogTitle.value = '编辑报价'
  isEdit.value = true
  editId.value = row.id
  form.id = row.id || form.id || null
  // å…ˆåŠ è½½äº§å“æ ‘æ•°æ®ï¼Œå¦åˆ™ el-tree-select æ— æ³•反显产品名称
  await getProductOptions()
  // è®¡ç®—属性
  const filteredList = computed(() => {
    let list = quotationList.value;
    return list;
  });
  // åªå¤åˆ¶éœ€è¦çš„字段,避免将组件引用放入响应式对象
  form.quotationNo = row.quotationNo || ''
  form.customer = row.customer || ''
  form.salesperson = row.salesperson || ''
  form.quotationDate = row.quotationDate || ''
  form.validDate = row.validDate || ''
  form.paymentMethod = row.paymentMethod || ''
  form.status = row.status || '草稿'
  form.remark = row.remark || ''
  form.products = row.products ? await Promise.all(row.products.map(async (product) => {
    const productName = product.product || product.productName || ''
    // ä¼˜å…ˆç”¨ productId;如果只有名称,尝试反查 id ä»¥ä¾¿æ ‘选择器反显
    const resolvedProductId = product.productId
      ? Number(product.productId)
      : findNodeIdByLabel(productOptions.value, productName) || ''
    // å¦‚果有产品ID,加载对应的规格型号列表
    let modelOptions = [];
    let resolvedSpecificationId = product.specificationId || '';
    if (resolvedProductId) {
      try {
        const res = await modelList({ id: resolvedProductId });
        modelOptions = res || [];
        // å¦‚果返回的数据没有 specificationId,但有 specification åç§°ï¼Œæ ¹æ®åç§°æŸ¥æ‰¾ ID
        if (!resolvedSpecificationId && product.specification) {
          const foundModel = modelOptions.find(item => item.model === product.specification);
          if (foundModel) {
            resolvedSpecificationId = foundModel.id;
          }
        }
      } catch (error) {
        console.error('加载规格型号失败:', error);
      }
    }
    return {
      productId: resolvedProductId,
      product: productName,
      specificationId: resolvedSpecificationId,
      specification: product.specification || '',
      quantity: product.quantity || 0,
      unit: product.unit || '',
      unitPrice: product.unitPrice || 0,
      amount: product.amount || 0,
      modelOptions: modelOptions // ä¸ºæ¯è¡Œæ·»åŠ ç‹¬ç«‹çš„è§„æ ¼åž‹å·åˆ—è¡¨
    }
  })) : []
  form.subtotal = row.subtotal || 0
  form.freight = row.freight || 0
  form.otherFee = row.otherFee || 0
  form.discountRate = row.discountRate || 0
  form.discountAmount = row.discountAmount || 0
  form.totalAmount = row.totalAmount || 0
  // åæ˜¾å®¡æ‰¹äºº
  if (row.approveUserIds) {
    const userIds = row.approveUserIds.split(',')
    approverNodes.value = userIds.map((userId, idx) => ({
      id: idx + 1,
      userId: parseInt(userId.trim())
    }))
    nextApproverId = userIds.length + 1
  } else {
    approverNodes.value = [{ id: 1, userId: null }]
    nextApproverId = 2
  }
  // åŠ è½½ç”¨æˆ·åˆ—è¡¨
  let userLists = await userListNoPage();
  userList.value = (userLists.data || []).map(item => ({
    userId: item.userId,
    nickName: item.nickName || '',
    userName: item.userName || ''
  }));
  dialogVisible.value = true
}
  // æ–¹æ³•
  const getStatusType = status => {
    const statusMap = {
      å¾…审批: "info",
      å®¡æ ¸ä¸­: "primary",
      é€šè¿‡: "success",
      æ‹’绝: "danger",
    };
    return statusMap[status] || "info";
  };
  const resetSearch = () => {
    searchForm.quotationNo = "";
    searchForm.customer = "";
    searchForm.status = "";
    // é‡ç½®åˆ°ç¬¬ä¸€é¡µå¹¶é‡æ–°æŸ¥è¯¢
    pagination.currentPage = 1;
    handleSearch();
  };
const handleDelete = (row) => {
  ElMessageBox.confirm('确认删除该报价单吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    const index = quotationList.value.findIndex(item => item.id === row.id)
    if (index > -1) {
      deleteQuotation(row.id).then(res=>{
        // console.log(res)
        if(res.code===200){
          ElMessage.success('删除成功')
          handleSearch()
        }
      })
      // quotationList.value.splice(index, 1)
      // pagination.total--
      // ElMessage.success('删除成功')
    }
  })
}
const resetForm = () => {
  form.customer = ''
  form.salesperson = ''
  form.quotationDate = ''
  form.validDate = ''
  form.paymentMethod = ''
  form.status = '草稿'
  form.remark = ''
  form.products = []
  form.subtotal = 0
  form.freight = 0
  form.otherFee = 0
  form.discountRate = 0
  form.discountAmount = 0
  form.totalAmount = 0
}
const addProduct = () => {
  form.products.push({
    productId: '',
    product: '',
    productName: '',
    specificationId: '',
    specification: '',
    quantity: 1,
    unit: '',
    unitPrice: 0,
    amount: 0,
    modelOptions: [] // ä¸ºæ¯è¡Œæ·»åŠ ç‹¬ç«‹çš„è§„æ ¼åž‹å·åˆ—è¡¨
  })
}
const removeProduct = (index) => {
  form.products.splice(index, 1)
  calculateSubtotal()
}
const calculateAmount = (product) => {
  product.amount = product.quantity * product.unitPrice
  calculateSubtotal()
}
const calculateSubtotal = () => {
  form.subtotal = form.products.reduce((sum, product) => sum + product.amount, 0)
  calculateTotal()
}
const calculateTotal = () => {
  form.discountAmount = form.subtotal * (form.discountRate / 100)
  form.totalAmount = form.subtotal + form.freight + form.otherFee - form.discountAmount
}
const handleCustomerChange = () => {
  // å¯ä»¥æ ¹æ®å®¢æˆ·ä¿¡æ¯è‡ªåŠ¨å¡«å……ä¸€äº›é»˜è®¤å€¼
}
const handleSubmit = () => {
  formRef.value.validate((valid) => {
    if (valid) {
      if (form.products.length === 0) {
        ElMessage.warning('请至少添加一个产品')
        return
      }
      // å®¡æ‰¹äººå¿…填校验
      const hasEmptyApprover = approverNodes.value.some(node => !node.userId)
      if (hasEmptyApprover) {
        ElMessage.error('请为所有审批节点选择审批人!')
        return
      }
      // æ”¶é›†æ‰€æœ‰èŠ‚ç‚¹çš„å®¡æ‰¹äººid
      form.approveUserIds = approverNodes.value.map(node => node.userId).join(',')
      // è®¡ç®—所有产品的单价总和
      form.totalAmount = form.products.reduce((sum, product) => {
        const price = Number(product.unitPrice) || 0
        return sum + price
      }, 0)
      if (isEdit.value) {
        // ç¼–辑
        const index = quotationList.value.findIndex(item => item.id === editId.value)
        if (index > -1) {
          updateQuotation(form).then(res=>{
            // console.log(res)
            if(res.code===200){
              ElMessage.success('编辑成功')
              dialogVisible.value = false
              handleSearch()
            }
          })
        }
      } else {
        // æ–°å¢ž
        addQuotation(form).then(res=>{
          if(res.code===200){
            ElMessage.success('新增成功')
            dialogVisible.value = false
            handleSearch()
          }
        })
      }
    }
  })
}
const handleCurrentChange = (val) => {
  pagination.currentPage = val.page
  pagination.pageSize = val.limit
  // åˆ†é¡µå˜åŒ–时重新查询列表
  handleSearch()
}
const handleSearch = ()=>{
  const params = {
    // åŽç«¯åˆ†é¡µå‚数:current / size
    current: pagination.currentPage,
    size: pagination.pageSize,
    ...searchForm
  }
  getQuotationList(params).then(res=>{
    // console.log(res)
    if(res.code===200){
      // åªå¤åˆ¶éœ€è¦çš„字段,避免将组件引用或其他对象放入响应式对象
      quotationList.value = (res.data.records || []).map(item => ({
  const handleAdd = async () => {
    dialogTitle.value = "新增报价";
    isEdit.value = false;
    resetForm();
    // é‡ç½®å®¡æ‰¹äººèŠ‚ç‚¹
    approverNodes.value = [{ id: 1, userId: null }];
    nextApproverId = 2;
    dialogVisible.value = true;
    let userLists = await userListNoPage();
    // åªå¤åˆ¶éœ€è¦çš„字段,避免将组件引用放入响应式对象
    userList.value = (userLists.data || []).map(item => ({
      userId: item.userId,
      nickName: item.nickName || "",
      userName: item.userName || "",
    }));
    approveUserList({ approveType: 6 }).then(res => {
      userListApprove.value = res.data;
    });
    getProductOptions();
    customerList().then(res => {
      // åªå¤åˆ¶éœ€è¦çš„字段,避免将组件引用放入响应式对象
      customerOption.value = (Array.isArray(res) ? res : []).map(item => ({
        id: item.id,
        quotationNo: item.quotationNo || '',
        customer: item.customer || '',
        salesperson: item.salesperson || '',
        quotationDate: item.quotationDate || '',
        validDate: item.validDate || '',
        paymentMethod: item.paymentMethod || '',
        status: item.status || '草稿',
        // å®¡æ‰¹äººï¼ˆç”¨äºŽç¼–辑时反显)
        approveUserIds: item.approveUserIds || '',
        remark: item.remark || '',
        products: item.products ? item.products.map(product => ({
          productId: product.productId || '',
          product: product.product || product.productName || '',
          specificationId: product.specificationId || '',
          specification: product.specification || '',
          quantity: product.quantity || 0,
          unit: product.unit || '',
          unitPrice: product.unitPrice || 0,
          amount: product.amount || 0
        })) : [],
        subtotal: item.subtotal || 0,
        freight: item.freight || 0,
        otherFee: item.otherFee || 0,
        discountRate: item.discountRate || 0,
        discountAmount: item.discountAmount || 0,
        totalAmount: item.totalAmount || 0
      }))
      pagination.total = res.data.total
    }
  })
    customerList().then((res) => {
        // åªå¤åˆ¶éœ€è¦çš„字段,避免将组件引用放入响应式对象
        customerOption.value = (Array.isArray(res) ? res : []).map(item => ({
      id: item.id,
      customerName: item.customerName || '',
      taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || ''
    }))
    });
}
        customerName: item.customerName || "",
        taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || "",
      }));
    });
  };
  const getProductOptions = () => {
    // è¿”回 Promise,便于编辑时 await ç¡®ä¿èƒ½åæ˜¾
    return productTreeList().then(res => {
      productOptions.value = convertIdToValue(res);
      return productOptions.value;
    });
  };
  function convertIdToValue(data) {
    return data.map(item => {
      const { id, children, ...rest } = item;
      const newItem = {
        ...rest,
        value: id, // å°† id æ”¹ä¸º value
      };
      if (children && children.length > 0) {
        newItem.children = convertIdToValue(children);
      }
onMounted(()=>{
  handleSearch()
})
      return newItem;
    });
  }
  // æ ¹æ®åç§°åæŸ¥èŠ‚ç‚¹ id,便于仅存名称时的反显
  function findNodeIdByLabel(nodes, label) {
    if (!label) return null;
    for (let i = 0; i < nodes.length; i++) {
      const node = nodes[i];
      if (node.label === label) return node.value;
      if (node.children && node.children.length > 0) {
        const found = findNodeIdByLabel(node.children, label);
        if (found !== null && found !== undefined) return found;
      }
    }
    return null;
  }
  const getModels = (value, row) => {
    if (!row) return;
    // å¦‚果清空选择,则清空相关字段
    if (!value) {
      row.productId = "";
      row.product = "";
      row.modelOptions = [];
      row.specificationId = "";
      row.specification = "";
      row.unit = "";
      return;
    }
    // æ›´æ–° productId(v-model å·²ç»è‡ªåŠ¨æ›´æ–°ï¼Œè¿™é‡Œç¡®ä¿ä¸€è‡´æ€§ï¼‰
    row.productId = value;
    // æ‰¾åˆ°å¯¹åº”çš„ label å¹¶èµ‹å€¼ç»™ row.product
    const label = findNodeById(productOptions.value, value);
    if (label) {
      row.product = label;
    }
    // èŽ·å–è§„æ ¼åž‹å·åˆ—è¡¨ï¼Œè®¾ç½®åˆ°å½“å‰è¡Œçš„ modelOptions
    modelList({ id: value }).then(res => {
      row.modelOptions = res || [];
    });
  };
  const getProductModel = (value, row) => {
    if (!row) return;
    // å¦‚果清空选择,则清空相关字段
    if (!value) {
      row.specificationId = "";
      row.specification = "";
      row.unit = "";
      return;
    }
    // æ›´æ–° specificationId(v-model å·²ç»è‡ªåŠ¨æ›´æ–°ï¼Œè¿™é‡Œç¡®ä¿ä¸€è‡´æ€§ï¼‰
    row.specificationId = value;
    const modelOptions = row.modelOptions || [];
    const index = modelOptions.findIndex(item => item.id === value);
    if (index !== -1) {
      row.specification = modelOptions[index].model;
      row.unit = modelOptions[index].unit;
    } else {
      row.specification = "";
      row.unit = "";
    }
  };
  const findNodeById = (nodes, productId) => {
    for (let i = 0; i < nodes.length; i++) {
      if (nodes[i].value === productId) {
        return nodes[i].label; // æ‰¾åˆ°èŠ‚ç‚¹ï¼Œè¿”å›ž label
      }
      if (nodes[i].children && nodes[i].children.length > 0) {
        const foundLabel = findNodeById(nodes[i].children, productId);
        if (foundLabel) {
          return foundLabel; // åœ¨å­èŠ‚ç‚¹ä¸­æ‰¾åˆ°ï¼Œè¿”å›ž label
        }
      }
    }
    return null; // æ²¡æœ‰æ‰¾åˆ°èŠ‚ç‚¹ï¼Œè¿”å›žnull
  };
  const handleView = row => {
    // åªå¤åˆ¶éœ€è¦çš„字段,避免将组件引用放入响应式对象
    currentQuotation.value = {
      quotationNo: row.quotationNo || "",
      customer: row.customer || "",
      salesperson: row.salesperson || "",
      quotationDate: row.quotationDate || "",
      validDate: row.validDate || "",
      paymentMethod: row.paymentMethod || "",
      status: row.status || "",
      remark: row.remark || "",
      products: row.products
        ? row.products.map(product => ({
            productId: product.productId || "",
            product: product.product || product.productName || "",
            specificationId: product.specificationId || "",
            specification: product.specification || "",
            quantity: product.quantity || 0,
            unit: product.unit || "",
            unitPrice: product.unitPrice || 0,
            amount: product.amount || 0,
          }))
        : [],
      totalAmount: row.totalAmount || 0,
    };
    viewDialogVisible.value = true;
  };
  const handleEdit = async row => {
    dialogTitle.value = "编辑报价";
    isEdit.value = true;
    editId.value = row.id;
    form.id = row.id || form.id || null;
    // å…ˆåŠ è½½äº§å“æ ‘æ•°æ®ï¼Œå¦åˆ™ el-tree-select æ— æ³•反显产品名称
    await getProductOptions();
    // åªå¤åˆ¶éœ€è¦çš„字段,避免将组件引用放入响应式对象
    form.quotationNo = row.quotationNo || "";
    form.customer = row.customer || "";
    form.salesperson = row.salesperson || "";
    form.quotationDate = row.quotationDate || "";
    form.validDate = row.validDate || "";
    form.paymentMethod = row.paymentMethod || "";
    form.status = row.status || "草稿";
    form.remark = row.remark || "";
    form.products = row.products
      ? await Promise.all(
          row.products.map(async product => {
            const productName = product.product || product.productName || "";
            // ä¼˜å…ˆç”¨ productId;如果只有名称,尝试反查 id ä»¥ä¾¿æ ‘选择器反显
            const resolvedProductId = product.productId
              ? Number(product.productId)
              : findNodeIdByLabel(productOptions.value, productName) || "";
            // å¦‚果有产品ID,加载对应的规格型号列表
            let modelOptions = [];
            let resolvedSpecificationId = product.specificationId || "";
            if (resolvedProductId) {
              try {
                const res = await modelList({ id: resolvedProductId });
                modelOptions = res || [];
                // å¦‚果返回的数据没有 specificationId,但有 specification åç§°ï¼Œæ ¹æ®åç§°æŸ¥æ‰¾ ID
                if (!resolvedSpecificationId && product.specification) {
                  const foundModel = modelOptions.find(
                    item => item.model === product.specification
                  );
                  if (foundModel) {
                    resolvedSpecificationId = foundModel.id;
                  }
                }
              } catch (error) {
                console.error("加载规格型号失败:", error);
              }
            }
            return {
              productId: resolvedProductId,
              product: productName,
              specificationId: resolvedSpecificationId,
              specification: product.specification || "",
              quantity: product.quantity || 0,
              unit: product.unit || "",
              unitPrice: product.unitPrice || 0,
              amount: product.amount || 0,
              modelOptions: modelOptions, // ä¸ºæ¯è¡Œæ·»åŠ ç‹¬ç«‹çš„è§„æ ¼åž‹å·åˆ—è¡¨
            };
          })
        )
      : [];
    form.subtotal = row.subtotal || 0;
    form.freight = row.freight || 0;
    form.otherFee = row.otherFee || 0;
    form.discountRate = row.discountRate || 0;
    form.discountAmount = row.discountAmount || 0;
    form.totalAmount = row.totalAmount || 0;
    // åæ˜¾å®¡æ‰¹äºº
    if (row.approveUserIds) {
      const userIds = row.approveUserIds.split(",");
      approverNodes.value = userIds.map((userId, idx) => ({
        id: idx + 1,
        userId: parseInt(userId.trim()),
      }));
      nextApproverId = userIds.length + 1;
    } else {
      approverNodes.value = [{ id: 1, userId: null }];
      nextApproverId = 2;
    }
    // åŠ è½½ç”¨æˆ·åˆ—è¡¨
    let userLists = await userListNoPage();
    userList.value = (userLists.data || []).map(item => ({
      userId: item.userId,
      nickName: item.nickName || "",
      userName: item.userName || "",
    }));
    dialogVisible.value = true;
  };
  const handleDelete = row => {
    ElMessageBox.confirm("确认删除该报价单吗?", "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    }).then(() => {
      const index = quotationList.value.findIndex(item => item.id === row.id);
      if (index > -1) {
        deleteQuotation(row.id).then(res => {
          // console.log(res)
          if (res.code === 200) {
            ElMessage.success("删除成功");
            handleSearch();
          }
        });
        // quotationList.value.splice(index, 1)
        // pagination.total--
        // ElMessage.success('删除成功')
      }
    });
  };
  const resetForm = () => {
    form.customer = "";
    form.salesperson = "";
    form.quotationDate = "";
    form.validDate = "";
    form.paymentMethod = "";
    form.status = "草稿";
    form.remark = "";
    form.products = [];
    form.subtotal = 0;
    form.freight = 0;
    form.otherFee = 0;
    form.discountRate = 0;
    form.discountAmount = 0;
    form.totalAmount = 0;
  };
  const addProduct = () => {
    form.products.push({
      productId: "",
      product: "",
      productName: "",
      specificationId: "",
      specification: "",
      quantity: 1,
      unit: "",
      unitPrice: 0,
      amount: 0,
      modelOptions: [], // ä¸ºæ¯è¡Œæ·»åŠ ç‹¬ç«‹çš„è§„æ ¼åž‹å·åˆ—è¡¨
    });
  };
  const removeProduct = index => {
    form.products.splice(index, 1);
    calculateSubtotal();
  };
  const calculateAmount = product => {
    product.amount = product.quantity * product.unitPrice;
    calculateSubtotal();
  };
  const calculateSubtotal = () => {
    form.subtotal = form.products.reduce(
      (sum, product) => sum + product.amount,
      0
    );
    calculateTotal();
  };
  const calculateTotal = () => {
    form.discountAmount = form.subtotal * (form.discountRate / 100);
    form.totalAmount =
      form.subtotal + form.freight + form.otherFee - form.discountAmount;
  };
  const handleCustomerChange = () => {
    // å¯ä»¥æ ¹æ®å®¢æˆ·ä¿¡æ¯è‡ªåŠ¨å¡«å……ä¸€äº›é»˜è®¤å€¼
  };
  const handleSubmit = () => {
    formRef.value.validate(valid => {
      if (valid) {
        if (form.products.length === 0) {
          ElMessage.warning("请至少添加一个产品");
          return;
        }
        // å®¡æ‰¹äººå¿…填校验
        const hasEmptyApprover = approverNodes.value.some(node => !node.userId);
        if (hasEmptyApprover) {
          ElMessage.error("请为所有审批节点选择审批人!");
          return;
        }
        // æ”¶é›†æ‰€æœ‰èŠ‚ç‚¹çš„å®¡æ‰¹äººid
        form.approveUserIds = approverNodes.value
          .map(node => node.userId)
          .join(",");
        // è®¡ç®—所有产品的单价总和
        form.totalAmount = form.products.reduce((sum, product) => {
          const price = Number(product.unitPrice) || 0;
          return sum + price;
        }, 0);
        if (isEdit.value) {
          // ç¼–辑
          const index = quotationList.value.findIndex(
            item => item.id === editId.value
          );
          if (index > -1) {
            updateQuotation(form).then(res => {
              // console.log(res)
              if (res.code === 200) {
                ElMessage.success("编辑成功");
                dialogVisible.value = false;
                handleSearch();
              }
            });
          }
        } else {
          // æ–°å¢ž
          addQuotation(form).then(res => {
            if (res.code === 200) {
              ElMessage.success("新增成功");
              dialogVisible.value = false;
              handleSearch();
            }
          });
        }
      }
    });
  };
  const handleCurrentChange = val => {
    pagination.currentPage = val.page;
    pagination.pageSize = val.limit;
    // åˆ†é¡µå˜åŒ–时重新查询列表
    handleSearch();
  };
  const handleSearch = () => {
    const params = {
      // åŽç«¯åˆ†é¡µå‚数:current / size
      current: pagination.currentPage,
      size: pagination.pageSize,
      ...searchForm,
    };
    getQuotationList(params).then(res => {
      // console.log(res)
      if (res.code === 200) {
        // åªå¤åˆ¶éœ€è¦çš„字段,避免将组件引用或其他对象放入响应式对象
        quotationList.value = (res.data.records || []).map(item => ({
          id: item.id,
          quotationNo: item.quotationNo || "",
          customer: item.customer || "",
          salesperson: item.salesperson || "",
          quotationDate: item.quotationDate || "",
          validDate: item.validDate || "",
          paymentMethod: item.paymentMethod || "",
          status: item.status || "草稿",
          // å®¡æ‰¹äººï¼ˆç”¨äºŽç¼–辑时反显)
          approveUserIds: item.approveUserIds || "",
          remark: item.remark || "",
          products: item.products
            ? item.products.map(product => ({
                productId: product.productId || "",
                product: product.product || product.productName || "",
                specificationId: product.specificationId || "",
                specification: product.specification || "",
                quantity: product.quantity || 0,
                unit: product.unit || "",
                unitPrice: product.unitPrice || 0,
                amount: product.amount || 0,
              }))
            : [],
          subtotal: item.subtotal || 0,
          freight: item.freight || 0,
          otherFee: item.otherFee || 0,
          discountRate: item.discountRate || 0,
          discountAmount: item.discountAmount || 0,
          totalAmount: item.totalAmount || 0,
        }));
        pagination.total = res.data.total;
      }
    });
    customerList().then(res => {
      // åªå¤åˆ¶éœ€è¦çš„字段,避免将组件引用放入响应式对象
      customerOption.value = (Array.isArray(res) ? res : []).map(item => ({
        id: item.id,
        customerName: item.customerName || "",
        taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || "",
      }));
    });
  };
  onMounted(() => {
    handleSearch();
  });
</script>
<style scoped lang="scss">
.search-row {
  margin-bottom: 20px;
}
.quotation-form-container {
  padding: 10px 0;
  max-height: calc(100vh - 200px);
  overflow-y: auto;
  &::-webkit-scrollbar {
    width: 6px;
    height: 6px;
  .search-row {
    margin-bottom: 20px;
  }
  &::-webkit-scrollbar-thumb {
    background: #c1c1c1;
    border-radius: 3px;
    &:hover {
      background: #a8a8a8;
  .quotation-form-container {
    padding: 10px 0;
    max-height: calc(100vh - 200px);
    overflow-y: auto;
    &::-webkit-scrollbar {
      width: 6px;
      height: 6px;
    }
    &::-webkit-scrollbar-thumb {
      background: #c1c1c1;
      border-radius: 3px;
      &:hover {
        background: #a8a8a8;
      }
    }
  }
}
.quotation-form {
  .el-form-item {
    margin-bottom: 22px;
  .quotation-form {
    .el-form-item {
      margin-bottom: 22px;
    }
  }
}
.form-card {
  margin-bottom: 24px;
  border-radius: 8px;
  transition: all 0.3s ease;
  &:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08) !important;
  }
  :deep(.el-card__header) {
    padding: 16px 20px;
    background: linear-gradient(135deg, #f5f7fa 0%, #ffffff 100%);
    border-bottom: 1px solid #ebeef5;
  }
  :deep(.el-card__body) {
    padding: 20px;
  }
}
  .form-card {
    margin-bottom: 24px;
    border-radius: 8px;
    transition: all 0.3s ease;
.card-header-wrapper {
  display: flex;
  align-items: center;
  gap: 8px;
  .card-icon {
    font-size: 18px;
    color: #409eff;
  }
  .card-title {
    font-weight: 600;
    font-size: 16px;
    color: #303133;
    flex: 1;
  }
  .header-btn {
    margin-left: auto;
  }
}
    &:hover {
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08) !important;
    }
.form-content {
  padding: 8px 0;
}
    :deep(.el-card__header) {
      padding: 16px 20px;
      background: linear-gradient(135deg, #f5f7fa 0%, #ffffff 100%);
      border-bottom: 1px solid #ebeef5;
    }
.product-table-form-item {
  margin-bottom: 0;
  :deep(.el-form-item__content) {
    margin-left: 0 !important;
    :deep(.el-card__body) {
      padding: 20px;
    }
  }
  :deep(.el-form-item__label) {
    width: auto;
    min-width: auto;
  }
}
.approver-nodes-container {
  display: flex;
  flex-wrap: wrap;
  gap: 24px;
  padding: 12px 0;
}
.approver-node-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 16px;
  background: #f8f9fa;
  border-radius: 8px;
  border: 1px solid #e4e7ed;
  transition: all 0.3s ease;
  min-width: 180px;
  &:hover {
    border-color: #409eff;
    background: #f0f7ff;
    box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
  }
}
.approver-node-label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  color: #606266;
  .node-step {
    display: inline-flex;
  .card-header-wrapper {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    background: #409eff;
    color: #fff;
    border-radius: 50%;
    font-size: 12px;
    font-weight: 600;
  }
  .node-text {
    font-weight: 500;
  }
  .arrow-icon {
    color: #909399;
    font-size: 14px;
  }
}
    gap: 8px;
.approver-select {
  width: 100%;
  min-width: 150px;
}
    .card-icon {
      font-size: 18px;
      color: #409eff;
    }
.remove-btn {
  margin-top: 4px;
}
.product-table {
  :deep(.el-table__header) {
    background-color: #f5f7fa;
    th {
      background-color: #f5f7fa !important;
      color: #606266;
    .card-title {
      font-weight: 600;
      font-size: 16px;
      color: #303133;
      flex: 1;
    }
    .header-btn {
      margin-left: auto;
    }
  }
  :deep(.el-table__row) {
    &:hover {
      background-color: #f5f7fa;
  .form-content {
    padding: 8px 0;
  }
  .product-table-form-item {
    margin-bottom: 0;
    :deep(.el-form-item__content) {
      margin-left: 0 !important;
    }
    :deep(.el-form-item__label) {
      width: auto;
      min-width: auto;
    }
  }
  :deep(.el-table__cell) {
  .approver-nodes-container {
    display: flex;
    flex-wrap: wrap;
    gap: 24px;
    padding: 12px 0;
  }
}
.dialog-footer {
  text-align: right;
}
// å“åº”式优化
@media (max-width: 1200px) {
  .approver-nodes-container {
    gap: 16px;
  }
  .approver-node-item {
    min-width: 160px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    padding: 16px;
    background: #f8f9fa;
    border-radius: 8px;
    border: 1px solid #e4e7ed;
    transition: all 0.3s ease;
    min-width: 180px;
    &:hover {
      border-color: #409eff;
      background: #f0f7ff;
      box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
    }
  }
}
  .approver-node-label {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 14px;
    color: #606266;
    .node-step {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      width: 24px;
      height: 24px;
      background: #409eff;
      color: #fff;
      border-radius: 50%;
      font-size: 12px;
      font-weight: 600;
    }
    .node-text {
      font-weight: 500;
    }
    .arrow-icon {
      color: #909399;
      font-size: 14px;
    }
  }
  .approver-select {
    width: 100%;
    min-width: 150px;
  }
  .remove-btn {
    margin-top: 4px;
  }
  .product-table {
    :deep(.el-table__header) {
      background-color: #f5f7fa;
      th {
        background-color: #f5f7fa !important;
        color: #606266;
        font-weight: 600;
      }
    }
    :deep(.el-table__row) {
      &:hover {
        background-color: #f5f7fa;
      }
    }
    :deep(.el-table__cell) {
      padding: 12px 0;
    }
  }
  .dialog-footer {
    text-align: right;
  }
  // å“åº”式优化
  @media (max-width: 1200px) {
    .approver-nodes-container {
      gap: 16px;
    }
    .approver-node-item {
      min-width: 160px;
    }
  }
</style>