已修改21个文件
已删除19个文件
10122 ■■■■ 文件已修改
src/api/financialManagement/salesRefund.js 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/login.js 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/invoiceEntry.js 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/paymentEntry.js 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/paymentLedger.js 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/procurementInvoiceLedger.js 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/indicatorStats.js 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/invoiceLedger.js 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/invoiceRegistration.js 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/receiptPayment.js 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/viewIndex.js 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/user.js 242 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/request.js 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/payable/payment.vue 438 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/payable/purchaseIn.vue 313 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/receivable/receipt.vue 1363 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/salesRefund/components/ReceiptandRefundPopupWindow.vue 227 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/salesRefund/index.vue 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/index.vue 592 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/invoiceEntry/components/ExpandTable.vue 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/invoiceEntry/components/Modal.vue 718 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/invoiceEntry/index.vue 311 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/paymentEntry/index.vue 597 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/paymentHistory/index.vue 270 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/paymentLedger/index.vue 494 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementInvoiceLedger/Modal/EditModal.vue 238 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementInvoiceLedger/Modal/UploadModal.vue 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementInvoiceLedger/index.vue 330 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/index.vue 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/processRouteItem/index.vue 162 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productStructure/Detail/index.vue 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/safeProduction/safetyTrainingAssessment/detail.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/invoiceLedger/index.vue 438 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/invoiceRegistration/index.vue 814 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPayment/index.vue 605 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPaymentHistory/index.vue 279 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPaymentLedger/index.vue 486 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesLedger/index.vue 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/financialManagement/salesRefund.js
ÎļþÒÑɾ³ý
src/api/login.js
@@ -1,4 +1,5 @@
import request from '@/utils/request'
import request from '@/utils/request'
import { getToken } from '@/utils/auth'
// ç™»å½•方法
export function login(username, password, code, uuid) {
@@ -32,12 +33,14 @@
}
// èŽ·å–ç”¨æˆ·è¯¦ç»†ä¿¡æ¯
export function getInfo() {
  return request({
    url: '/getInfo',
    method: 'get'
  })
}
export function getInfo() {
  const token = getToken()
  return request({
    url: '/getInfo',
    headers: token ? { Authorization: `Bearer ${token}` } : {},
    method: 'get'
  })
}
// é€€å‡ºæ–¹æ³•
export function logout() {
src/api/procurementManagement/invoiceEntry.js
ÎļþÒÑɾ³ý
src/api/procurementManagement/paymentEntry.js
ÎļþÒÑɾ³ý
src/api/procurementManagement/paymentLedger.js
@@ -1,20 +1,20 @@
// é‡‡è´­å°è´¦é¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
/** ä»˜æ¬¾å°è´¦ - ä¾›åº”商往来汇总 */
export function paymentLedgerList(query) {
  return request({
    url: "/purchase/paymentRegistration/supplierNameListPage",
    url: "/purchase/report/supplierTransactions",
    method: "get",
    params: query,
  });
}
// åˆ†é¡µæŸ¥è¯¢
export function paymentRecordList(supplierId) {
/** ä»˜æ¬¾å°è´¦ - ä¾›åº”商往来明细 */
export function paymentRecordList(params) {
  return request({
    url: "/purchase/paymentRegistration/supplierNameListPageDetails",
    url: "/purchase/report/supplierTransactionsDetails",
    method: "get",
    params: supplierId,
    params,
  });
}
src/api/procurementManagement/procurementInvoiceLedger.js
ÎļþÒÑɾ³ý
src/api/salesManagement/indicatorStats.js
@@ -18,3 +18,21 @@
    params: query,
  });
}
// å®¢æˆ·å¾€æ¥åˆ—表
export function customewTransactions(query) {
  return request({
    url: "/metricStatistics/customewTransactions",
    method: "get",
    params: query,
  });
}
// å®¢æˆ·å¾€æ¥æ˜Žç»†
export function customewTransactionsDetails(query) {
  return request({
    url: "/metricStatistics/customewTransactionsDetails",
    method: "get",
    params: query,
  });
}
src/api/salesManagement/invoiceLedger.js
@@ -1,92 +1,10 @@
// å¼€ç¥¨å°è´¦é¡µé¢æŽ¥å£
import request from '@/utils/request'
// åˆ†é¡µæŸ¥è¯¢
export function invoiceLedgerList(query) {
    return request({
        url: '/invoiceLedger/page',
        method: 'get',
        params: query
    })
}
// æ–°å¢ž
export function invoiceLedgerSaveOrUpdate(query) {
    return request({
        url: '/invoiceLedger/saveOrUpdate',
        method: 'post',
        data: query
    })
}
// å¼€ç¥¨å°è´¦åˆ é™¤
export function invoiceLedgerDel(query) {
    return request({
        url: '/invoiceLedger/del',
        method: 'delete',
        data: query
    })
}
// è¯¦æƒ…查询
export function invoiceLedgerDetail(query) {
    return request({
        url: '/invoiceLedger/info',
        method: 'get',
        params: query
    })
}
// é™„件提交
export function commitFile(query) {
    return request({
        url: '/invoiceLedger/commitFile',
        method: 'post',
        data: query
    })
}
// å¼€ç¥¨å°è´¦éƒ¨åˆ†ä¹ŸæŸ¥è¯¢
export function invoiceLedgerListNoPage(query) {
    return request({
        url: '/invoiceLedger/list',
        method: 'get',
        data: query
    })
}
// åˆ†é¡µæŸ¥è¯¢
/** å›žæ¬¾å°è´¦ - å®¢æˆ·é”€å”®è´¦æˆ·åˆ†é¡µ */
export function invoiceLedgerSalesAccount(query) {
    return request({
        url: '/invoiceLedger/salesAccount',
        method: 'get',
        params: query
    })
  return request({
    url: '/invoiceLedger/salesAccount',
    method: 'get',
    params: query
  })
}
// äº§å“å¼€ç¥¨è®°å½•分页查询
export function registrationProductPage(query) {
    return request({
        url: '/invoiceLedger/registrationProductPage',
        method: 'get',
        params: query
    })
}
// äº§å“å¼€ç¥¨è¯¦æƒ…查询
export function invoiceLedgerProductInfo(query) {
    return request({
        url: '/invoiceLedger/invoiceLedgerProductInfo',
        method: 'get',
        params: query
    })
}
export function delInvoiceLedgerByRegProductId(invoiceRegistrationProductId) {
    return request({
        url: '/invoiceLedger/delInvoiceLedger/'+ invoiceRegistrationProductId,
        method: 'delete'
    })
}
src/api/salesManagement/invoiceRegistration.js
ÎļþÒÑɾ³ý
src/api/salesManagement/receiptPayment.js
@@ -1,84 +1,10 @@
// å¼€ç¥¨ç™»è®°é¡µé¢æŽ¥å£
import request from '@/utils/request'
// æ–°å¢ž/修改
export function receiptPaymentSaveOrUpdate(query) {
    return request({
        url: '/receiptPayment/saveOrUpdate',
        method: 'post',
        data: query
    })
}
// å®¢æˆ·å¾€æ¥è®°å½•查询
/** å›žæ¬¾å°è´¦ - å®¢æˆ·å¾€æ¥è®°å½• */
export function customerInteractions(query) {
    return request({
        url: '/receiptPayment/customerInteractions',
        method: 'get',
        params: query
    })
}
// è¯¦æƒ…
export function receiptPaymentInfo(query) {
    return request({
        url: '/receiptPayment/info',
        method: 'get',
        params: query
    })
}
// åˆ é™¤
export function receiptPaymentDel(query) {
    return request({
        url: '/receiptPayment/del',
        method: 'delete',
        data: query
    })
}
// æŸ¥è¯¢å·²ç»ç»‘定发票的开票台账
export function bindInvoiceNoRegPage(query) {
    return request({
        url: '/sales/product/listPageSalesLedger',
        method: 'get',
        params: query
    })
}
// å¼€ç¥¨å°è´¦è¯¦æƒ…
export function invoiceInfo(query) {
    return request({
        url: '/receiptPayment/invoiceInfo',
        method: 'get',
        params: query
    })
}
// è¯¢å›žæ¬¾è®°å½•
export function receiptPaymentHistoryList(query) {
    return request({
        url: '/receiptPayment/receiptPaymentHistoryList',
        method: 'get',
        params: query
    })
}
/**
 * æŸ¥è¯¢å›žæ¬¾è®°å½•分页查询
 */
export function receiptPaymentHistoryListPage(query) {
    return request({
        url: '/receiptPayment/receiptPaymentHistoryListPage',
        method: 'get',
        params: query
    })
}
export function receiptPaymentHistoryListNoPage(query) {
    return request({
        url: '/receiptPayment/receiptPaymentHistoryListNoPage',
        method: 'get',
        params: query
    })
  return request({
    url: '/receiptPayment/customerInteractions',
    method: 'get',
    params: query
  })
}
src/api/viewIndex.js
@@ -326,3 +326,59 @@
    method: "get",
  });
};
export const productionOverview = () => {
  return request({
    url: "/home/productionOverview",
    method: "get",
    headers: {
      handleAuthError: false,
    },
  });
};
export const productionRealtimeBoard = () => {
  return request({
    url: "/home/productionRealtimeBoard",
    method: "get",
    headers: {
      handleAuthError: false,
    },
  });
};
export const productionOrderProgress = (params = {}) => {
  const safePageNum = Math.max(1, Number(params.pageNum || 1));
  const safePageSize = Math.min(50, Math.max(1, Number(params.pageSize || 10)));
  const safeTab = ["all", "inProgress", "completed", "paused"].includes(params.tab)
    ? params.tab
    : "all";
  return request({
    url: "/home/productionOrderProgress",
    method: "get",
    params: {
      ...params,
      tab: safeTab,
      pageNum: safePageNum,
      pageSize: safePageSize,
    },
    headers: {
      handleAuthError: false,
    },
  });
};
export const todayProductionPlan = (params = {}) => {
  const safeLimit = Math.min(20, Math.max(1, Number(params.limit || 4)));
  return request({
    url: "/home/todayProductionPlan",
    method: "get",
    params: {
      ...params,
      limit: safeLimit,
    },
    headers: {
      handleAuthError: false,
    },
  });
};
src/router/index.js
@@ -131,127 +131,6 @@
      },
    ],
  },
  // è´¢åŠ¡ç®¡ç†æ¨¡å—è·¯ç”±
  {
    path: "/financial",
    component: Layout,
    hidden: false,
    redirect: "/financial/general-ledger",
    alwaysShow: true,
    meta: { title: "财务管理", icon: "money" },
    children: [
      {
        path: "sales-out",
        component: () => import("@/views/financialManagement/receivable/salesOut.vue"),
        name: "SalesOut",
        meta: { title: "销售出库" },
      },
      {
        path: "sales-return",
        component: () => import("@/views/financialManagement/receivable/salesReturn.vue"),
        name: "SalesReturn",
        meta: { title: "销售退货" },
      },
      {
        path: "invoice-apply",
        component: () => import("@/views/financialManagement/receivable/invoiceApply.vue"),
        name: "InvoiceApply",
        meta: { title: "开票申请" },
      },
      {
        path: "output-invoice",
        component: () => import("@/views/financialManagement/receivable/outputInvoice.vue"),
        name: "OutputInvoice",
        meta: { title: "销项发票" },
      },
      {
        path: "receipt",
        component: () => import("@/views/financialManagement/receivable/receipt.vue"),
        name: "Receipt",
        meta: { title: "收款单" },
      },
      {
        path: "receivable-reconciliation",
        component: () => import("@/views/financialManagement/receivable/reconciliation.vue"),
        name: "ReceivableReconciliation",
        meta: { title: "应收对账" },
      },
      {
        path: "purchase-in",
        component: () => import("@/views/financialManagement/payable/purchaseIn.vue"),
        name: "PurchaseIn",
        meta: { title: "采购入库" },
      },
      {
        path: "purchase-return",
        component: () => import("@/views/financialManagement/payable/purchaseReturn.vue"),
        name: "PurchaseReturn",
        meta: { title: "采购退货" },
      },
      {
        path: "input-invoice",
        component: () => import("@/views/financialManagement/payable/input-invoice.vue"),
        name: "InputInvoice",
        meta: { title: "进项发票" },
      },
      {
        path: "payment-apply",
        component: () => import("@/views/financialManagement/payable/paymentApply.vue"),
        name: "PaymentApply",
        meta: { title: "付款申请" },
      },
      {
        path: "payment",
        component: () => import("@/views/financialManagement/payable/payment.vue"),
        name: "Payment",
        meta: { title: "付款单" },
      },
      {
        path: "payable-reconciliation",
        component: () => import("@/views/financialManagement/payable/reconciliation.vue"),
        name: "PayableReconciliation",
        meta: { title: "应付对账" },
      },
      {
        path: "fixed-assets",
        component: () => import("@/views/financialManagement/assets/fixedAssets.vue"),
        name: "FixedAssets",
        meta: { title: "固定资产" },
      },
      {
        path: "intangible-assets",
        component: () => import("@/views/financialManagement/assets/intangibleAssets.vue"),
        name: "IntangibleAssets",
        meta: { title: "无形资产" },
      },
      {
        path: "general-ledger",
        component: () => import("@/views/financialManagement/generalLedger/index.vue"),
        name: "GeneralLedger",
        meta: { title: "总帐科目" },
      },
      {
        path: "voucher",
        component: () => import("@/views/financialManagement/voucher/index.vue"),
        name: "Voucher",
        meta: { title: "凭证" },
      },
      {
        path: "voucher-general-ledger",
        component: () => import("@/views/financialManagement/voucher/generalLedger.vue"),
        name: "VoucherGeneralLedger",
        meta: { title: "科目总帐" },
      },
      {
        path: "voucher-detail-ledger",
        component: () => import("@/views/financialManagement/voucher/detailLedger.vue"),
        name: "VoucherDetailLedger",
        meta: { title: "科目明细帐" },
      },
    ],
  },
];
// åŠ¨æ€è·¯ç”±ï¼ŒåŸºäºŽç”¨æˆ·æƒé™åŠ¨æ€åŽ»åŠ è½½
src/store/modules/user.js
@@ -1,12 +1,12 @@
import {login, logout, getInfo, loginCheck, loginCheckFactory,tideLogin} from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { isHttp, isEmpty } from "@/utils/validate"
import defAva from '@/assets/images/profile.jpg'
import { defineStore } from 'pinia'
const useUserStore = defineStore(
  'user',
  {
import {login, logout, getInfo, loginCheck, loginCheckFactory,tideLogin} from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { isHttp, isEmpty } from "@/utils/validate"
import defAva from '@/assets/images/profile.jpg'
import { defineStore } from 'pinia'
const useUserStore = defineStore(
  'user',
  {
    state: () => ({
      token: getToken(),
      id: '',
@@ -15,66 +15,72 @@
      roles: [],
      permissions: [],
      aiEnabled: 0
    }),
    actions: {
      // ç™»å½•
      login(userInfo) {
        const username = userInfo.username.trim()
        const password = userInfo.password
        const code = userInfo.code
        const uuid = userInfo.uuid
        return new Promise((resolve, reject) => {
          login(username, password, code, uuid).then(res => {
            setToken(res.token)
            this.token = res.token
            resolve()
          }).catch(error => {
            reject(error)
          })
        })
      },
      getCurrentTime() {
        const now = new Date();
        const year = now.getFullYear();       // èŽ·å–å¹´ä»½
        const month = String(now.getMonth() + 1).padStart(2, '0');  // æœˆä»½ä»Ž0开始,要+1,并补零
        const day = String(now.getDate()).padStart(2, '0');         // æ—¥æœŸè¡¥é›¶
        const hours = String(now.getHours()).padStart(2, '0');      // å°æ—¶è¡¥é›¶
        const minutes = String(now.getMinutes()).padStart(2, '0');  // åˆ†é’Ÿè¡¥é›¶
        const seconds = String(now.getSeconds()).padStart(2, '0');  // ç§’数补零
        return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
      },
      // èŽ·å–ç”¨æˆ·ä¿¡æ¯
      getInfo() {
        return new Promise((resolve, reject) => {
          getInfo().then(res => {
            const user = res.user
            let avatar = user.avatar || ""
            avatar = import.meta.env.VITE_APP_BASE_API + '/profile/' + avatar
            if (res.roles && res.roles.length > 0) { // éªŒè¯è¿”回的roles是否是一个非空数组
              this.roles = res.roles
              this.permissions = res.permissions
            } else {
              this.roles = ['ROLE_DEFAULT']
            }
            this.id = user.userId
            this.name = user.userName
            this.avatar = avatar
            this.currentFactoryName = user.currentFactoryName
            this.nickName = user.nickName
            this.roleName = user.roles[0].roleName
            this.currentDeptId = user.tenantId
            this.currentLoginTime = this.getCurrentTime()
            this.aiEnabled = Number(res.aiEnabled) === 1 ? 1 : 0
            resolve(res)
    }),
    actions: {
      // ç™»å½•
      login(userInfo) {
        const username = userInfo.username.trim()
        const password = userInfo.password
        const code = userInfo.code
        const uuid = userInfo.uuid
        return new Promise((resolve, reject) => {
          login(username, password, code, uuid).then(res => {
            const token = res?.token || res?.data?.token
            if (!token) {
              reject(new Error('未获取到登录令牌'))
              return
            }
            setToken(token)
            this.token = token
            resolve()
          }).catch(error => {
            reject(error)
          })
        })
      },
      // é€€å‡ºç³»ç»Ÿ
      logOut() {
        return new Promise((resolve, reject) => {
          logout(this.token).then(() => {
      },
      getCurrentTime() {
        const now = new Date();
        const year = now.getFullYear();       // èŽ·å–å¹´ä»½
        const month = String(now.getMonth() + 1).padStart(2, '0');  // æœˆä»½ä»Ž0开始,要+1,并补零
        const day = String(now.getDate()).padStart(2, '0');         // æ—¥æœŸè¡¥é›¶
        const hours = String(now.getHours()).padStart(2, '0');      // å°æ—¶è¡¥é›¶
        const minutes = String(now.getMinutes()).padStart(2, '0');  // åˆ†é’Ÿè¡¥é›¶
        const seconds = String(now.getSeconds()).padStart(2, '0');  // ç§’数补零
        return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
      },
      // èŽ·å–ç”¨æˆ·ä¿¡æ¯
      getInfo() {
        return new Promise((resolve, reject) => {
          getInfo().then(res => {
            const data = res?.data ?? res
            const user = data.user || {}
            let avatar = user.avatar || ""
            avatar = import.meta.env.VITE_APP_BASE_API + '/profile/' + avatar
            if (data.roles && data.roles.length > 0) { // éªŒè¯è¿”回的roles是否是一个非空数组
              this.roles = data.roles
              this.permissions = data.permissions
            } else {
              this.roles = ['ROLE_DEFAULT']
            }
            this.id = user.userId || ''
            this.name = user.userName || ''
            this.avatar = avatar
            this.currentFactoryName = user.currentFactoryName || ''
            this.nickName = user.nickName || ''
            this.roleName = Array.isArray(user.roles) && user.roles.length > 0 ? (user.roles[0].roleName || '') : ''
            this.currentDeptId = user.tenantId || ''
            this.currentLoginTime = this.getCurrentTime()
            this.aiEnabled = Number(data.aiEnabled) === 1 ? 1 : 0
            resolve(data)
          }).catch(error => {
            reject(error)
          })
        })
      },
      // é€€å‡ºç³»ç»Ÿ
      logOut() {
        return new Promise((resolve, reject) => {
          logout(this.token).then(() => {
            this.token = ''
            this.roles = []
            this.permissions = []
@@ -84,51 +90,61 @@
          }).catch(error => {
            reject(error)
          })
        })
      },
      // ç™»å½•校验
      loginCheck(userInfo) {
        const username = userInfo.username.trim()
        const password = userInfo.password
        return new Promise((resolve, reject) => {
          loginCheck(username, password).then(res => {
            resolve(res)
          }).catch(error => {
            reject(error)
          })
        })
      },
      // éƒ¨é—¨ç™»å½•
      loginCheckFactory(userInfo) {
        const username = userInfo.username.trim()
        const password = userInfo.password
        return new Promise((resolve, reject) => {
          loginCheckFactory(username, password).then(res => {
            setToken(res.token)
            this.token = res.token
            resolve()
          }).catch(error => {
            reject(error)
          })
        })
      },
      TideLogin(code) {
        return new Promise((resolve, reject) => {
          tideLogin(code)
              .then((res) => {
                setToken(res.token);
                this.token = res.token
                Vue.prototype.uploadHeader = {
                  Authorization: "Bearer " + res.token,
                };
                resolve();
              })
              .catch((error) => {
                reject(error);
              });
        });
      },
    }
  })
export default useUserStore
        })
      },
      // ç™»å½•校验
      loginCheck(userInfo) {
        const username = userInfo.username.trim()
        const password = userInfo.password
        return new Promise((resolve, reject) => {
          loginCheck(username, password).then(res => {
            resolve(res)
          }).catch(error => {
            reject(error)
          })
        })
      },
      // éƒ¨é—¨ç™»å½•
      loginCheckFactory(userInfo) {
        const username = userInfo.username.trim()
        const password = userInfo.password
        return new Promise((resolve, reject) => {
          loginCheckFactory(username, password).then(res => {
            const token = res?.token || res?.data?.token
            if (!token) {
              reject(new Error('未获取到登录令牌'))
              return
            }
            setToken(token)
            this.token = token
            resolve()
          }).catch(error => {
            reject(error)
          })
        })
      },
      TideLogin(code) {
        return new Promise((resolve, reject) => {
          tideLogin(code)
              .then((res) => {
                const token = res?.token || res?.data?.token
                if (!token) {
                  reject(new Error('未获取到登录令牌'))
                  return
                }
                setToken(token);
                this.token = token
                Vue.prototype.uploadHeader = {
                  Authorization: "Bearer " + token,
                };
                resolve();
              })
              .catch((error) => {
                reject(error);
              });
        });
      },
    }
  })
export default useUserStore
src/utils/request.js
@@ -21,11 +21,12 @@
})
// request拦截器
service.interceptors.request.use(config => {
service.interceptors.request.use(config => {
  config.headers = config.headers || {}
  // æ˜¯å¦éœ€è¦è®¾ç½® token
  const isToken = (config.headers || {}).isToken === false
  const isToken = config.headers.isToken === false
  // æ˜¯å¦éœ€è¦é˜²æ­¢æ•°æ®é‡å¤æäº¤
  const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
  const isRepeatSubmit = config.headers.repeatSubmit === false
  if (getToken() && !isToken) {
    config.headers['Authorization'] = 'Bearer ' + getToken() // è®©æ¯ä¸ªè¯·æ±‚携带自定义token è¯·æ ¹æ®å®žé™…情况自行修改
  }
@@ -66,23 +67,27 @@
    }
  }
  return config
}, error => {
    console.log(error)
    Promise.reject(error)
})
}, error => {
    console.log(error)
    return Promise.reject(error)
})
// å“åº”拦截器
service.interceptors.response.use(res => {
    // æœªè®¾ç½®çŠ¶æ€ç åˆ™é»˜è®¤æˆåŠŸçŠ¶æ€
    const code = res.data.code || 200
    const code = res.data.code || 200
    const handleAuthError = (res.config && res.config.headers && res.config.headers.handleAuthError) !== false
    // èŽ·å–é”™è¯¯ä¿¡æ¯
    const msg = errorCode[code] || res.data.msg || errorCode['default']
    // äºŒè¿›åˆ¶æ•°æ®åˆ™ç›´æŽ¥è¿”回
    if (res.request.responseType ===  'blob' || res.request.responseType ===  'arraybuffer') {
      return res.data
    }
    if (code === 401) {
      if (!isRelogin.show) {
    if (code === 401) {
      if (!handleAuthError) {
        return Promise.reject(new Error(msg))
      }
      if (!isRelogin.show) {
        isRelogin.show = true
        ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
          isRelogin.show = false
src/views/financialManagement/payable/payment.vue
@@ -1,68 +1,76 @@
<template>
  <div class="app-container">
    <el-form :model="filters" :inline="true">
    <el-form :model="filters"
             :inline="true">
      <el-form-item label="付款单号:">
        <el-input v-model="filters.paymentNumber" placeholder="请输入付款单号" clearable style="width: 200px;" />
        <el-input v-model="filters.paymentNumber"
                  placeholder="请输入付款单号"
                  clearable
                  style="width: 200px;" />
      </el-form-item>
      <el-form-item label="供应商:">
        <el-select v-model="filters.supplierId" placeholder="请选择供应商" clearable filterable style="width: 200px;">
          <el-option
            v-for="item in supplierList"
            :key="item.id"
            :label="item.supplierName"
            :value="item.id"
          />
        <el-select v-model="filters.supplierId"
                   placeholder="请选择供应商"
                   clearable
                   filterable
                   style="width: 200px;">
          <el-option v-for="item in supplierList"
                     :key="item.id"
                     :label="item.supplierName"
                     :value="item.id" />
        </el-select>
      </el-form-item>
      <el-form-item label="付款方式:">
        <el-select v-model="filters.paymentMethod" placeholder="请选择付款方式" clearable style="width: 150px;">
          <el-option
            v-for="item in checkout_payment"
            :key="item.value"
            :label="item.label"
            :value="item.value"
          />
        <el-select v-model="filters.paymentMethod"
                   placeholder="请选择付款方式"
                   clearable
                   style="width: 150px;">
          <el-option v-for="item in checkout_payment"
                     :key="item.value"
                     :label="item.label"
                     :value="item.value" />
        </el-select>
      </el-form-item>
      <el-form-item label="付款日期:">
        <el-date-picker
          v-model="filters.dateRange"
          type="daterange"
          value-format="YYYY-MM-DD"
          format="YYYY-MM-DD"
          range-separator="至"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
          clearable
          style="width: 240px;"
        />
        <el-date-picker v-model="filters.dateRange"
                        type="daterange"
                        value-format="YYYY-MM-DD"
                        format="YYYY-MM-DD"
                        range-separator="至"
                        start-placeholder="开始日期"
                        end-placeholder="结束日期"
                        clearable
                        style="width: 240px;" />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="onSearch">搜索</el-button>
        <el-button type="primary"
                   @click="onSearch">搜索</el-button>
        <el-button @click="resetFilters">重置</el-button>
      </el-form-item>
    </el-form>
    <div class="table_list">
      <div class="actions">
        <div>
          <el-statistic title="本页付款合计" :value="totalPaymentAmount" :precision="2" prefix="Â¥" />
          <el-statistic title="本页付款合计"
                        :value="totalPaymentAmount"
                        :precision="2"
                        prefix="Â¥" />
        </div>
        <div>
          <el-button @click="handleExport" icon="Download">导出</el-button>
          <el-button @click="handleExport"
                     icon="Download">导出</el-button>
        </div>
      </div>
      <PIMTable
        rowKey="id"
        :column="columns"
        :tableData="dataList"
        :tableLoading="tableLoading"
        :page="{
      <PIMTable rowKey="id"
                :column="columns"
                :tableData="dataList"
                :tableLoading="tableLoading"
                :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
          total: pagination.total,
        }"
        @pagination="changePage"
      >
                @pagination="changePage">
        <template #amount="{ row }">
          <span class="text-danger">Â¥{{ formatMoney(row.amount) }}</span>
        </template>
@@ -70,7 +78,10 @@
          <el-tag>{{ getPaymentMethodLabel(row.paymentMethod) }}</el-tag>
        </template>
        <template #operation="{ row }">
          <el-button type="danger" link @click="handleDelete(row)">删除</el-button>
          <el-button :disabled="row.accountStatemen"
                     type="danger"
                     link
                     @click="handleDelete(row)">删除</el-button>
        </template>
      </PIMTable>
    </div>
@@ -78,194 +89,211 @@
</template>
<script setup>
import { ref, reactive, computed, onMounted, getCurrentInstance } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { getOptions } from "@/api/procurementManagement/procurementLedger.js";
import {
  listPageAccountPurchasePayment,
  deleteAccountPurchasePayment,
} from "@/api/financialManagement/accountPurchasePayment.js";
  import { ref, reactive, computed, onMounted, getCurrentInstance } from "vue";
  import { ElMessage, ElMessageBox } from "element-plus";
  import { getOptions } from "@/api/procurementManagement/procurementLedger.js";
  import {
    listPageAccountPurchasePayment,
    deleteAccountPurchasePayment,
  } from "@/api/financialManagement/accountPurchasePayment.js";
defineOptions({
  name: "付款单",
});
const { proxy } = getCurrentInstance();
const { checkout_payment } = proxy.useDict("checkout_payment");
const filters = reactive({
  paymentNumber: "",
  supplierId: "",
  paymentMethod: "",
  dateRange: [],
});
const pagination = reactive({
  currentPage: 1,
  pageSize: 10,
  total: 0,
});
const columns = [
  { label: "付款单号", prop: "paymentNumber", width: "150" },
  { label: "关联申请单", prop: "invoiceApplicationNo", width: "150" },
  { label: "供应商", prop: "supplierName", width: "180" },
  { label: "付款日期", prop: "paymentDate", width: "120" },
  { label: "付款金额", prop: "amount", dataType: "slot", slot: "amount" },
  { label: "付款方式", prop: "paymentMethod", dataType: "slot", slot: "paymentMethod", width: "120" },
  { label: "备注", prop: "remark", showOverflowTooltip: true },
  { label: "操作", prop: "operation", dataType: "slot", slot: "operation", width: "80", fixed: "right" },
];
const dataList = ref([]);
const tableLoading = ref(false);
const supplierList = ref([]);
const totalPaymentAmount = computed(() =>
  dataList.value.reduce((sum, item) => sum + Number(item.amount ?? 0), 0)
);
const formatMoney = (value) => {
  if (value === undefined || value === null) return "0.00";
  return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};
const getPaymentMethodLabel = (value) => {
  if (value === undefined || value === null || value === "") return "-";
  const item = checkout_payment.value?.find((m) => String(m.value) === String(value));
  return item?.label ?? value;
};
const normalizeTableRow = (row) => ({
  ...row,
  paymentNumber: row.paymentNumber ?? row.paymentCode,
  invoiceApplicationNo: row.invoiceApplicationNo ?? row.applyCode ?? "",
  amount: row.paymentAmount ?? row.amount,
  bankAccountNum: row.bankAccountNum ?? row.bankAccount ?? "",
  bankAccountName: row.bankAccountName ?? row.bankName ?? "",
});
const getSupplierList = () => {
  getOptions().then((res) => {
    if (res.code === 200) {
      supplierList.value = res.data ?? [];
    }
  });
};
const appendFilterParams = (params) => {
  if (filters.paymentNumber) {
    params.paymentNumber = filters.paymentNumber;
  }
  if (filters.supplierId) {
    params.supplierId = filters.supplierId;
  }
  if (filters.paymentMethod) {
    params.paymentMethod = filters.paymentMethod;
  }
  if (filters.dateRange?.length === 2) {
    params.startDate = filters.dateRange[0];
    params.endDate = filters.dateRange[1];
  }
  return params;
};
const buildListParams = () =>
  appendFilterParams({
    current: pagination.currentPage,
    size: pagination.pageSize,
  defineOptions({
    name: "付款单",
  });
const buildExportParams = () => appendFilterParams({});
  const { proxy } = getCurrentInstance();
  const { checkout_payment } = proxy.useDict("checkout_payment");
const handleExport = () => {
  proxy.download(
    "/accountPurchasePayment/exportAccountPurchasePayment",
    buildExportParams(),
    `付款单_${Date.now()}.xlsx`
  const filters = reactive({
    paymentNumber: "",
    supplierId: "",
    paymentMethod: "",
    dateRange: [],
  });
  const pagination = reactive({
    currentPage: 1,
    pageSize: 10,
    total: 0,
  });
  const columns = [
    { label: "付款单号", prop: "paymentNumber", width: "150" },
    { label: "关联申请单", prop: "invoiceApplicationNo", width: "150" },
    { label: "供应商", prop: "supplierName", width: "180" },
    { label: "付款日期", prop: "paymentDate", width: "120" },
    { label: "付款金额", prop: "amount", dataType: "slot", slot: "amount" },
    {
      label: "付款方式",
      prop: "paymentMethod",
      dataType: "slot",
      slot: "paymentMethod",
      width: "120",
    },
    { label: "备注", prop: "remark", showOverflowTooltip: true },
    {
      label: "操作",
      prop: "operation",
      dataType: "slot",
      slot: "operation",
      width: "80",
      fixed: "right",
    },
  ];
  const dataList = ref([]);
  const tableLoading = ref(false);
  const supplierList = ref([]);
  const totalPaymentAmount = computed(() =>
    dataList.value.reduce((sum, item) => sum + Number(item.amount ?? 0), 0)
  );
};
const getTableData = () => {
  tableLoading.value = true;
  listPageAccountPurchasePayment(buildListParams())
    .then((res) => {
  const formatMoney = value => {
    if (value === undefined || value === null) return "0.00";
    return Number(value)
      .toFixed(2)
      .replace(/\B(?=(\d{3})+(?!\d))/g, ",");
  };
  const getPaymentMethodLabel = value => {
    if (value === undefined || value === null || value === "") return "-";
    const item = checkout_payment.value?.find(
      m => String(m.value) === String(value)
    );
    return item?.label ?? value;
  };
  const normalizeTableRow = row => ({
    ...row,
    paymentNumber: row.paymentNumber ?? row.paymentCode,
    invoiceApplicationNo: row.invoiceApplicationNo ?? row.applyCode ?? "",
    amount: row.paymentAmount ?? row.amount,
    bankAccountNum: row.bankAccountNum ?? row.bankAccount ?? "",
    bankAccountName: row.bankAccountName ?? row.bankName ?? "",
  });
  const getSupplierList = () => {
    getOptions().then(res => {
      if (res.code === 200) {
        dataList.value = (res.data?.records ?? []).map(normalizeTableRow);
        pagination.total = res.data?.total ?? 0;
      } else {
        dataList.value = [];
        pagination.total = 0;
        ElMessage.error(res.msg || "查询失败");
        supplierList.value = res.data ?? [];
      }
    })
    .catch(() => {
      dataList.value = [];
      pagination.total = 0;
      ElMessage.error("查询失败");
    })
    .finally(() => {
      tableLoading.value = false;
    });
};
  };
const onSearch = () => {
  pagination.currentPage = 1;
  getTableData();
};
  const appendFilterParams = params => {
    if (filters.paymentNumber) {
      params.paymentNumber = filters.paymentNumber;
    }
    if (filters.supplierId) {
      params.supplierId = filters.supplierId;
    }
    if (filters.paymentMethod) {
      params.paymentMethod = filters.paymentMethod;
    }
    if (filters.dateRange?.length === 2) {
      params.startDate = filters.dateRange[0];
      params.endDate = filters.dateRange[1];
    }
    return params;
  };
const resetFilters = () => {
  filters.paymentNumber = "";
  filters.supplierId = "";
  filters.paymentMethod = "";
  filters.dateRange = [];
  pagination.currentPage = 1;
  getTableData();
};
  const buildListParams = () =>
    appendFilterParams({
      current: pagination.currentPage,
      size: pagination.pageSize,
    });
const changePage = ({ page, limit }) => {
  pagination.currentPage = page;
  pagination.pageSize = limit;
  getTableData();
};
  const buildExportParams = () => appendFilterParams({});
const handleDelete = (row) => {
  ElMessageBox.confirm(`确认删除付款单「${row.paymentNumber}」吗?`, "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  }).then(() => {
    deleteAccountPurchasePayment([row.id])
      .then((res) => {
  const handleExport = () => {
    proxy.download(
      "/accountPurchasePayment/exportAccountPurchasePayment",
      buildExportParams(),
      `付款单_${Date.now()}.xlsx`
    );
  };
  const getTableData = () => {
    tableLoading.value = true;
    listPageAccountPurchasePayment(buildListParams())
      .then(res => {
        if (res.code === 200) {
          ElMessage.success("删除成功");
          getTableData();
          dataList.value = (res.data?.records ?? []).map(normalizeTableRow);
          pagination.total = res.data?.total ?? 0;
        } else {
          ElMessage.error(res.msg || "删除失败");
          dataList.value = [];
          pagination.total = 0;
          ElMessage.error(res.msg || "查询失败");
        }
      })
      .catch(() => {
        ElMessage.error("删除失败");
        dataList.value = [];
        pagination.total = 0;
        ElMessage.error("查询失败");
      })
      .finally(() => {
        tableLoading.value = false;
      });
  });
};
  };
onMounted(() => {
  getSupplierList();
  getTableData();
});
  const onSearch = () => {
    pagination.currentPage = 1;
    getTableData();
  };
  const resetFilters = () => {
    filters.paymentNumber = "";
    filters.supplierId = "";
    filters.paymentMethod = "";
    filters.dateRange = [];
    pagination.currentPage = 1;
    getTableData();
  };
  const changePage = ({ page, limit }) => {
    pagination.currentPage = page;
    pagination.pageSize = limit;
    getTableData();
  };
  const handleDelete = row => {
    ElMessageBox.confirm(`确认删除付款单「${row.paymentNumber}」吗?`, "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    }).then(() => {
      deleteAccountPurchasePayment([row.id])
        .then(res => {
          if (res.code === 200) {
            ElMessage.success("删除成功");
            getTableData();
          } else {
            ElMessage.error(res.msg || "删除失败");
          }
        })
        .catch(() => {
          ElMessage.error("删除失败");
        });
    });
  };
  onMounted(() => {
    getSupplierList();
    getTableData();
  });
</script>
<style lang="scss" scoped>
.actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 15px;
}
  .actions {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 15px;
  }
.text-danger {
  color: #f56c6c;
  font-weight: bold;
}
  .text-danger {
    color: #f56c6c;
    font-weight: bold;
  }
</style>
src/views/financialManagement/payable/purchaseIn.vue
@@ -1,34 +1,39 @@
<template>
  <!-- é‡‡è´­å…¥åº“ -->
  <div class="app-container">
    <el-form :model="filters" :inline="true">
    <el-form :model="filters"
             :inline="true">
      <el-form-item label="入库单号:">
        <el-input v-model="filters.inboundBatches" placeholder="请输入入库单号" clearable style="width: 200px;" />
        <el-input v-model="filters.inboundBatches"
                  placeholder="请输入入库单号"
                  clearable
                  style="width: 200px;" />
      </el-form-item>
      <el-form-item label="供应商:">
        <el-select v-model="filters.supplierId" placeholder="请选择供应商" clearable filterable style="width: 200px;">
          <el-option
            v-for="item in supplierList"
            :key="item.id"
            :label="item.supplierName"
            :value="item.id"
          />
        <el-select v-model="filters.supplierId"
                   placeholder="请选择供应商"
                   clearable
                   filterable
                   style="width: 200px;">
          <el-option v-for="item in supplierList"
                     :key="item.id"
                     :label="item.supplierName"
                     :value="item.id" />
        </el-select>
      </el-form-item>
      <el-form-item label="入库日期:">
        <el-date-picker
          v-model="filters.dateRange"
          value-format="YYYY-MM-DD"
          format="YYYY-MM-DD"
          type="daterange"
          range-separator="至"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
          clearable
        />
        <el-date-picker v-model="filters.dateRange"
                        value-format="YYYY-MM-DD"
                        format="YYYY-MM-DD"
                        type="daterange"
                        range-separator="至"
                        start-placeholder="开始日期"
                        end-placeholder="结束日期"
                        clearable />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="onSearch">搜索</el-button>
        <el-button type="primary"
                   @click="onSearch">搜索</el-button>
        <el-button @click="resetFilters">重置</el-button>
      </el-form-item>
    </el-form>
@@ -36,21 +41,20 @@
      <div class="actions">
        <div></div>
        <div>
          <el-button @click="handleOut" icon="Download">导出</el-button>
          <el-button @click="handleOut"
                     icon="Download">导出</el-button>
        </div>
      </div>
      <PIMTable
        rowKey="id"
        :column="columns"
        :tableData="dataList"
        :tableLoading="tableLoading"
        :page="{
      <PIMTable rowKey="id"
                :column="columns"
                :tableData="dataList"
                :tableLoading="tableLoading"
                :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
          total: pagination.total,
        }"
        @pagination="changePage"
      >
                @pagination="changePage">
        <template #inboundDate="{ row }">
          {{ row.inboundDate ?? row.InboundDate ?? "" }}
        </template>
@@ -60,136 +64,149 @@
</template>
<script setup>
import { ref, reactive, onMounted, getCurrentInstance } from "vue";
import { ElMessage } from "element-plus";
import { listPageAccountPurchase } from "@/api/financialManagement/accountPurchase";
import { listSupplier } from "@/api/basicData/supplierManageFile.js";
  import { ref, reactive, onMounted, getCurrentInstance } from "vue";
  import { ElMessage } from "element-plus";
  import { listPageAccountPurchase } from "@/api/financialManagement/accountPurchase";
  import { listSupplier } from "@/api/basicData/supplierManageFile.js";
defineOptions({
  name: "采购入库",
});
const { proxy } = getCurrentInstance();
const filters = reactive({
  inboundBatches: "",
  supplierId: "",
  dateRange: [],
});
const pagination = reactive({
  currentPage: 1,
  pageSize: 10,
  total: 0,
});
const columns = [
  { label: "入库单号", prop: "inboundBatches", minWidth: "150" },
  { label: "供应商", prop: "supplierName", minWidth: "180" },
  {
    label: "入库日期",
    prop: "inboundDate",
    minWidth: "170",
    dataType: "slot",
    slot: "inboundDate",
  },
  { label: "产品名称", prop: "productName", minWidth: "140" },
  { label: "产品规格", prop: "specificationModel", minWidth: "140" },
  { label: "采购订单号", prop: "purchaseContractNumber", minWidth: "150" },
];
const dataList = ref([]);
const tableLoading = ref(false);
const supplierList = ref([]);
const buildFilterParams = () => {
  const params = {};
  if (filters.inboundBatches) {
    params.inboundBatches = filters.inboundBatches;
  }
  if (filters.supplierId) {
    params.supplierId = filters.supplierId;
  }
  if (filters.dateRange?.length === 2) {
    params.startDate = filters.dateRange[0];
    params.endDate = filters.dateRange[1];
  }
  return params;
};
const getSupplierList = () => {
  listSupplier({ current: -1, size: -1, isWhite: 0 }).then((res) => {
    if (res.code === 200) {
      supplierList.value = res.data?.records ?? [];
    }
  defineOptions({
    name: "采购入库",
  });
};
const onSearch = () => {
  pagination.currentPage = 1;
  getTableData();
};
  const { proxy } = getCurrentInstance();
const getTableData = () => {
  tableLoading.value = true;
  listPageAccountPurchase({
    ...buildFilterParams(),
    current: pagination.currentPage,
    size: pagination.pageSize,
  })
    .then((res) => {
      const ok = res.code === 200 || res.code === 0;
      if (ok && res.data) {
        pagination.total = res.data.total ?? 0;
        dataList.value = res.data.records ?? [];
      } else {
        ElMessage.error(res.msg || "查询失败");
  const filters = reactive({
    inboundBatches: "",
    supplierId: "",
    dateRange: [],
  });
  const pagination = reactive({
    currentPage: 1,
    pageSize: 10,
    total: 0,
  });
  const columns = [
    { label: "入库单号", prop: "inboundBatches", minWidth: "150" },
    { label: "供应商", prop: "supplierName", minWidth: "180" },
    {
      label: "入库日期",
      prop: "inboundDate",
      minWidth: "170",
      dataType: "slot",
      slot: "inboundDate",
    },
    { label: "产品名称", prop: "productName", minWidth: "140" },
    { label: "产品规格", prop: "specificationModel", minWidth: "140" },
    {
      label: "金额",
      prop: "inboundAmount",
      minWidth: "120",
      align: "right",
      formatData: val =>
        val === null || val === undefined || val === ""
          ? ""
          : Number(val).toLocaleString("zh-CN", {
              minimumFractionDigits: 2,
              maximumFractionDigits: 2,
            }),
    },
    { label: "采购订单号", prop: "purchaseContractNumber", minWidth: "150" },
  ];
  const dataList = ref([]);
  const tableLoading = ref(false);
  const supplierList = ref([]);
  const buildFilterParams = () => {
    const params = {};
    if (filters.inboundBatches) {
      params.inboundBatches = filters.inboundBatches;
    }
    if (filters.supplierId) {
      params.supplierId = filters.supplierId;
    }
    if (filters.dateRange?.length === 2) {
      params.startDate = filters.dateRange[0];
      params.endDate = filters.dateRange[1];
    }
    return params;
  };
  const getSupplierList = () => {
    listSupplier({ current: -1, size: -1, isWhite: 0 }).then(res => {
      if (res.code === 200) {
        supplierList.value = res.data?.records ?? [];
      }
    });
  };
  const onSearch = () => {
    pagination.currentPage = 1;
    getTableData();
  };
  const getTableData = () => {
    tableLoading.value = true;
    listPageAccountPurchase({
      ...buildFilterParams(),
      current: pagination.currentPage,
      size: pagination.pageSize,
    })
      .then(res => {
        const ok = res.code === 200 || res.code === 0;
        if (ok && res.data) {
          pagination.total = res.data.total ?? 0;
          dataList.value = res.data.records ?? [];
        } else {
          ElMessage.error(res.msg || "查询失败");
          dataList.value = [];
          pagination.total = 0;
        }
      })
      .catch(() => {
        dataList.value = [];
        pagination.total = 0;
      }
    })
    .catch(() => {
      dataList.value = [];
      pagination.total = 0;
      ElMessage.error("查询失败");
    })
    .finally(() => {
      tableLoading.value = false;
    });
};
        ElMessage.error("查询失败");
      })
      .finally(() => {
        tableLoading.value = false;
      });
  };
const resetFilters = () => {
  filters.inboundBatches = "";
  filters.supplierId = "";
  filters.dateRange = [];
  pagination.currentPage = 1;
  getTableData();
};
  const resetFilters = () => {
    filters.inboundBatches = "";
    filters.supplierId = "";
    filters.dateRange = [];
    pagination.currentPage = 1;
    getTableData();
  };
const changePage = ({ page, limit }) => {
  pagination.currentPage = page;
  pagination.pageSize = limit;
  getTableData();
};
  const changePage = ({ page, limit }) => {
    pagination.currentPage = page;
    pagination.pageSize = limit;
    getTableData();
  };
const handleOut = () => {
  proxy.download(
    "/accountPurchase/exportAccountPurchaseInbound",
    buildFilterParams(),
    `采购入库_${Date.now()}.xlsx`
  );
};
  const handleOut = () => {
    proxy.download(
      "/accountPurchase/exportAccountPurchaseInbound",
      buildFilterParams(),
      `采购入库_${Date.now()}.xlsx`
    );
  };
onMounted(() => {
  getSupplierList();
  getTableData();
});
  onMounted(() => {
    getSupplierList();
    getTableData();
  });
</script>
<style lang="scss" scoped>
.actions {
  display: flex;
  justify-content: space-between;
  margin-bottom: 15px;
}
  .actions {
    display: flex;
    justify-content: space-between;
    margin-bottom: 15px;
  }
</style>
src/views/financialManagement/receivable/receipt.vue
@@ -1,64 +1,80 @@
<template>
  <div class="app-container">
    <el-form :model="filters" :inline="true">
    <el-form :model="filters"
             :inline="true">
      <el-form-item label="收款单号:">
        <el-input v-model="filters.collectionNumber" placeholder="请输入收款单号" clearable style="width: 200px;" />
        <el-input v-model="filters.collectionNumber"
                  placeholder="请输入收款单号"
                  clearable
                  style="width: 200px;" />
      </el-form-item>
      <el-form-item label="客户:">
        <el-select v-model="filters.customerId" placeholder="请选择客户" clearable filterable style="width: 200px;">
          <el-option v-for="item in customerList" :key="item.id" :label="item.customerName" :value="item.id" />
        <el-select v-model="filters.customerId"
                   placeholder="请选择客户"
                   clearable
                   filterable
                   style="width: 200px;">
          <el-option v-for="item in customerList"
                     :key="item.id"
                     :label="item.customerName"
                     :value="item.id" />
        </el-select>
      </el-form-item>
      <el-form-item label="收款方式:">
        <el-select v-model="filters.collectionMethod" placeholder="请选择收款方式" clearable style="width: 150px;">
          <el-option
            v-for="item in payment_methods"
            :key="item.value"
            :label="item.label"
            :value="item.value"
          />
        <el-select v-model="filters.collectionMethod"
                   placeholder="请选择收款方式"
                   clearable
                   style="width: 150px;">
          <el-option v-for="item in payment_methods"
                     :key="item.value"
                     :label="item.label"
                     :value="item.value" />
        </el-select>
      </el-form-item>
      <el-form-item label="收款日期:">
        <el-date-picker
          v-model="filters.dateRange"
          type="daterange"
          value-format="YYYY-MM-DD"
          format="YYYY-MM-DD"
          range-separator="至"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
          clearable
          style="width: 240px;"
        />
        <el-date-picker v-model="filters.dateRange"
                        type="daterange"
                        value-format="YYYY-MM-DD"
                        format="YYYY-MM-DD"
                        range-separator="至"
                        start-placeholder="开始日期"
                        end-placeholder="结束日期"
                        clearable
                        style="width: 240px;" />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="onSearch">搜索</el-button>
        <el-button type="primary"
                   @click="onSearch">搜索</el-button>
        <el-button @click="resetFilters">重置</el-button>
      </el-form-item>
    </el-form>
    <div class="table_list">
      <div class="actions">
        <div>
          <el-statistic title="本页收款合计" :value="totalReceiptAmount" :precision="2" prefix="Â¥" />
          <el-statistic title="本页收款合计"
                        :value="totalReceiptAmount"
                        :precision="2"
                        prefix="Â¥" />
        </div>
        <div>
          <el-button type="primary" @click="add" icon="Plus">新增收款</el-button>
          <el-button type="success" @click="handleExport" icon="Download">导出</el-button>
          <el-button type="primary"
                     @click="add"
                     icon="Plus">新增收款</el-button>
          <el-button type="success"
                     @click="handleExport"
                     icon="Download">导出</el-button>
        </div>
      </div>
      <PIMTable
        rowKey="id"
        v-loading="tableLoading"
        :column="columns"
        :tableData="dataList"
        :page="{
      <PIMTable rowKey="id"
                v-loading="tableLoading"
                :column="columns"
                :tableData="dataList"
                :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
          total: pagination.total,
        }"
        @pagination="changePage"
      >
                @pagination="changePage">
        <template #amount="{ row }">
          <span class="text-success">Â¥{{ formatMoney(row.amount) }}</span>
        </template>
@@ -66,60 +82,71 @@
          <span>{{ getReceiptMethodLabel(row.receiptMethod) }}</span>
        </template>
        <template #operation="{ row }">
          <el-button type="primary" link @click="view(row)">查看</el-button>
          <el-button type="primary" link @click="edit(row)">编辑</el-button>
          <el-button type="danger" link @click="handleDelete(row)">删除</el-button>
          <el-button type="primary"
                     link
                     @click="view(row)">查看</el-button>
          <el-button :disabled="row.accountStatemen"
                     type="primary"
                     link
                     @click="edit(row)">编辑</el-button>
          <el-button :disabled="row.accountStatemen"
                     type="danger"
                     link
                     @click="handleDelete(row)">删除</el-button>
        </template>
      </PIMTable>
    </div>
    <FormDialog
      :title="dialogTitle"
      v-model="dialogVisible"
      width="800px"
      :operation-type="isView ? 'detail' : ''"
      @confirm="submitForm"
      @cancel="closeDialog"
    >
      <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
    <FormDialog :title="dialogTitle"
                v-model="dialogVisible"
                width="800px"
                :operation-type="isView ? 'detail' : ''"
                @confirm="submitForm"
                @cancel="closeDialog">
      <el-form :model="form"
               :rules="rules"
               ref="formRef"
               label-width="120px">
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="收款单号" prop="receiptCode">
              <el-input v-model="form.receiptCode" placeholder="系统自动生成" disabled />
            <el-form-item label="收款单号"
                          prop="receiptCode">
              <el-input v-model="form.receiptCode"
                        placeholder="系统自动生成"
                        disabled />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="客户" prop="customerId">
              <el-select
                v-model="form.customerId"
                placeholder="请选择客户"
                style="width: 100%;"
                :disabled="isEdit || isView"
                filterable
                @change="handleCustomerChange"
              >
                <el-option v-for="item in customerList" :key="item.id" :label="item.customerName" :value="item.id" />
            <el-form-item label="客户"
                          prop="customerId">
              <el-select v-model="form.customerId"
                         placeholder="请选择客户"
                         style="width: 100%;"
                         :disabled="isEdit || isView"
                         filterable
                         @change="handleCustomerChange">
                <el-option v-for="item in customerList"
                           :key="item.id"
                           :label="item.customerName"
                           :value="item.id" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="关联单据" prop="stockOutRecordIds">
              <el-input
                :model-value="outboundBatchDisplayText"
                placeholder="请先选择客户"
                readonly
                :disabled="!form.customerId || isEdit || isView"
                class="outbound-batch-input"
                @click="handleOutboundInputClick"
              >
                <template v-if="!isEdit && !isView" #append>
                  <el-button
                    :disabled="!form.customerId"
                    :loading="outboundBatchLoading"
                    @click.stop="openOutboundSelectDialog"
                  >
            <el-form-item label="关联单据"
                          prop="stockOutRecordIds">
              <el-input :model-value="outboundBatchDisplayText"
                        placeholder="请先选择客户"
                        readonly
                        :disabled="!form.customerId || isEdit || isView"
                        class="outbound-batch-input"
                        @click="handleOutboundInputClick">
                <template v-if="!isEdit && !isView"
                          #append>
                  <el-button :disabled="!form.customerId"
                             :loading="outboundBatchLoading"
                             @click.stop="openOutboundSelectDialog">
                    é€‰æ‹©
                  </el-button>
                </template>
@@ -129,95 +156,123 @@
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="收款日期" prop="receiptDate">
              <el-date-picker
                v-model="form.receiptDate"
                type="date"
                placeholder="选择日期"
                value-format="YYYY-MM-DD"
                style="width: 100%;"
                :disabled="isView"
              />
            <el-form-item label="收款日期"
                          prop="receiptDate">
              <el-date-picker v-model="form.receiptDate"
                              type="date"
                              placeholder="选择日期"
                              value-format="YYYY-MM-DD"
                              style="width: 100%;"
                              :disabled="isView" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="收款金额" prop="amount">
              <el-input-number
                v-model="form.amount"
                :min="0"
                :precision="2"
                style="width: 100%;"
                :disabled="isView"
                placeholder="根据关联单据自动汇总,可修改"
              />
            <el-form-item label="收款金额"
                          prop="amount">
              <el-input-number v-model="form.amount"
                               :min="0"
                               :precision="2"
                               style="width: 100%;"
                               :disabled="isView"
                               placeholder="根据关联单据自动汇总,可修改" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="收款方式" prop="receiptMethod">
              <el-select
                v-model="form.receiptMethod"
                placeholder="请选择收款方式"
                style="width: 100%;"
                :disabled="isView"
              >
                <el-option
                  v-for="item in payment_methods"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
            <el-form-item label="收款方式"
                          prop="receiptMethod">
              <el-select v-model="form.receiptMethod"
                         placeholder="请选择收款方式"
                         style="width: 100%;"
                         :disabled="isView">
                <el-option v-for="item in payment_methods"
                           :key="item.value"
                           :label="item.label"
                           :value="item.value" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" :disabled="isView" />
        <el-form-item label="备注"
                      prop="remark">
          <el-input v-model="form.remark"
                    type="textarea"
                    :rows="3"
                    placeholder="请输入备注"
                    :disabled="isView" />
        </el-form-item>
      </el-form>
      <template v-if="!isView" #footer>
        <el-button type="primary" :loading="submitLoading" @click="submitForm">确定</el-button>
      <template v-if="!isView"
                #footer>
        <el-button type="primary"
                   :loading="submitLoading"
                   @click="submitForm">确定</el-button>
        <el-button @click="closeDialog">取消</el-button>
      </template>
    </FormDialog>
    <el-dialog
      v-model="outboundSelectVisible"
      title="选择关联单据"
      width="1200px"
      append-to-body
      destroy-on-close
      :close-on-click-modal="false"
      @closed="handleOutboundDialogClosed"
    >
      <el-table
        ref="outboundTableRef"
        v-loading="outboundBatchLoading"
        :data="outboundBatchList"
        row-key="id"
        border
        stripe
        max-height="480"
        @selection-change="handleOutboundDialogSelectionChange"
      >
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column prop="outboundBatches" label="出库单号" min-width="140" show-overflow-tooltip />
        <el-table-column prop="customerName" label="客户名称" min-width="120" show-overflow-tooltip />
        <el-table-column prop="productName" label="产品名称" min-width="120" show-overflow-tooltip />
        <el-table-column prop="specificationModel" label="规格型号" min-width="140" show-overflow-tooltip />
        <el-table-column prop="salesContractNo" label="销售合同号" min-width="140" show-overflow-tooltip />
        <el-table-column prop="shippingNo" label="发货单号" min-width="130" show-overflow-tooltip />
        <el-table-column prop="shippingDate" label="发货日期" width="110" align="center" />
        <el-table-column prop="outboundAmount" label="出库金额" width="110" align="right">
    <el-dialog v-model="outboundSelectVisible"
               title="选择关联单据"
               width="1200px"
               append-to-body
               destroy-on-close
               :close-on-click-modal="false"
               @closed="handleOutboundDialogClosed">
      <el-table ref="outboundTableRef"
                v-loading="outboundBatchLoading"
                :data="outboundBatchList"
                row-key="id"
                border
                stripe
                max-height="480"
                @selection-change="handleOutboundDialogSelectionChange">
        <el-table-column type="selection"
                         width="55"
                         align="center" />
        <el-table-column prop="outboundBatches"
                         label="出库单号"
                         min-width="140"
                         show-overflow-tooltip />
        <el-table-column prop="customerName"
                         label="客户名称"
                         min-width="120"
                         show-overflow-tooltip />
        <el-table-column prop="productName"
                         label="产品名称"
                         min-width="120"
                         show-overflow-tooltip />
        <el-table-column prop="specificationModel"
                         label="规格型号"
                         min-width="140"
                         show-overflow-tooltip />
        <el-table-column prop="salesContractNo"
                         label="销售合同号"
                         min-width="140"
                         show-overflow-tooltip />
        <el-table-column prop="shippingNo"
                         label="发货单号"
                         min-width="130"
                         show-overflow-tooltip />
        <el-table-column prop="shippingDate"
                         label="发货日期"
                         width="110"
                         align="center" />
        <el-table-column prop="outboundAmount"
                         label="出库金额"
                         width="110"
                         align="right">
          <template #default="{ row }">Â¥{{ formatMoney(row.outboundAmount) }}</template>
        </el-table-column>
        <el-table-column prop="taxRate" label="税率" width="80" align="center">
        <el-table-column prop="taxRate"
                         label="税率"
                         width="80"
                         align="center">
          <template #default="{ row }">{{ row.taxRate }}%</template>
        </el-table-column>
      </el-table>
      <template #footer>
        <el-button type="primary" @click="confirmOutboundSelection">确定</el-button>
        <el-button type="primary"
                   @click="confirmOutboundSelection">确定</el-button>
        <el-button @click="outboundSelectVisible = false">取消</el-button>
      </template>
    </el-dialog>
@@ -225,516 +280,576 @@
</template>
<script setup>
import { ref, reactive, computed, onMounted, nextTick, getCurrentInstance } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { listCustomer } from "@/api/basicData/customer.js";
import {
  getOutboundBatchesByCustomer,
  addAccountSalesCollection,
  listPageAccountSalesCollection,
  updateAccountSalesCollection,
  deleteAccountSalesCollection,
} from "@/api/financialManagement/accountSalesCollection.js";
  import {
    ref,
    reactive,
    computed,
    onMounted,
    nextTick,
    getCurrentInstance,
  } from "vue";
  import { ElMessage, ElMessageBox } from "element-plus";
  import FormDialog from "@/components/Dialog/FormDialog.vue";
  import { listCustomer } from "@/api/basicData/customer.js";
  import {
    getOutboundBatchesByCustomer,
    addAccountSalesCollection,
    listPageAccountSalesCollection,
    updateAccountSalesCollection,
    deleteAccountSalesCollection,
  } from "@/api/financialManagement/accountSalesCollection.js";
defineOptions({
  name: "收款单",
});
const { proxy } = getCurrentInstance();
const { payment_methods } = proxy.useDict("payment_methods");
const filters = reactive({
  collectionNumber: "",
  customerId: "",
  collectionMethod: "",
  dateRange: [],
});
const pagination = reactive({
  currentPage: 1,
  pageSize: 10,
  total: 0,
});
const columns = [
  { label: "收款单号", prop: "receiptCode", width: "150" },
  { label: "客户名称", prop: "customerName", width: "180" },
  { label: "收款日期", prop: "receiptDate", width: "120" },
  { label: "收款金额", prop: "amount", dataType: "slot", slot: "amount" },
  { label: "收款方式", prop: "receiptMethod", dataType: "slot", slot: "receiptMethod", width: "120" },
  { label: "备注", prop: "remark", showOverflowTooltip: true },
  { label: "操作", prop: "operation", dataType: "slot", slot: "operation", width: "200", fixed: "right" },
];
const dataList = ref([]);
const tableLoading = ref(false);
const dialogVisible = ref(false);
const dialogTitle = ref("");
const formRef = ref(null);
const isEdit = ref(false);
const isView = ref(false);
const currentId = ref(null);
const submitLoading = ref(false);
const customerList = ref([]);
const outboundBatchList = ref([]);
const outboundBatchOptions = ref([]);
const outboundBatchLoading = ref(false);
const outboundSelectVisible = ref(false);
const outboundTableRef = ref(null);
const dialogOutboundSelection = ref([]);
const getReceiptMethodLabel = (value) => {
  if (value === undefined || value === null || value === "") return "-";
  const item = payment_methods.value?.find((m) => String(m.value) === String(value));
  return item?.label ?? value;
};
const getDefaultReceiptMethod = () => payment_methods.value?.[0]?.value ?? "";
const form = reactive({
  receiptCode: "",
  customerId: "",
  receiptDate: "",
  amount: 0,
  receiptMethod: "",
  stockOutRecordIds: [],
  outboundBatches: "",
  remark: "",
});
const rules = {
  customerId: [{ required: true, message: "请选择客户", trigger: "change" }],
  stockOutRecordIds: [{ required: true, type: "array", min: 1, message: "请选择关联单据", trigger: "change" }],
  receiptDate: [{ required: true, message: "请选择收款日期", trigger: "change" }],
  amount: [{ required: true, message: "请输入收款金额", trigger: "blur" }],
  receiptMethod: [{ required: true, message: "请选择收款方式", trigger: "change" }],
};
const totalReceiptAmount = computed(() =>
  dataList.value.reduce((sum, item) => sum + Number(item.amount || 0), 0)
);
const formatMoney = (value) => {
  if (value === undefined || value === null) return "0.00";
  return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};
const parseStockOutRecordIds = (value) => {
  if (!value) return [];
  if (Array.isArray(value)) return value;
  return String(value)
    .split(/[,,]/)
    .map((s) => s.trim())
    .filter(Boolean)
    .map((s) => (/^\d+$/.test(s) ? Number(s) : s));
};
const formatOutboundBatches = (value) => {
  if (value === undefined || value === null || value === "") return "";
  if (Array.isArray(value)) return value.filter(Boolean).join("、");
  return String(value)
    .split(/[,,]/)
    .map((s) => s.trim())
    .filter(Boolean)
    .join("、");
};
const normalizeTableRow = (row) => ({
  ...row,
  receiptCode: row.collectionNumber ?? row.receiptCode,
  receiptDate: row.collectionDate ?? row.receiptDate,
  amount: row.collectionAmount ?? row.amount,
  receiptMethod: row.collectionMethod ?? row.receiptMethod ?? "",
  stockOutRecordIds: row.stockOutRecordIds ?? row.stockOutRecordId ?? "",
  outboundBatches: formatOutboundBatches(row.outboundBatches),
});
const getCustomerList = () => {
  listCustomer({ current: -1, size: -1, type: 0 }).then((res) => {
    if (res.code === 200) {
      customerList.value = res.data?.records || [];
    }
  });
};
const normalizeOutboundBatchOptions = (data) => {
  const list = Array.isArray(data) ? data : [];
  return list.map((item, index) => {
    if (typeof item === "string" || typeof item === "number") {
      const text = String(item);
      return { label: text, value: text, outboundAmount: 0 };
    }
    const label =
      item.outboundBatches ??
      item.batchNo ??
      item.shippingNo ??
      item.outboundNo ??
      item.label ??
      `出库单${index + 1}`;
    const value = item.id ?? item.stockOutRecordId ?? label;
    return { label: String(label), value, outboundAmount: Number(item.outboundAmount) || 0 };
  });
};
const isSameOutboundId = (a, b) => String(a) === String(b);
const getOutboundRowId = (row) => row?.id ?? row?.stockOutRecordId;
const outboundBatchDisplayText = computed(() => {
  if (isEdit.value || isView.value) {
    return form.outboundBatches || "";
  }
  if (form.outboundBatches) return form.outboundBatches;
  const ids = form.stockOutRecordIds || [];
  if (!ids.length) return "";
  const labels = outboundBatchOptions.value
    .filter((opt) => ids.some((id) => isSameOutboundId(id, opt.value)))
    .map((opt) => opt.label);
  if (labels.length) return labels.join("、");
  return ids.join("、");
});
const handleOutboundInputClick = () => {
  if (isEdit.value || isView.value) return;
  openOutboundSelectDialog();
};
/** ä¸ºå·²é€‰ ID è¡¥å…¨é€‰é¡¹ï¼ˆç¼–辑/查看回显) */
const ensureOutboundOptionsForSelected = () => {
  const ids = form.stockOutRecordIds || [];
  ids.forEach((id) => {
    const exists = outboundBatchOptions.value.some((opt) => isSameOutboundId(opt.value, id));
    if (exists) return;
    const fromList = outboundBatchList.value.find((row) => isSameOutboundId(getOutboundRowId(row), id));
    if (fromList) {
      const [option] = normalizeOutboundBatchOptions([fromList]);
      if (option) outboundBatchOptions.value.push(option);
      return;
    }
    outboundBatchOptions.value.push({
      label: String(id),
      value: id,
      outboundAmount: 0,
    });
  });
};
const syncCollectionAmount = () => {
  const selected = form.stockOutRecordIds || [];
  const sum = outboundBatchOptions.value
    .filter((opt) => selected.some((id) => isSameOutboundId(id, opt.value)))
    .reduce((acc, opt) => acc + (Number(opt.outboundAmount) || 0), 0);
  form.amount = sum > 0 ? Number(sum.toFixed(2)) : 0;
};
const restoreOutboundTableSelection = () => {
  nextTick(() => {
    const table = outboundTableRef.value;
    if (!table) return;
    table.clearSelection();
    const selectedIds = new Set((form.stockOutRecordIds || []).map((id) => String(id)));
    outboundBatchList.value.forEach((row) => {
      const rowId = getOutboundRowId(row);
      if (rowId !== undefined && rowId !== null && selectedIds.has(String(rowId))) {
        table.toggleRowSelection(row, true);
      }
    });
  });
};
const loadOutboundBatches = (customerId, keepSelected = false) => {
  if (!customerId) {
    outboundBatchList.value = [];
    outboundBatchOptions.value = [];
    if (!keepSelected) {
      form.stockOutRecordIds = [];
      form.amount = 0;
    }
    return Promise.resolve();
  }
  outboundBatchLoading.value = true;
  return getOutboundBatchesByCustomer({ customerId })
    .then((res) => {
      if (res.code === 200) {
        const list = res.data?.records ?? res.data ?? [];
        outboundBatchList.value = Array.isArray(list) ? list : [];
        outboundBatchOptions.value = normalizeOutboundBatchOptions(list);
      } else {
        outboundBatchList.value = [];
        outboundBatchOptions.value = [];
      }
    })
    .catch(() => {
      outboundBatchList.value = [];
      outboundBatchOptions.value = [];
    })
    .finally(() => {
      outboundBatchLoading.value = false;
      if (keepSelected) {
        ensureOutboundOptionsForSelected();
        restoreOutboundTableSelection();
      }
    });
};
const handleCustomerChange = (customerId) => {
  form.stockOutRecordIds = [];
  form.outboundBatches = "";
  form.amount = 0;
  loadOutboundBatches(customerId);
};
const openOutboundSelectDialog = () => {
  if (!form.customerId || isEdit.value || isView.value) return;
  outboundSelectVisible.value = true;
  loadOutboundBatches(form.customerId, true).then(() => {
    restoreOutboundTableSelection();
  });
};
const handleOutboundDialogSelectionChange = (selection) => {
  dialogOutboundSelection.value = selection;
};
const confirmOutboundSelection = () => {
  if (dialogOutboundSelection.value.length === 0) {
    ElMessage.warning("请至少选择一条关联单据");
    return;
  }
  form.stockOutRecordIds = dialogOutboundSelection.value
    .map((row) => getOutboundRowId(row))
    .filter((id) => id !== undefined && id !== null);
  form.outboundBatches = dialogOutboundSelection.value
    .map((row) => row.outboundBatches ?? row.batchNo ?? row.shippingNo ?? "")
    .filter(Boolean)
    .join("、");
  outboundSelectVisible.value = false;
  syncCollectionAmount();
  formRef.value?.validateField("stockOutRecordIds");
};
const handleOutboundDialogClosed = () => {
  dialogOutboundSelection.value = [];
};
const appendFilterParams = (params) => {
  if (filters.collectionNumber) {
    params.collectionNumber = filters.collectionNumber;
  }
  if (filters.customerId) {
    params.customerId = filters.customerId;
  }
  if (filters.collectionMethod) {
    params.collectionMethod = filters.collectionMethod;
  }
  if (filters.dateRange?.length === 2) {
    params.startDate = filters.dateRange[0];
    params.endDate = filters.dateRange[1];
  }
  return params;
};
const buildListParams = () =>
  appendFilterParams({
    current: pagination.currentPage,
    size: pagination.pageSize,
  defineOptions({
    name: "收款单",
  });
const buildExportParams = () => appendFilterParams({});
  const { proxy } = getCurrentInstance();
  const { payment_methods } = proxy.useDict("payment_methods");
const buildSubmitPayload = (forUpdate = false) => {
  const payload = {
    customerId: form.customerId,
    collectionDate: form.receiptDate,
    collectionAmount: form.amount,
    collectionMethod: form.receiptMethod,
    collectionNumber: form.receiptCode || "",
    remark: form.remark || "",
    stockOutRecordIds: (form.stockOutRecordIds || []).join(","),
  };
  if (forUpdate) {
    payload.id = currentId.value;
  }
  return payload;
};
const fillFormFromRow = (row) => {
  const stockOutRecordIds = parseStockOutRecordIds(row.stockOutRecordIds ?? row.stockOutRecordId);
  Object.assign(form, {
    receiptCode: row.receiptCode ?? row.collectionNumber ?? "",
    customerId: row.customerId,
    receiptDate: row.receiptDate ?? row.collectionDate ?? "",
    amount: Number(row.amount ?? row.collectionAmount ?? 0),
    receiptMethod: row.receiptMethod ?? row.collectionMethod ?? "",
    stockOutRecordIds,
    outboundBatches: formatOutboundBatches(row.outboundBatches),
    remark: row.remark ?? "",
  });
};
const closeDialog = () => {
  dialogVisible.value = false;
  outboundSelectVisible.value = false;
  isView.value = false;
  isEdit.value = false;
};
const handleExport = () => {
  const params = buildExportParams();
  proxy.download("/accountSalesCollection/exportAccountSalesCollection", params, `收款单_${Date.now()}.xlsx`);
};
const getTableData = () => {
  tableLoading.value = true;
  listPageAccountSalesCollection(buildListParams())
    .then((res) => {
      const ok = res.code === 200 || res.code === 0;
      if (ok && res.data) {
        pagination.total = res.data.total ?? 0;
        dataList.value = (res.data.records ?? []).map(normalizeTableRow);
      } else {
        ElMessage.error(res.msg || "查询失败");
        dataList.value = [];
        pagination.total = 0;
      }
    })
    .catch(() => {
      dataList.value = [];
      pagination.total = 0;
      ElMessage.error("查询失败");
    })
    .finally(() => {
      tableLoading.value = false;
    });
};
const onSearch = () => {
  pagination.currentPage = 1;
  getTableData();
};
const resetFilters = () => {
  filters.collectionNumber = "";
  filters.customerId = "";
  filters.collectionMethod = "";
  filters.dateRange = [];
  pagination.currentPage = 1;
  getTableData();
};
const changePage = ({ current, size }) => {
  pagination.currentPage = current;
  pagination.pageSize = size;
  getTableData();
};
const add = () => {
  isEdit.value = false;
  isView.value = false;
  dialogTitle.value = "新增收款";
  Object.assign(form, {
    receiptCode: "SK" + Date.now().toString().slice(-8),
  const filters = reactive({
    collectionNumber: "",
    customerId: "",
    receiptDate: new Date().toISOString().split("T")[0],
    collectionMethod: "",
    dateRange: [],
  });
  const pagination = reactive({
    currentPage: 1,
    pageSize: 10,
    total: 0,
  });
  const columns = [
    { label: "收款单号", prop: "receiptCode", width: "150" },
    { label: "客户名称", prop: "customerName", width: "180" },
    { label: "收款日期", prop: "receiptDate", width: "120" },
    { label: "收款金额", prop: "amount", dataType: "slot", slot: "amount" },
    {
      label: "收款方式",
      prop: "receiptMethod",
      dataType: "slot",
      slot: "receiptMethod",
      width: "120",
    },
    { label: "备注", prop: "remark", showOverflowTooltip: true },
    {
      label: "操作",
      prop: "operation",
      dataType: "slot",
      slot: "operation",
      width: "200",
      fixed: "right",
    },
  ];
  const dataList = ref([]);
  const tableLoading = ref(false);
  const dialogVisible = ref(false);
  const dialogTitle = ref("");
  const formRef = ref(null);
  const isEdit = ref(false);
  const isView = ref(false);
  const currentId = ref(null);
  const submitLoading = ref(false);
  const customerList = ref([]);
  const outboundBatchList = ref([]);
  const outboundBatchOptions = ref([]);
  const outboundBatchLoading = ref(false);
  const outboundSelectVisible = ref(false);
  const outboundTableRef = ref(null);
  const dialogOutboundSelection = ref([]);
  const getReceiptMethodLabel = value => {
    if (value === undefined || value === null || value === "") return "-";
    const item = payment_methods.value?.find(
      m => String(m.value) === String(value)
    );
    return item?.label ?? value;
  };
  const getDefaultReceiptMethod = () => payment_methods.value?.[0]?.value ?? "";
  const form = reactive({
    receiptCode: "",
    customerId: "",
    receiptDate: "",
    amount: 0,
    receiptMethod: getDefaultReceiptMethod(),
    receiptMethod: "",
    stockOutRecordIds: [],
    outboundBatches: "",
    remark: "",
  });
  outboundBatchList.value = [];
  outboundBatchOptions.value = [];
  dialogVisible.value = true;
};
const edit = (row) => {
  isEdit.value = true;
  isView.value = false;
  currentId.value = row.id;
  dialogTitle.value = "编辑收款";
  fillFormFromRow(row);
  dialogVisible.value = true;
};
  const rules = {
    customerId: [{ required: true, message: "请选择客户", trigger: "change" }],
    stockOutRecordIds: [
      {
        required: true,
        type: "array",
        min: 1,
        message: "请选择关联单据",
        trigger: "change",
      },
    ],
    receiptDate: [
      { required: true, message: "请选择收款日期", trigger: "change" },
    ],
    amount: [{ required: true, message: "请输入收款金额", trigger: "blur" }],
    receiptMethod: [
      { required: true, message: "请选择收款方式", trigger: "change" },
    ],
  };
const view = (row) => {
  isView.value = true;
  isEdit.value = false;
  dialogTitle.value = "查看收款";
  fillFormFromRow(row);
  dialogVisible.value = true;
};
  const totalReceiptAmount = computed(() =>
    dataList.value.reduce((sum, item) => sum + Number(item.amount || 0), 0)
  );
const handleDelete = (row) => {
  ElMessageBox.confirm(`确认删除收款单「${row.receiptCode ?? row.collectionNumber}」吗?`, "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  }).then(() => {
    deleteAccountSalesCollection([row.id])
      .then((res) => {
        if (res.code === 200) {
          ElMessage.success("删除成功");
          getTableData();
        } else {
          ElMessage.error(res.msg || "删除失败");
        }
      })
      .catch(() => {
        ElMessage.error("删除失败");
      });
  const formatMoney = value => {
    if (value === undefined || value === null) return "0.00";
    return Number(value)
      .toFixed(2)
      .replace(/\B(?=(\d{3})+(?!\d))/g, ",");
  };
  const parseStockOutRecordIds = value => {
    if (!value) return [];
    if (Array.isArray(value)) return value;
    return String(value)
      .split(/[,,]/)
      .map(s => s.trim())
      .filter(Boolean)
      .map(s => (/^\d+$/.test(s) ? Number(s) : s));
  };
  const formatOutboundBatches = value => {
    if (value === undefined || value === null || value === "") return "";
    if (Array.isArray(value)) return value.filter(Boolean).join("、");
    return String(value)
      .split(/[,,]/)
      .map(s => s.trim())
      .filter(Boolean)
      .join("、");
  };
  const normalizeTableRow = row => ({
    ...row,
    receiptCode: row.collectionNumber ?? row.receiptCode,
    receiptDate: row.collectionDate ?? row.receiptDate,
    amount: row.collectionAmount ?? row.amount,
    receiptMethod: row.collectionMethod ?? row.receiptMethod ?? "",
    stockOutRecordIds: row.stockOutRecordIds ?? row.stockOutRecordId ?? "",
    outboundBatches: formatOutboundBatches(row.outboundBatches),
  });
};
const submitForm = () => {
  formRef.value.validate((valid) => {
    if (!valid) return;
    submitLoading.value = true;
    const request = isEdit.value
      ? updateAccountSalesCollection(buildSubmitPayload(true))
      : addAccountSalesCollection(buildSubmitPayload());
    request
      .then((res) => {
  const getCustomerList = () => {
    listCustomer({ current: -1, size: -1, type: 0 }).then(res => {
      if (res.code === 200) {
        customerList.value = res.data?.records || [];
      }
    });
  };
  const normalizeOutboundBatchOptions = data => {
    const list = Array.isArray(data) ? data : [];
    return list.map((item, index) => {
      if (typeof item === "string" || typeof item === "number") {
        const text = String(item);
        return { label: text, value: text, outboundAmount: 0 };
      }
      const label =
        item.outboundBatches ??
        item.batchNo ??
        item.shippingNo ??
        item.outboundNo ??
        item.label ??
        `出库单${index + 1}`;
      const value = item.id ?? item.stockOutRecordId ?? label;
      return {
        label: String(label),
        value,
        outboundAmount: Number(item.outboundAmount) || 0,
      };
    });
  };
  const isSameOutboundId = (a, b) => String(a) === String(b);
  const getOutboundRowId = row => row?.id ?? row?.stockOutRecordId;
  const outboundBatchDisplayText = computed(() => {
    if (isEdit.value || isView.value) {
      return form.outboundBatches || "";
    }
    if (form.outboundBatches) return form.outboundBatches;
    const ids = form.stockOutRecordIds || [];
    if (!ids.length) return "";
    const labels = outboundBatchOptions.value
      .filter(opt => ids.some(id => isSameOutboundId(id, opt.value)))
      .map(opt => opt.label);
    if (labels.length) return labels.join("、");
    return ids.join("、");
  });
  const handleOutboundInputClick = () => {
    if (isEdit.value || isView.value) return;
    openOutboundSelectDialog();
  };
  /** ä¸ºå·²é€‰ ID è¡¥å…¨é€‰é¡¹ï¼ˆç¼–辑/查看回显) */
  const ensureOutboundOptionsForSelected = () => {
    const ids = form.stockOutRecordIds || [];
    ids.forEach(id => {
      const exists = outboundBatchOptions.value.some(opt =>
        isSameOutboundId(opt.value, id)
      );
      if (exists) return;
      const fromList = outboundBatchList.value.find(row =>
        isSameOutboundId(getOutboundRowId(row), id)
      );
      if (fromList) {
        const [option] = normalizeOutboundBatchOptions([fromList]);
        if (option) outboundBatchOptions.value.push(option);
        return;
      }
      outboundBatchOptions.value.push({
        label: String(id),
        value: id,
        outboundAmount: 0,
      });
    });
  };
  const syncCollectionAmount = () => {
    const selected = form.stockOutRecordIds || [];
    const sum = outboundBatchOptions.value
      .filter(opt => selected.some(id => isSameOutboundId(id, opt.value)))
      .reduce((acc, opt) => acc + (Number(opt.outboundAmount) || 0), 0);
    form.amount = sum > 0 ? Number(sum.toFixed(2)) : 0;
  };
  const restoreOutboundTableSelection = () => {
    nextTick(() => {
      const table = outboundTableRef.value;
      if (!table) return;
      table.clearSelection();
      const selectedIds = new Set(
        (form.stockOutRecordIds || []).map(id => String(id))
      );
      outboundBatchList.value.forEach(row => {
        const rowId = getOutboundRowId(row);
        if (
          rowId !== undefined &&
          rowId !== null &&
          selectedIds.has(String(rowId))
        ) {
          table.toggleRowSelection(row, true);
        }
      });
    });
  };
  const loadOutboundBatches = (customerId, keepSelected = false) => {
    if (!customerId) {
      outboundBatchList.value = [];
      outboundBatchOptions.value = [];
      if (!keepSelected) {
        form.stockOutRecordIds = [];
        form.amount = 0;
      }
      return Promise.resolve();
    }
    outboundBatchLoading.value = true;
    return getOutboundBatchesByCustomer({ customerId })
      .then(res => {
        if (res.code === 200) {
          ElMessage.success(isEdit.value ? "修改成功" : "新增成功");
          closeDialog();
          getTableData();
          const list = res.data?.records ?? res.data ?? [];
          outboundBatchList.value = Array.isArray(list) ? list : [];
          outboundBatchOptions.value = normalizeOutboundBatchOptions(list);
        } else {
          ElMessage.error(res.msg || (isEdit.value ? "修改失败" : "新增失败"));
          outboundBatchList.value = [];
          outboundBatchOptions.value = [];
        }
      })
      .catch(() => {
        ElMessage.error(isEdit.value ? "修改失败" : "新增失败");
        outboundBatchList.value = [];
        outboundBatchOptions.value = [];
      })
      .finally(() => {
        submitLoading.value = false;
        outboundBatchLoading.value = false;
        if (keepSelected) {
          ensureOutboundOptionsForSelected();
          restoreOutboundTableSelection();
        }
      });
  });
};
  };
onMounted(() => {
  getCustomerList();
  getTableData();
});
  const handleCustomerChange = customerId => {
    form.stockOutRecordIds = [];
    form.outboundBatches = "";
    form.amount = 0;
    loadOutboundBatches(customerId);
  };
  const openOutboundSelectDialog = () => {
    if (!form.customerId || isEdit.value || isView.value) return;
    outboundSelectVisible.value = true;
    loadOutboundBatches(form.customerId, true).then(() => {
      restoreOutboundTableSelection();
    });
  };
  const handleOutboundDialogSelectionChange = selection => {
    dialogOutboundSelection.value = selection;
  };
  const confirmOutboundSelection = () => {
    if (dialogOutboundSelection.value.length === 0) {
      ElMessage.warning("请至少选择一条关联单据");
      return;
    }
    form.stockOutRecordIds = dialogOutboundSelection.value
      .map(row => getOutboundRowId(row))
      .filter(id => id !== undefined && id !== null);
    form.outboundBatches = dialogOutboundSelection.value
      .map(row => row.outboundBatches ?? row.batchNo ?? row.shippingNo ?? "")
      .filter(Boolean)
      .join("、");
    outboundSelectVisible.value = false;
    syncCollectionAmount();
    formRef.value?.validateField("stockOutRecordIds");
  };
  const handleOutboundDialogClosed = () => {
    dialogOutboundSelection.value = [];
  };
  const appendFilterParams = params => {
    if (filters.collectionNumber) {
      params.collectionNumber = filters.collectionNumber;
    }
    if (filters.customerId) {
      params.customerId = filters.customerId;
    }
    if (filters.collectionMethod) {
      params.collectionMethod = filters.collectionMethod;
    }
    if (filters.dateRange?.length === 2) {
      params.startDate = filters.dateRange[0];
      params.endDate = filters.dateRange[1];
    }
    return params;
  };
  const buildListParams = () =>
    appendFilterParams({
      current: pagination.currentPage,
      size: pagination.pageSize,
    });
  const buildExportParams = () => appendFilterParams({});
  const buildSubmitPayload = (forUpdate = false) => {
    const payload = {
      customerId: form.customerId,
      collectionDate: form.receiptDate,
      collectionAmount: form.amount,
      collectionMethod: form.receiptMethod,
      collectionNumber: form.receiptCode || "",
      remark: form.remark || "",
      stockOutRecordIds: (form.stockOutRecordIds || []).join(","),
    };
    if (forUpdate) {
      payload.id = currentId.value;
    }
    return payload;
  };
  const fillFormFromRow = row => {
    const stockOutRecordIds = parseStockOutRecordIds(
      row.stockOutRecordIds ?? row.stockOutRecordId
    );
    Object.assign(form, {
      receiptCode: row.receiptCode ?? row.collectionNumber ?? "",
      customerId: row.customerId,
      receiptDate: row.receiptDate ?? row.collectionDate ?? "",
      amount: Number(row.amount ?? row.collectionAmount ?? 0),
      receiptMethod: row.receiptMethod ?? row.collectionMethod ?? "",
      stockOutRecordIds,
      outboundBatches: formatOutboundBatches(row.outboundBatches),
      remark: row.remark ?? "",
    });
  };
  const closeDialog = () => {
    dialogVisible.value = false;
    outboundSelectVisible.value = false;
    isView.value = false;
    isEdit.value = false;
  };
  const handleExport = () => {
    const params = buildExportParams();
    proxy.download(
      "/accountSalesCollection/exportAccountSalesCollection",
      params,
      `收款单_${Date.now()}.xlsx`
    );
  };
  const getTableData = () => {
    tableLoading.value = true;
    listPageAccountSalesCollection(buildListParams())
      .then(res => {
        const ok = res.code === 200 || res.code === 0;
        if (ok && res.data) {
          pagination.total = res.data.total ?? 0;
          dataList.value = (res.data.records ?? []).map(normalizeTableRow);
        } else {
          ElMessage.error(res.msg || "查询失败");
          dataList.value = [];
          pagination.total = 0;
        }
      })
      .catch(() => {
        dataList.value = [];
        pagination.total = 0;
        ElMessage.error("查询失败");
      })
      .finally(() => {
        tableLoading.value = false;
      });
  };
  const onSearch = () => {
    pagination.currentPage = 1;
    getTableData();
  };
  const resetFilters = () => {
    filters.collectionNumber = "";
    filters.customerId = "";
    filters.collectionMethod = "";
    filters.dateRange = [];
    pagination.currentPage = 1;
    getTableData();
  };
  const changePage = ({ current, size }) => {
    pagination.currentPage = current;
    pagination.pageSize = size;
    getTableData();
  };
  const add = () => {
    isEdit.value = false;
    isView.value = false;
    dialogTitle.value = "新增收款";
    Object.assign(form, {
      receiptCode: "SK" + Date.now().toString().slice(-8),
      customerId: "",
      receiptDate: new Date().toISOString().split("T")[0],
      amount: 0,
      receiptMethod: getDefaultReceiptMethod(),
      stockOutRecordIds: [],
      outboundBatches: "",
      remark: "",
    });
    outboundBatchList.value = [];
    outboundBatchOptions.value = [];
    dialogVisible.value = true;
  };
  const edit = row => {
    isEdit.value = true;
    isView.value = false;
    currentId.value = row.id;
    dialogTitle.value = "编辑收款";
    fillFormFromRow(row);
    dialogVisible.value = true;
  };
  const view = row => {
    isView.value = true;
    isEdit.value = false;
    dialogTitle.value = "查看收款";
    fillFormFromRow(row);
    dialogVisible.value = true;
  };
  const handleDelete = row => {
    ElMessageBox.confirm(
      `确认删除收款单「${row.receiptCode ?? row.collectionNumber}」吗?`,
      "提示",
      {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }
    ).then(() => {
      deleteAccountSalesCollection([row.id])
        .then(res => {
          if (res.code === 200) {
            ElMessage.success("删除成功");
            getTableData();
          } else {
            ElMessage.error(res.msg || "删除失败");
          }
        })
        .catch(() => {
          ElMessage.error("删除失败");
        });
    });
  };
  const submitForm = () => {
    formRef.value.validate(valid => {
      if (!valid) return;
      submitLoading.value = true;
      const request = isEdit.value
        ? updateAccountSalesCollection(buildSubmitPayload(true))
        : addAccountSalesCollection(buildSubmitPayload());
      request
        .then(res => {
          if (res.code === 200) {
            ElMessage.success(isEdit.value ? "修改成功" : "新增成功");
            closeDialog();
            getTableData();
          } else {
            ElMessage.error(res.msg || (isEdit.value ? "修改失败" : "新增失败"));
          }
        })
        .catch(() => {
          ElMessage.error(isEdit.value ? "修改失败" : "新增失败");
        })
        .finally(() => {
          submitLoading.value = false;
        });
    });
  };
  onMounted(() => {
    getCustomerList();
    getTableData();
  });
</script>
<style lang="scss" scoped>
.actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 15px;
}
.text-success {
  color: #67c23a;
  font-weight: bold;
}
.outbound-batch-input:not(.is-disabled) {
  :deep(.el-input__wrapper) {
    cursor: pointer;
  .actions {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 15px;
  }
}
  .text-success {
    color: #67c23a;
    font-weight: bold;
  }
  .outbound-batch-input:not(.is-disabled) {
    :deep(.el-input__wrapper) {
      cursor: pointer;
    }
  }
</style>
src/views/financialManagement/salesRefund/components/ReceiptandRefundPopupWindow.vue
ÎļþÒÑɾ³ý
src/views/financialManagement/salesRefund/index.vue
ÎļþÒÑɾ³ý
src/views/index.vue
@@ -21,10 +21,10 @@
    <section v-if="dashboardCards.length > 0" class="top-row">
      <div class="stats-grid">
        <article
          v-for="card in dashboardCards"
          :key="card.key"
          class="stat-card"
          :class="card.key"
            v-for="card in dashboardCards"
            :key="card.key"
            class="stat-card"
            :class="card.key"
        >
          <div class="stat-header">
            <div class="stat-title-wrap">
@@ -66,15 +66,15 @@
          <div class="process-body">
            <div class="process-chart" :class="{ empty: !hasProcessData }">
              <Echarts
                :options="chartBaseOptions"
                :chartStyle="{ width: '100%', height: '100%' }"
                :grid="processGrid"
                :series="processSeries"
                :tooltip="processTooltip"
                :xAxis="processXAxis"
                :yAxis="processYAxis"
                :style="{ height: hasProcessData ? '340px' : '280px' }"
                @click="handleChartClick"
                  :options="chartBaseOptions"
                  :chartStyle="{ width: '100%', height: '100%' }"
                  :grid="processGrid"
                  :series="processSeries"
                  :tooltip="processTooltip"
                  :xAxis="processXAxis"
                  :yAxis="processYAxis"
                  :style="{ height: hasProcessData ? '340px' : '280px' }"
                  @click="handleChartClick"
              />
              <div v-if="!hasProcessData" class="chart-empty">
                <el-icon><DataAnalysis /></el-icon>
@@ -113,10 +113,10 @@
          <div class="panel-title-row">
            <div class="panel-title">生产订单进度</div>
            <el-radio-group v-model="orderFilter" size="small">
              <el-radio-button label="all">全部</el-radio-button>
              <el-radio-button label="in_progress">进行中</el-radio-button>
              <el-radio-button label="completed">已完成</el-radio-button>
              <el-radio-button label="paused">已暂停</el-radio-button>
              <el-radio-button label="all">全部({{ orderProgressMeta.total }})</el-radio-button>
              <el-radio-button label="inProgress">进行中({{ orderProgressMeta.inProgressCount }})</el-radio-button>
              <el-radio-button label="completed">已完成({{ orderProgressMeta.completedCount }})</el-radio-button>
              <el-radio-button label="paused">已暂停({{ orderProgressMeta.pausedCount }})</el-radio-button>
            </el-radio-group>
          </div>
          <el-table :data="filteredOrders" stripe>
@@ -128,10 +128,10 @@
              <template #default="{ row }">
                <div class="table-progress">
                  <el-progress
                    :stroke-width="8"
                    :percentage="row.completionRate"
                    :show-text="false"
                    status="success"
                      :stroke-width="8"
                      :percentage="row.completionRate"
                      :show-text="false"
                      status="success"
                  />
                  <span>{{ row.completionRate }}%</span>
                </div>
@@ -141,7 +141,7 @@
            <el-table-column label="状态" min-width="90">
              <template #default="{ row }">
                <el-tag :type="getOrderStatusType(row.status)" effect="light">
                  {{ getOrderStatusText(row.status) }}
                  {{ row.statusLabel || getOrderStatusText(row.status) }}
                </el-tag>
              </template>
            </el-table-column>
@@ -149,53 +149,53 @@
        </div>
        <div v-if="visiblePanels.contract" class="cockpit-panel contract-panel">
            <div class="panel-title">客户合同金额分析</div>
            <div class="contract-summary">
              <div class="contract-card">
                <div class="contract-name">总合同金额(元)</div>
                <div class="contract-main digital-number">{{ formatNumber(sum) }}</div>
                <div class="contract-compare">
                  åŒæ¯”
                  <span class="rise">{{ trendText(yny) }}</span>
                  çŽ¯æ¯”
                  <span class="rise">{{ trendText(chain) }}</span>
                </div>
          <div class="panel-title">客户合同金额分析</div>
          <div class="contract-summary">
            <div class="contract-card">
              <div class="contract-name">总合同金额(元)</div>
              <div class="contract-main digital-number">{{ formatNumber(sum) }}</div>
              <div class="contract-compare">
                åŒæ¯”
                <span class="rise">{{ trendText(yny) }}</span>
                çŽ¯æ¯”
                <span class="rise">{{ trendText(chain) }}</span>
              </div>
              <div class="contract-chart-wrap">
                <Echarts
            </div>
            <div class="contract-chart-wrap">
              <Echarts
                  :options="chartBaseOptions"
                  :legend="pieLegend"
                  :chartStyle="chartStylePie"
                  :series="materialPieSeries"
                  :tooltip="pieTooltip"
                />
              </div>
              />
            </div>
            <ul class="contract-list">
              <li v-for="item in materialPieSeries[0].data" :key="item.name">
                <span class="legend-dot" :style="{ backgroundColor: item.itemStyle?.color }"></span>
                <span class="contract-item-name">{{ item.name }}</span>
                <span class="contract-item-rate">{{ item.rate }}%</span>
                <span class="contract-item-value digital-number">Â¥{{ formatNumber(item.value) }}</span>
              </li>
            </ul>
          </div>
          <ul class="contract-list">
            <li v-for="item in materialPieSeries[0].data" :key="item.name">
              <span class="legend-dot" :style="{ backgroundColor: item.itemStyle?.color }"></span>
              <span class="contract-item-name">{{ item.name }}</span>
              <span class="contract-item-rate">{{ item.rate }}%</span>
              <span class="contract-item-value digital-number">Â¥{{ formatNumber(item.value) }}</span>
            </li>
          </ul>
        </div>
        <div v-if="visiblePanels.quality" class="cockpit-panel quality-panel">
            <div class="panel-title-row">
              <div class="panel-title">质量统计</div>
              <el-radio-group v-model="qualityRange" size="small" @change="qualityStatisticsInfo">
                <el-radio-button :value="1">周</el-radio-button>
                <el-radio-button :value="2">月</el-radio-button>
                <el-radio-button :value="3">季度</el-radio-button>
              </el-radio-group>
            </div>
            <div class="quality-cards">
              <div class="quality-card one">原材料已检数量 <span>{{ qualityStatisticsObject.supplierNum }}ä»¶</span></div>
              <div class="quality-card two">过程检验数量 <span>{{ qualityStatisticsObject.processNum }}ä»¶</span></div>
              <div class="quality-card three">出厂已检数量 <span>{{ qualityStatisticsObject.factoryNum }}ä»¶</span></div>
            </div>
            <Echarts
          <div class="panel-title-row">
            <div class="panel-title">质量统计</div>
            <el-radio-group v-model="qualityRange" size="small" @change="qualityStatisticsInfo">
              <el-radio-button :value="1">周</el-radio-button>
              <el-radio-button :value="2">月</el-radio-button>
              <el-radio-button :value="3">季度</el-radio-button>
            </el-radio-group>
          </div>
          <div class="quality-cards">
            <div class="quality-card one">原材料已检数量 <span>{{ qualityStatisticsObject.supplierNum }}ä»¶</span></div>
            <div class="quality-card two">过程检验数量 <span>{{ qualityStatisticsObject.processNum }}ä»¶</span></div>
            <div class="quality-card three">出厂已检数量 <span>{{ qualityStatisticsObject.factoryNum }}ä»¶</span></div>
          </div>
          <Echarts
              :options="chartBaseOptions"
              :chartStyle="chartStyle"
              :grid="grid"
@@ -205,8 +205,8 @@
              :xAxis="xAxis1"
              :yAxis="yAxis1"
              style="height: 270px"
            />
          </div>
          />
        </div>
      </div>
      <div v-if="hasRightPanels" class="right-column">
@@ -236,11 +236,11 @@
          <div class="realtime-grid">
            <div class="realtime-item" v-for="item in realtimeBoard" :key="item.key">
              <el-progress
                type="circle"
                :percentage="item.percent"
                :stroke-width="10"
                :width="94"
                :color="item.color"
                  type="circle"
                  :percentage="item.percent"
                  :stroke-width="10"
                  :width="94"
                  :color="item.color"
              >
                <template #default>
                  <div class="realtime-value digital-number">{{ item.display }}</div>
@@ -258,11 +258,11 @@
          </div>
          <div class="quick-grid">
            <button
              v-for="item in quickEntries"
              :key="item.label"
              class="quick-item"
              type="button"
              @click="goToQuick(item.path)"
                v-for="item in quickEntries"
                :key="item.label"
                class="quick-item"
                type="button"
                @click="goToQuick(item.path)"
            >
              <span class="quick-icon">
                <el-icon>
@@ -277,7 +277,7 @@
        <div v-if="visiblePanels.plan" class="cockpit-panel plan-panel">
          <div class="panel-title-row">
            <div class="panel-title">今日生产计划</div>
            <span class="panel-more">{{ todayPlanList.length }}项</span>
            <span class="panel-more">{{ todayPlanTotal }}项</span>
          </div>
          <ul class="plan-list">
            <li v-for="item in todayPlanList" :key="item.orderNo" class="plan-item">
@@ -296,15 +296,15 @@
        <div v-if="visiblePanels.receipt" class="cockpit-panel receipt-panel">
          <div class="panel-title">回款与开票分析</div>
          <Echarts
            :options="chartBaseOptions"
            :chartStyle="chartStyle"
            :grid="grid"
            :legend="lineLegend"
            :series="lineSeries"
            :tooltip="tooltipLine"
            :xAxis="xAxis2"
            :yAxis="yAxis2"
            style="height: 300px"
              :options="chartBaseOptions"
              :chartStyle="chartStyle"
              :grid="grid"
              :legend="lineLegend"
              :series="lineSeries"
              :tooltip="tooltipLine"
              :xAxis="xAxis2"
              :yAxis="yAxis2"
              style="height: 300px"
          />
        </div>
@@ -363,8 +363,12 @@
  getBusiness,
  homeTodos,
  processDataProductionStatistics,
  productionOrderProgress,
  productionOverview,
  productionRealtimeBoard,
  qualityInspectionStatistics,
  statisticsReceivablePayable,
  todayProductionPlan,
} from "@/api/viewIndex.js";
import { list } from "@/api/productionManagement/productionProcess";
@@ -391,7 +395,7 @@
});
const welcomeAvatar = computed(() =>
  welcomeAvatarLoadFailed.value || !userStore.avatar ? defaultWelcomeAvatar : userStore.avatar
    welcomeAvatarLoadFailed.value || !userStore.avatar ? defaultWelcomeAvatar : userStore.avatar
);
const handleWelcomeAvatarError = () => {
@@ -401,10 +405,10 @@
};
watch(
  () => userStore.avatar,
  () => {
    welcomeAvatarLoadFailed.value = false;
  }
    () => userStore.avatar,
    () => {
      welcomeAvatarLoadFailed.value = false;
    }
);
const axisTextColor = "#5f6f86";
@@ -436,6 +440,31 @@
  processNum: 0,
  factoryNum: 0,
});
const productionOverviewData = ref({
  totalOutput: 0,
  totalScrap: 0,
  yieldRate: 0,
});
const realtimeBoardData = ref({
  deviceOee: { value: 0, compareYesterday: 0 },
  orderAchievementRate: { value: 0, compareYesterday: 0 },
  defectRate: { value: 0, compareYesterday: 0 },
});
const orderProgressMeta = ref({
  tab: "all",
  total: 0,
  pageNum: 1,
  pageSize: 10,
  inProgressCount: 0,
  completedCount: 0,
  pausedCount: 0,
});
const todayPlanList = ref([]);
const todayPlanTotal = ref(0);
const sum = ref(0);
const yny = ref(0);
@@ -677,11 +706,11 @@
    const name = params?.[0]?.name ?? "";
    const list = Array.isArray(params) ? params : [];
    const lines = list
      .map((p) => {
        const colorBox = `<span style="display:inline-block;margin-right:6px;border-radius:2px;width:10px;height:10px;background:${p.color}"></span>`;
        return `${colorBox}${p.seriesName}<b style="float:right;">${Number(p.value || 0).toFixed(2)}</b>`;
      })
      .join("<br/>");
        .map((p) => {
          const colorBox = `<span style="display:inline-block;margin-right:6px;border-radius:2px;width:10px;height:10px;background:${p.color}"></span>`;
          return `${colorBox}${p.seriesName}<b style="float:right;">${Number(p.value || 0).toFixed(2)}</b>`;
        })
        .join("<br/>");
    return `<div style="min-width:140px;"><div style="font-weight:700;margin-bottom:6px;">${name}</div>${lines}</div>`;
  },
});
@@ -730,15 +759,15 @@
});
const processTotals = computed(() =>
  processChartData.value.reduce(
    (acc, cur) => {
      acc.input += Number(cur.input || 0);
      acc.scrap += Number(cur.scrap || 0);
      acc.output += Number(cur.output || 0);
      return acc;
    },
    { input: 0, scrap: 0, output: 0 }
  )
    processChartData.value.reduce(
        (acc, cur) => {
          acc.input += Number(cur.input || 0);
          acc.scrap += Number(cur.scrap || 0);
          acc.output += Number(cur.output || 0);
          return acc;
        },
        { input: 0, scrap: 0, output: 0 }
    )
);
const hasProcessData = computed(() => {
@@ -771,8 +800,8 @@
    subLabel: "待付款金额",
    subValue: formatNumber(businessInfo.value.monthPurchaseHaveMoney),
    trend: `占比 ${ratioText(
      businessInfo.value.monthPurchaseHaveMoney,
      businessInfo.value.monthPurchaseMoney
        businessInfo.value.monthPurchaseHaveMoney,
        businessInfo.value.monthPurchaseMoney
    )}`,
    icon: ShoppingCartFull,
    visible: visibleModules.value.procurement,
@@ -792,102 +821,68 @@
    key: "production",
    title: "生产总览",
    desc: "累计产出(ä»¶)",
    value: formatNumber(processTotals.value.output),
    value: formatNumber(productionOverviewData.value.totalOutput),
    subLabel: "累计报废",
    subValue: formatNumber(processTotals.value.scrap),
    trend: `良率 ${ratioText(processTotals.value.output, processTotals.value.input)}`,
    subValue: formatNumber(productionOverviewData.value.totalScrap),
    trend: `良率 ${Number(productionOverviewData.value.yieldRate || 0).toFixed(2)}%`,
    icon: Operation,
    visible: visibleModules.value.production,
  },
].filter((item) => item.visible));
const productionOrders = ref([
  {
    orderNo: "MO-20260518-001",
    productName: "智能控制器",
    planQty: 1000,
    completedQty: 860,
    completionRate: 86,
    deliveryDate: "2026-05-20",
    status: "in_progress",
  },
  {
    orderNo: "MO-20260518-002",
    productName: "电源模块",
    planQty: 800,
    completedQty: 640,
    completionRate: 80,
    deliveryDate: "2026-05-22",
    status: "in_progress",
  },
  {
    orderNo: "MO-20260518-003",
    productName: "传感器组件",
    planQty: 500,
    completedQty: 150,
    completionRate: 30,
    deliveryDate: "2026-05-25",
    status: "paused",
  },
  {
    orderNo: "MO-20260518-004",
    productName: "结构件A",
    planQty: 1200,
    completedQty: 1200,
    completionRate: 100,
    deliveryDate: "2026-05-15",
    status: "completed",
  },
]);
const productionOrders = ref([]);
const orderFilter = ref("all");
const filteredOrders = computed(() => {
  if (orderFilter.value === "all") return productionOrders.value;
  return productionOrders.value.filter((item) => item.status === orderFilter.value);
});
const filteredOrders = computed(() => productionOrders.value);
const todayPlanList = computed(() =>
  productionOrders.value
    .slice()
    .sort((a, b) => dayjs(a.deliveryDate).valueOf() - dayjs(b.deliveryDate).valueOf())
    .slice(0, 5)
);
const getCompareTrend = (value) => {
  const num = Number(value || 0);
  if (num > 0) return "up";
  if (num < 0) return "down";
  return "flat";
};
const avgCompletionRate = computed(() => {
  if (!productionOrders.value.length) return 0;
  const total = productionOrders.value.reduce((acc, cur) => acc + Number(cur.completionRate || 0), 0);
  return Number((total / productionOrders.value.length).toFixed(1));
});
const getCompareText = (value) => {
  const num = Number(value || 0);
  const abs = Math.abs(num).toFixed(2);
  if (num > 0) return `较昨日 â†‘ ${abs}%`;
  if (num < 0) return `较昨日 â†“ ${abs}%`;
  return "较昨日 æŒå¹³";
};
const realtimeBoard = computed(() => {
  const oee = ratioNumber(processTotals.value.output, processTotals.value.input);
  const defectRate = ratioNumber(processTotals.value.scrap, processTotals.value.input);
  const oee = Number(realtimeBoardData.value.deviceOee?.value || 0);
  const orderAchievement = Number(realtimeBoardData.value.orderAchievementRate?.value || 0);
  const defectRate = Number(realtimeBoardData.value.defectRate?.value || 0);
  const oeeCompare = Number(realtimeBoardData.value.deviceOee?.compareYesterday || 0);
  const orderCompare = Number(realtimeBoardData.value.orderAchievementRate?.compareYesterday || 0);
  const defectCompare = Number(realtimeBoardData.value.defectRate?.compareYesterday || 0);
  return [
    {
      key: "oee",
      label: "设备 OEE",
      percent: clampPercent(oee),
      display: `${oee.toFixed(1)}%`,
      delta: "较昨日 â†‘ 4.0%",
      trend: "up",
      display: `${oee.toFixed(2)}%`,
      delta: getCompareText(oeeCompare),
      trend: getCompareTrend(oeeCompare),
      color: "#2d8cff",
    },
    {
      key: "order",
      label: "订单达成率",
      percent: clampPercent(avgCompletionRate.value),
      display: `${avgCompletionRate.value.toFixed(1)}%`,
      delta: "较昨日 â†‘ 2.6%",
      trend: "up",
      percent: clampPercent(orderAchievement),
      display: `${orderAchievement.toFixed(2)}%`,
      delta: getCompareText(orderCompare),
      trend: getCompareTrend(orderCompare),
      color: "#31d2ff",
    },
    {
      key: "defect",
      label: "不良率",
      percent: clampPercent(defectRate),
      display: `${defectRate.toFixed(1)}%`,
      delta: "较昨日 â†“ 0.5%",
      trend: "down",
      display: `${defectRate.toFixed(2)}%`,
      delta: getCompareText(defectCompare),
      trend: getCompareTrend(defectCompare),
      color: "#f6a23f",
    },
  ];
@@ -930,11 +925,11 @@
const normalizeMenuTitle = (title) => String(title || "").replace(/\s+/g, "").trim();
const normalizeRoutePath = (path) =>
  String(path || "")
    .trim()
    .replace(/\/+/g, "/")
    .replace(/\/$/, "")
    .toLowerCase();
    String(path || "")
        .trim()
        .replace(/\/+/g, "/")
        .replace(/\/$/, "")
        .toLowerCase();
const resolveRoutePath = (route, parentPath = "") => {
  const currentPath = String(route?.path || "").trim();
@@ -966,11 +961,11 @@
const accessibleMenuRoutes = computed(() => {
  const routePool =
    permissionStore.defaultRoutes?.length > 0
      ? permissionStore.defaultRoutes
      : permissionStore.sidebarRouters?.length > 0
        ? permissionStore.sidebarRouters
        : permissionStore.routes;
      permissionStore.defaultRoutes?.length > 0
          ? permissionStore.defaultRoutes
          : permissionStore.sidebarRouters?.length > 0
              ? permissionStore.sidebarRouters
              : permissionStore.routes;
  return collectAccessibleRoutes(routePool || []);
});
@@ -1014,13 +1009,13 @@
};
const hasModuleAccess = (config) =>
  accessibleMenuRoutes.value.some((route) => {
    const matchedTitle = (config.titles || []).some((title) => route.title === normalizeMenuTitle(title));
    const matchedPath = (config.pathPrefixes || []).some(
      (prefix) => route.path === prefix || route.path.startsWith(`${prefix}/`)
    );
    return matchedTitle || matchedPath;
  });
    accessibleMenuRoutes.value.some((route) => {
      const matchedTitle = (config.titles || []).some((title) => route.title === normalizeMenuTitle(title));
      const matchedPath = (config.pathPrefixes || []).some(
          (prefix) => route.path === prefix || route.path.startsWith(`${prefix}/`)
      );
      return matchedTitle || matchedPath;
    });
const visibleModules = computed(() => ({
  sales: hasModuleAccess(moduleAccessConfig.sales),
@@ -1047,29 +1042,29 @@
}));
const hasLeftPanels = computed(
  () => visiblePanels.value.process || visiblePanels.value.order || visiblePanels.value.contract || visiblePanels.value.quality
    () => visiblePanels.value.process || visiblePanels.value.order || visiblePanels.value.contract || visiblePanels.value.quality
);
const hasRightPanels = computed(
  () => visiblePanels.value.todo || visiblePanels.value.realtime || visiblePanels.value.quick || visiblePanels.value.plan || visiblePanels.value.receipt
    () => visiblePanels.value.todo || visiblePanels.value.realtime || visiblePanels.value.quick || visiblePanels.value.plan || visiblePanels.value.receipt
);
const hasVisiblePanels = computed(() => hasLeftPanels.value || hasRightPanels.value);
const quickEntries = computed(() =>
  quickEntryConfigs
    .map((item) => {
      const targetRoute = accessibleMenuRoutes.value.find((route) =>
        item.titles.some((title) => route.title === normalizeMenuTitle(title))
      );
      const resolvedPath = targetRoute?.path || "";
      return resolvedPath
        ? {
            label: item.label,
            icon: item.icon,
            path: resolvedPath,
          }
        : null;
    })
    .filter(Boolean)
    quickEntryConfigs
        .map((item) => {
          const targetRoute = accessibleMenuRoutes.value.find((route) =>
              item.titles.some((title) => route.title === normalizeMenuTitle(title))
          );
          const resolvedPath = targetRoute?.path || "";
          return resolvedPath
              ? {
                label: item.label,
                icon: item.icon,
                path: resolvedPath,
              }
              : null;
        })
        .filter(Boolean)
);
const updateNowTime = () => {
@@ -1111,20 +1106,137 @@
const getOrderStatusText = (status) => {
  const mapping = {
    in_progress: "进行中",
    completed: "已完成",
    paused: "暂停",
    1: "待开始",
    2: "进行中",
    3: "已完成",
    4: "已暂停",
  };
  return mapping[status] || "未知";
};
const getOrderStatusType = (status) => {
  const mapping = {
    in_progress: "success",
    completed: "primary",
    paused: "warning",
    1: "info",
    2: "success",
    3: "primary",
    4: "warning",
  };
  return mapping[status] || "info";
};
const formatDueDate = (value) => {
  if (!value) return "--";
  const date = dayjs(value);
  return date.isValid() ? date.format("YYYY-MM-DD") : "--";
};
const mapOrderProgressRecord = (item = {}) => ({
  orderNo: item.orderNo || "--",
  productName: item.productName || "--",
  planQty: Number(item.plannedQuantity || 0),
  completedQty: Number(item.completedQuantity || 0),
  completionRate: clampPercent(Number(item.completionRate || 0)),
  deliveryDate: formatDueDate(item.dueDate),
  status: Number(item.status || 0),
  statusLabel: item.statusLabel || "",
});
const mapTodayPlanRecord = (item = {}) => ({
  orderNo: item.orderNo || "--",
  productName: item.productName || "--",
  planQty: Number(item.plannedQuantity || 0),
  deliveryDate: formatDueDate(item.dueDate),
  status: Number(item.status || 0),
  statusLabel: item.statusLabel || "",
});
const refreshProductionOverview = async () => {
  try {
    const res = await productionOverview();
    const data = res?.data || {};
    productionOverviewData.value = {
      totalOutput: Number(data.totalOutput || 0),
      totalScrap: Number(data.totalScrap || 0),
      yieldRate: Number(data.yieldRate || 0),
    };
  } catch {
    productionOverviewData.value = {
      totalOutput: 0,
      totalScrap: 0,
      yieldRate: 0,
    };
  }
};
const refreshProductionRealtimeBoard = async () => {
  try {
    const res = await productionRealtimeBoard();
    const data = res?.data || {};
    realtimeBoardData.value = {
      deviceOee: {
        value: Number(data.deviceOee?.value || 0),
        compareYesterday: Number(data.deviceOee?.compareYesterday || 0),
      },
      orderAchievementRate: {
        value: Number(data.orderAchievementRate?.value || 0),
        compareYesterday: Number(data.orderAchievementRate?.compareYesterday || 0),
      },
      defectRate: {
        value: Number(data.defectRate?.value || 0),
        compareYesterday: Number(data.defectRate?.compareYesterday || 0),
      },
    };
  } catch {
    realtimeBoardData.value = {
      deviceOee: { value: 0, compareYesterday: 0 },
      orderAchievementRate: { value: 0, compareYesterday: 0 },
      defectRate: { value: 0, compareYesterday: 0 },
    };
  }
};
const refreshProductionOrderProgress = async () => {
  try {
    const res = await productionOrderProgress({
      tab: orderFilter.value,
      pageNum: 1,
      pageSize: 10,
    });
    const data = res?.data || {};
    orderProgressMeta.value = {
      tab: data.tab || orderFilter.value,
      total: Number(data.total || 0),
      pageNum: Number(data.pageNum || 1),
      pageSize: Number(data.pageSize || 10),
      inProgressCount: Number(data.inProgressCount || 0),
      completedCount: Number(data.completedCount || 0),
      pausedCount: Number(data.pausedCount || 0),
    };
    productionOrders.value = (data.records || []).map(mapOrderProgressRecord);
  } catch {
    orderProgressMeta.value = {
      tab: orderFilter.value,
      total: 0,
      pageNum: 1,
      pageSize: 10,
      inProgressCount: 0,
      completedCount: 0,
      pausedCount: 0,
    };
    productionOrders.value = [];
  }
};
const refreshTodayProductionPlan = async () => {
  try {
    const res = await todayProductionPlan({ limit: 4 });
    const data = res?.data || {};
    todayPlanTotal.value = Number(data.total || 0);
    todayPlanList.value = (data.records || []).map(mapTodayPlanRecord);
  } catch {
    todayPlanTotal.value = 0;
    todayPlanList.value = [];
  }
};
const getBusinessData = async () => {
@@ -1238,11 +1350,20 @@
  router.push(path).catch(() => {});
};
watch(orderFilter, () => {
  if (visiblePanels.value.order) {
    refreshProductionOrderProgress();
  }
});
onMounted(() => {
  updateNowTime();
  clockTimer = setInterval(updateNowTime, 1000);
  if (dashboardCards.value.length > 0) {
    getBusinessData();
  }
  if (visibleModules.value.production) {
    refreshProductionOverview();
  }
  if (visiblePanels.value.contract) {
    analysisCustomer();
@@ -1260,6 +1381,15 @@
  if (visiblePanels.value.process) {
    getProcessList();
    refreshProcessStats();
  }
  if (visiblePanels.value.order) {
    refreshProductionOrderProgress();
  }
  if (visiblePanels.value.realtime) {
    refreshProductionRealtimeBoard();
  }
  if (visiblePanels.value.plan) {
    refreshTodayProductionPlan();
  }
});
@@ -1280,7 +1410,7 @@
  flex-direction: column;
  gap: 16px;
  overflow-x: clip;
    margin-top: 10px;
  margin-top: 10px;
}
.digital-number {
@@ -1305,8 +1435,8 @@
  min-height: 92px;
  padding: 18px 22px;
  background:
    radial-gradient(circle at 8% 20%, rgba(59, 130, 246, 0.12), transparent 32%),
    linear-gradient(135deg, rgba(255, 255, 255, 0.94), rgba(239, 246, 255, 0.88));
      radial-gradient(circle at 8% 20%, rgba(59, 130, 246, 0.12), transparent 32%),
      linear-gradient(135deg, rgba(255, 255, 255, 0.94), rgba(239, 246, 255, 0.88));
}
.welcome-user {
@@ -1522,7 +1652,7 @@
  z-index: 1;
  pointer-events: none;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 340 40' preserveAspectRatio='none'%3E%3Cpath d='M0 31C20 16 44 36 66 24C87 12 107 31 129 18C148 8 169 28 193 16C214 5 237 25 259 14C280 3 306 19 340 8' fill='none' stroke='%236ea4ee' stroke-width='1.5'/%3E%3C/svg%3E")
      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 340 40' preserveAspectRatio='none'%3E%3Cpath d='M0 31C20 16 44 36 66 24C87 12 107 31 129 18C148 8 169 28 193 16C214 5 237 25 259 14C280 3 306 19 340 8' fill='none' stroke='%236ea4ee' stroke-width='1.5'/%3E%3C/svg%3E")
      center / 100% 100% no-repeat;
}
@@ -1658,21 +1788,21 @@
  border: 1px solid rgba(148, 163, 184, 0.24);
  border-radius: 14px;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(244, 249, 255, 0.9)),
    repeating-linear-gradient(
      to right,
      rgba(148, 163, 184, 0.07) 0,
      rgba(148, 163, 184, 0.07) 1px,
      transparent 1px,
      transparent 48px
    ),
    repeating-linear-gradient(
      to bottom,
      rgba(148, 163, 184, 0.06) 0,
      rgba(148, 163, 184, 0.06) 1px,
      transparent 1px,
      transparent 34px
    );
      linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(244, 249, 255, 0.9)),
      repeating-linear-gradient(
          to right,
          rgba(148, 163, 184, 0.07) 0,
          rgba(148, 163, 184, 0.07) 1px,
          transparent 1px,
          transparent 48px
      ),
      repeating-linear-gradient(
          to bottom,
          rgba(148, 163, 184, 0.06) 0,
          rgba(148, 163, 184, 0.06) 1px,
          transparent 1px,
          transparent 34px
      );
  overflow: hidden;
  padding: 10px;
}
@@ -1953,6 +2083,10 @@
  color: #f59e0b;
}
.realtime-delta.flat {
  color: #64748b;
}
.warning-list {
  margin-top: 10px;
  display: flex;
src/views/procurementManagement/invoiceEntry/components/ExpandTable.vue
ÎļþÒÑɾ³ý
src/views/procurementManagement/invoiceEntry/components/Modal.vue
ÎļþÒÑɾ³ý
src/views/procurementManagement/invoiceEntry/index.vue
ÎļþÒÑɾ³ý
src/views/procurementManagement/paymentEntry/index.vue
ÎļþÒÑɾ³ý
src/views/procurementManagement/paymentHistory/index.vue
ÎļþÒÑɾ³ý
src/views/procurementManagement/paymentLedger/index.vue
@@ -1,64 +1,54 @@
<template>
  <div class="app-container">
    <div class="search_form" style="margin-bottom: 20px;">
    <div class="search_form"
         style="margin-bottom: 20px;">
      <div>
        <span class="search_title">供应商名称:</span>
        <el-input
          v-model="searchForm.supplierName"
          style="width: 240px"
          placeholder="输入供应商名称"
          @change="handleQuery"
          clearable
          :prefix-icon="Search"
        />
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
          >搜索</el-button
        >
        <el-input v-model="searchForm.supplierName"
                  style="width: 240px"
                  placeholder="输入供应商名称"
                  @change="handleQuery"
                  clearable
                  :prefix-icon="Search" />
        <el-button type="primary"
                   @click="handleQuery"
                   style="margin-left: 10px">搜索</el-button>
      </div>
      <div></div>
    </div>
    <el-row :gutter="20">
      <el-col :span="14">
        <div class="table_list">
          <el-table
            ref="multipleTable"
            border
            v-loading="tableLoading"
            :data="tableData"
            :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
            height="calc(100vh - 18.5em)"
            :highlight-current-row="true"
            style="width: 100%"
            tooltip-effect="dark"
            @row-click="rowClick"
            :show-summary="isShowSummary"
            :summary-method="summarizeMainTable"
            class="lims-table"
          >
            <el-table-column
              align="center"
              label="序号"
              type="index"
              width="60"
            />
            <el-table-column label="供应商名称" prop="supplierName" />
            <el-table-column
              label="合同金额(元)"
              prop="invoiceAmount"
              show-overflow-tooltip
              :formatter="formattedNumber"
            />
            <el-table-column
              label="付款金额(元)"
              prop="paymentAmount"
              show-overflow-tooltip
              :formatter="formattedNumber"
            />
            <el-table-column
              label="应付金额(元)"
              prop="payableAmount"
              show-overflow-tooltip
            >
          <el-table ref="multipleTable"
                    border
                    v-loading="tableLoading"
                    :data="tableData"
                    :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
                    height="calc(100vh - 18.5em)"
                    :highlight-current-row="true"
                    style="width: 100%"
                    tooltip-effect="dark"
                    @row-click="rowClick"
                    :show-summary="isShowSummary"
                    :summary-method="summarizeMainTable"
                    class="lims-table">
            <el-table-column align="center"
                             label="序号"
                             type="index"
                             width="60" />
            <el-table-column label="供应商名称"
                             prop="supplierName" />
            <el-table-column label="合同金额(元)"
                             prop="contractAmounts"
                             show-overflow-tooltip
                             :formatter="formattedNumber" />
            <el-table-column label="付款金额(元)"
                             prop="paymentAmount"
                             show-overflow-tooltip
                             :formatter="formattedNumber" />
            <el-table-column label="应付金额(元)"
                             prop="payableAmount"
                             show-overflow-tooltip>
              <template #default="{ row, column }">
                <el-text type="danger">
                  {{ formattedNumber(row, column, row.payableAmount) }}
@@ -66,29 +56,27 @@
              </template>
            </el-table-column>
          </el-table>
          <pagination
            v-show="total > 0"
            @pagination="paginationSearch"
            :total="total"
            :layout="page.layout"
            :page="page.current"
            :limit="page.size"
          />
          <pagination v-show="total > 0"
                      @pagination="paginationSearch"
                      :total="total"
                      :layout="page.layout"
                      :page="page.current"
                      :limit="page.size" />
        </div>
      </el-col>
      <el-col :span="10">
        <div class="table_list">
          <PIMTable
            rowKey="id"
            :column="tableColumnSon"
            :tableData="originalTableDataSon"
            :isSelection="false"
            :isShowPagination="false"
            :tableLoading="tableLoadingSon"
            :isShowSummary="isShowSummarySon"
            :summaryMethod="summarizeMainTable1"
                        height="calc(100vh - 18.5em)"
          >
          <PIMTable rowKey="id"
                    :column="tableColumnSon"
                    :tableData="originalTableDataSon"
                    :isSelection="false"
                    :isShowPagination="true"
                    :page="sonPage"
                    :tableLoading="tableLoadingSon"
                    :isShowSummary="isShowSummarySon"
                    :summaryMethod="summarizeMainTable1"
                    height="calc(100vh - 18.5em)"
                    @pagination="sonPaginationSearch">
            <template #payableAmountSlot="{ row }">
              <el-text type="danger">
                {{ parseFloat(row.payableAmount).toFixed(2) }}
@@ -102,191 +90,205 @@
</template>
<script setup>
import { ref, toRefs } from "vue";
import { Search } from "@element-plus/icons-vue";
import {
  paymentLedgerList,
  paymentRecordList,
} from "@/api/procurementManagement/paymentLedger.js";
import Pagination from "../../../components/PIMTable/Pagination.vue";
  import { ref, toRefs } from "vue";
  import { Search } from "@element-plus/icons-vue";
  import {
    paymentLedgerList,
    paymentRecordList,
  } from "@/api/procurementManagement/paymentLedger.js";
  import Pagination from "../../../components/PIMTable/Pagination.vue";
const tableData = ref([]);
const tableLoading = ref(false);
const data = reactive({
  searchForm: {
    supplierNameOrContractNo: "",
  },
});
const page = reactive({
  current: 1,
  size: 100,
});
const sonPage = reactive({
  current: 1,
  size: 100,
});
const total = ref(0);
const sonTotal = ref(0);
const isShowSummary = ref(true);
const { searchForm } = toRefs(data);
const currentSupplierId = ref("");
const rowClick = (row) => {
  currentSupplierId.value = row.supplierId;
  getPaymenRecordtList(row.supplierId);
};
// å­æ¨¡å—
const tableColumnSon = ref([
  {
    label: "发生日期",
    prop: "paymentDate",
        width: 110,
  },
  {
    label: "采购合同号",
    prop: "purchaseContractNumber",
        width: 150,
  },
  {
    label: "合同金额(元)",
    prop: "invoiceAmount",
        width: 200,
    formatData: (params) => {
      return params ? parseFloat(params).toFixed(2) : 0;
  const tableData = ref([]);
  const tableLoading = ref(false);
  const data = reactive({
    searchForm: {
      supplierName: "",
    },
  },
  {
    label: "付款金额(元)",
    prop: "paymentAmount",
        width: 200,
    formatData: (params) => {
      return params ? parseFloat(params).toFixed(2) : 0;
    },
  },
  {
    label: "应付金额(元)",
    dataType: "slot",
        width: 200,
    prop: "payableAmount",
    slot: "payableAmountSlot",
  },
]);
const tableDataSon = ref([]);
const originalTableDataSon = ref([]);
const tableLoadingSon = ref(false);
const isShowSummarySon = ref(true);
const detailPageNum = ref(1);
const detailPageSize = ref(10);
const { proxy } = getCurrentInstance();
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = (param) => {
  return proxy.summarizeTable(
    param,
    ["invoiceAmount", "paymentAmount", "payableAmount"],
    {
      ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
    }
  );
};
// å­è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable1 = (param) => {
  let summarizeTable = proxy.summarizeTable(
    param,
    ["invoiceAmount", "paymentAmount"],
    {
      ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
    }
  );
  if (originalTableDataSon.value.length > 0) {
    summarizeTable[summarizeTable.length - 1] =
      originalTableDataSon.value[
        originalTableDataSon.value.length - 1
      ].payableAmount.toFixed(2);
  } else {
    summarizeTable[summarizeTable.length - 1] = 0.0;
  }
  return summarizeTable;
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
const paginationSearch = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  paymentLedgerList({
    ...searchForm.value,
    ...page,
  }).then((res) => {
    let result = res.data;
    tableLoading.value = false;
    tableData.value = result.records || [];
    total.value = result.total || 0;
    if (tableData.value.length > 0) {
      getPaymenRecordtList(tableData.value[0].supplierId);
      currentSupplierId.value = tableData.value[0].supplierId;
    }
  });
};
  const page = reactive({
    current: 1,
    size: 100,
  });
  const sonPage = reactive({
    current: 1,
    size: 10,
    total: 0,
    layout: "total, sizes, prev, pager, next, jumper",
  });
  const total = ref(0);
  const isShowSummary = ref(true);
  const { searchForm } = toRefs(data);
  const currentSupplierId = ref("");
  const rowClick = row => {
    currentSupplierId.value = row.supplierId;
    sonPage.current = 1;
    getPaymenRecordtList(row.supplierId);
  };
  // å­æ¨¡å—
  const tableColumnSon = ref([
    {
      label: "合同签订日期",
      prop: "executionDate",
      width: 110,
    },
    {
      label: "采购合同号",
      prop: "purchaseContractNumber",
      width: 150,
    },
    {
      label: "合同金额(元)",
      prop: "contractAmount",
      width: 200,
      formatData: params => {
        return params ? parseFloat(params).toFixed(2) : 0;
      },
    },
    {
      label: "付款金额(元)",
      prop: "paymentAmount",
      width: 200,
      formatData: params => {
        return params ? parseFloat(params).toFixed(2) : 0;
      },
    },
    {
      label: "应付金额(元)",
      dataType: "slot",
      width: 200,
      prop: "payableAmount",
      slot: "payableAmountSlot",
    },
  ]);
  const tableDataSon = ref([]);
  const originalTableDataSon = ref([]);
  const tableLoadingSon = ref(false);
  const isShowSummarySon = ref(true);
  const { proxy } = getCurrentInstance();
const getPaymenRecordtList = (supplierId) => {
  tableLoadingSon.value = true;
  paymentRecordList({supplierId: supplierId})
    .then((res) => {
      tableLoadingSon.value = false;
      tableDataSon.value = res.data;
      handlePagination({ page: 1, limit: sonPage.size });
      sonTotal.value = res.data.length;
    })
    .catch((e) => {
      tableLoadingSon.value = false;
  // ä¸»è¡¨åˆè®¡æ–¹æ³•
  const summarizeMainTable = param => {
    return proxy.summarizeTable(
      param,
      ["contractAmounts", "paymentAmount", "payableAmount"],
      {
        ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
        futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      }
    );
  };
  // å­è¡¨åˆè®¡æ–¹æ³•
  const summarizeMainTable1 = param => {
    let summarizeTable = proxy.summarizeTable(
      param,
      ["contractAmount", "invoiceAmount", "paymentAmount"],
      {
        ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
        futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      }
    );
    if (originalTableDataSon.value.length > 0) {
      summarizeTable[summarizeTable.length - 1] =
        originalTableDataSon.value[
          originalTableDataSon.value.length - 1
        ].payableAmount.toFixed(2);
    } else {
      summarizeTable[summarizeTable.length - 1] = 0.0;
    }
    return summarizeTable;
  };
  /** æœç´¢æŒ‰é’®æ“ä½œ */
  const handleQuery = () => {
    page.current = 1;
    getList();
  };
  const paginationSearch = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
  };
  const getList = () => {
    tableLoading.value = true;
    paymentLedgerList({
      ...searchForm.value,
      ...page,
    }).then(res => {
      let result = res.data;
      tableLoading.value = false;
      tableData.value = result.records || [];
      total.value = result.total || 0;
      if (tableData.value.length > 0) {
        currentSupplierId.value = tableData.value[0].supplierId;
        sonPage.current = 1;
        getPaymenRecordtList(tableData.value[0].supplierId);
      }
    });
};
const handlePagination = ({ page, limit }) => {
  sonPage.current = page;
  sonPage.size = limit;
  };
  const start = (page - 1) * limit;
  const end = start + limit;
  const getPaymenRecordtList = supplierId => {
    tableLoadingSon.value = true;
    paymentRecordList({
      supplierId: supplierId,
      current: sonPage.current,
      size: sonPage.size,
    })
      .then(res => {
        tableLoadingSon.value = false;
        let result = res.data;
        if (Array.isArray(result)) {
          tableDataSon.value = result;
          sonPage.total = result.length;
          handlePagination({ page: sonPage.current, limit: sonPage.size });
        } else {
          originalTableDataSon.value = result.records || [];
          sonPage.total = result.total || 0;
        }
      })
      .catch(e => {
        tableLoadingSon.value = false;
      });
  };
  const handlePagination = ({ page, limit }) => {
    console.log(page, limit);
    sonPage.current = page;
    sonPage.size = limit;
  originalTableDataSon.value = tableDataSon.value.slice(start, end);
};
    const start = (page - 1) * limit;
    const end = start + limit;
const sonPaginationSearch = (pagination) => {
  // æŽ¥æ”¶åˆ†é¡µå™¨å‚æ•° { page, limit }
  handlePagination(pagination);
};
const formattedNumber = (row, column, cellValue) => {
  if (column.property !== "supplierName") {
    return parseFloat(cellValue).toFixed(2);
  } else {
    return cellValue;
  }
};
getList();
    originalTableDataSon.value = tableDataSon.value.slice(start, end);
  };
  const sonPaginationSearch = pagination => {
    // æŽ¥æ”¶åˆ†é¡µå™¨å‚æ•° { page, limit }
    sonPage.current = pagination.page;
    sonPage.size = pagination.limit;
    getPaymenRecordtList(currentSupplierId.value);
  };
  const formattedNumber = (row, column, cellValue) => {
    if (column.property !== "supplierName") {
      return parseFloat(cellValue).toFixed(2);
    } else {
      return cellValue;
    }
  };
  getList();
</script>
<style scoped lang="scss">
.el-pagination {
  width: 100%;
  height: 55px;
  display: flex;
  justify-content: flex-end;
  float: right;
  flex-direction: row;
  align-items: center;
  background: #fff;
  margin: -20px 0 0 0;
  padding: 0 20px;
}
.pagination-container {
  margin-top: 0;
}
  .el-pagination {
    width: 100%;
    height: 55px;
    display: flex;
    justify-content: flex-end;
    float: right;
    flex-direction: row;
    align-items: center;
    background: #fff;
    margin: -20px 0 0 0;
    padding: 0 20px;
  }
  .pagination-container {
    margin-top: 0;
  }
</style>
src/views/procurementManagement/procurementInvoiceLedger/Modal/EditModal.vue
ÎļþÒÑɾ³ý
src/views/procurementManagement/procurementInvoiceLedger/Modal/UploadModal.vue
ÎļþÒÑɾ³ý
src/views/procurementManagement/procurementInvoiceLedger/index.vue
ÎļþÒÑɾ³ý
src/views/productionManagement/processRoute/index.vue
@@ -61,7 +61,9 @@
  import EditProcess from "@/views/productionManagement/processRoute/Edit.vue";
  import RouteItemForm from "@/views/productionManagement/processRoute/ItemsForm.vue";
  import { listPage, del } from "@/api/productionManagement/processRoute.js";
  const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue"));
  const FileList = defineAsyncComponent(() =>
    import("@/components/Dialog/FileList.vue")
  );
  import { useRouter } from "vue-router";
  import { ElMessage, ElMessageBox } from "element-plus";
src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -47,16 +47,45 @@
            <span class="info-value">{{ routeInfo.quantity || '-' }}</span>
          </div>
        </div>
        <div class="info-item full-width"
             v-if="routeInfo.description">
        <div class="info-item">
          <div class="info-label-wrapper">
            <span class="info-label">描述</span>
            <span class="info-label">备注</span>
          </div>
          <div class="info-value-wrapper">
            <span class="info-value">{{ routeInfo.description }}</span>
          </div>
        </div>
      </div>
    </el-card>
    <!-- é™„件模块 -->
    <div v-if="pageType === 'order'"
         class="section-header">
      <div class="section-title">附件</div>
    </div>
    <el-card v-if="pageType === 'order'"
             class="attachment-card"
             shadow="hover"
             style="margin-top: 10px; margin-bottom: 20px;">
      <el-table :data="attachmentTableData"
                border
                class="attachment-table">
        <el-table-column label="附件名称"
                         prop="originalFilename"
                         show-overflow-tooltip />
        <el-table-column fixed="right"
                         label="操作"
                         width="200"
                         align="center">
          <template #default="scope">
            <el-button link
                       type="primary"
                       size="small"
                       @click="downloadAttachmentFile(scope.row.downloadURL)">
              ä¸‹è½½
            </el-button>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
    <!-- è¡¨æ ¼è§†å›¾ -->
    <div v-if="viewMode === 'table'"
@@ -382,6 +411,18 @@
                         v-model="bomDataValue.showProductDialog"
                         :single="true"
                         @confirm="handleBomProduct" />
    <!-- ä¸Šä¼ ç»„件弹窗 -->
    <el-dialog v-model="uploadDialogVisible"
               title="上传附件"
               width="50%"
               @close="closeAttachmentUpload">
      <AttachmentUpload v-model:file-list="newFileList" />
      <template #footer>
        <el-button @click="saveAttachmentUpload"
                   type="primary">保存</el-button>
        <el-button @click="closeAttachmentUpload">关闭</el-button>
      </template>
    </el-dialog>
    <!-- æ–°å¢ž/编辑弹窗 -->
    <el-dialog v-model="dialogVisible"
               :title="operationType === 'add' ? '新增工艺路线项目' : '编辑工艺路线项目'"
@@ -518,6 +559,12 @@
    queryList2,
    add2,
  } from "@/api/productionManagement/productStructure.js";
  import AttachmentUpload from "@/components/AttachmentUpload/file/index.vue";
  import {
    attachmentList,
    deleteAttachment,
    createAttachment,
  } from "@/api/basicData/storageAttachment.js";
  import { useRoute } from "vue-router";
  import { ElMessageBox, ElMessage } from "element-plus";
@@ -530,6 +577,7 @@
  const orderId = computed(() => route.query.orderId);
  const pageType = computed(() => route.query.type);
  const editable = computed(() => route.query.editable !== "false");
  const technologyRoutingId = computed(() => route.query.technologyRoutingId);
  const tableLoading = ref(false);
  const tableData = ref([]);
@@ -548,7 +596,66 @@
    bomNo: "",
    description: "",
    quantity: 0,
    technologyRoutingId: "",
  });
  // é™„件相关
  const attachmentTableData = ref([]);
  const uploadDialogVisible = ref(false);
  const newFileList = ref([]);
  const getAttachmentList = () => {
    if (!technologyRoutingId.value) return;
    attachmentList({
      recordType: "technology_routing",
      recordId: technologyRoutingId.value,
    }).then(res => {
      attachmentTableData.value = (res && res.data) || [];
    });
  };
  const handleUploadAttachment = () => {
    uploadDialogVisible.value = true;
  };
  const saveAttachmentUpload = async () => {
    if (newFileList.value.length > 0) {
      createAttachment({
        application: "file",
        recordType: "technology_routing",
        recordId: technologyRoutingId.value,
        storageBlobDTOs: [...newFileList.value, ...attachmentTableData.value],
      })
        .then(res => {
          if (res && res.code === 200) {
            proxy?.$modal?.msgSuccess("上传成功");
            newFileList.value = [];
            getAttachmentList();
          }
        })
        .finally(() => {
          uploadDialogVisible.value = false;
        });
    }
  };
  const closeAttachmentUpload = () => {
    newFileList.value = [];
    uploadDialogVisible.value = false;
  };
  const handleDeleteAttachment = async row => {
    deleteAttachment([row.storageAttachmentId]).then(res => {
      if (res && res.code === 200) {
        proxy?.$modal?.msgSuccess("删除成功");
        getAttachmentList();
      }
    });
  };
  const downloadAttachmentFile = url => {
    window.open(url, "_blank");
  };
  const processOptions = ref([]);
  const showProductSelectDialog = ref(false);
@@ -680,6 +787,7 @@
      bomId: route.query.bomId || "",
      description: route.query.description || "",
      quantity: route.query.quantity || 0,
      technologyRoutingId: route.query.technologyRoutingId || "",
      status: !(route.query.status == 1 || route.query.status === "false"),
    };
    bomTableData.value[0].productName = routeInfo.value.productName;
@@ -1165,16 +1273,20 @@
  const handleBomProcessChange = (row, value) => {
    row.processId = value || "";
    syncProcessOperationFields(row);
    // åŒä¸€å±‚级只能选一样的工序
    // æ£€æŸ¥åŒä¸€å±‚级是否已经有其他不同的工序被选中
    const siblings = findSiblings(bomDataValue.value.dataList, row.tempId);
    if (siblings && value) {
      siblings.forEach(sibling => {
        if (sibling.tempId !== row.tempId) {
          sibling.processId = value;
          syncProcessOperationFields(sibling);
        }
      const hasDifferentProcess = siblings.some(sibling => {
        return (
          sibling.tempId !== row.tempId &&
          sibling.processId &&
          sibling.processId !== value
        );
      });
      if (hasDifferentProcess) {
        ElMessage.warning("同一层级已存在不同的工序,请先统一工序后再进行修改");
      }
    }
  };
@@ -1391,9 +1503,36 @@
      }
    };
    // æ ¡éªŒåŒä¸€å±‚级的工序是否一致
    const validateProcessConsistency = items => {
      if (!items || items.length === 0) return;
      // æ£€æŸ¥å½“前层级
      const processes = items
        .filter(item => item.processId)
        .map(item => item.processId);
      if (processes.length > 1) {
        const uniqueProcesses = [...new Set(processes)];
        if (uniqueProcesses.length > 1) {
          ElMessage.error("同一层级的工序必须一致");
          isValid = false;
          return;
        }
      }
      // é€’归检查子级
      items.forEach(item => {
        if (item.children && item.children.length > 0) {
          validateProcessConsistency(item.children);
        }
      });
    };
    bomDataValue.value.dataList.forEach(item => {
      validateItem(item, true);
    });
    validateProcessConsistency(bomDataValue.value.dataList);
    return isValid;
  };
@@ -1449,6 +1588,9 @@
    getList();
    getProcessList();
    fetchBomData();
    if (pageType.value === "order") {
      getAttachmentList();
    }
  };
  onMounted(() => {
src/views/productionManagement/productStructure/Detail/index.vue
@@ -336,15 +336,15 @@
    row.processId = value || "";
    syncProcessOperationFields(row);
    
    // åŒä¸€å±‚级只能选一样的工序
    // æ£€æŸ¥åŒä¸€å±‚级是否已经有其他不同的工序被选中
    const siblings = findSiblings(dataValue.dataList, row.tempId);
    if (siblings && value) {
      siblings.forEach(sibling => {
        if (sibling.tempId !== row.tempId) {
          sibling.processId = value;
          syncProcessOperationFields(sibling);
        }
      const hasDifferentProcess = siblings.some(sibling => {
        return sibling.tempId !== row.tempId && sibling.processId && sibling.processId !== value;
      });
      if (hasDifferentProcess) {
        ElMessage.warning("同一层级已存在不同的工序,请先统一工序后再进行修改");
      }
    }
  };
src/views/productionManagement/productionOrder/index.vue
@@ -723,6 +723,7 @@
          bomNo: row.bomNo || "",
          description: data.description || "",
          quantity: row.quantity || 0,
          technologyRoutingId: data.technologyRoutingId,
          orderId,
          type: "order",
          editable: !row.endOrder,
src/views/safeProduction/safetyTrainingAssessment/detail.vue
@@ -111,8 +111,6 @@
<script setup>
  import { onMounted, ref } from "vue";
  import { invoiceLedgerSalesAccount } from "@/api/salesManagement/invoiceLedger.js";
  import { customerInteractions } from "@/api/salesManagement/receiptPayment.js";
  import Pagination from "@/components/PIMTable/Pagination.vue";
  import {
    safeTrainingDetailListPage,
src/views/salesManagement/invoiceLedger/index.vue
ÎļþÒÑɾ³ý
src/views/salesManagement/invoiceRegistration/index.vue
ÎļþÒÑɾ³ý
src/views/salesManagement/receiptPayment/index.vue
ÎļþÒÑɾ³ý
src/views/salesManagement/receiptPaymentHistory/index.vue
ÎļþÒÑɾ³ý
src/views/salesManagement/receiptPaymentLedger/index.vue
@@ -1,268 +1,252 @@
<template>
  <div class="app-container">
    <div class="search_form" style="margin-bottom: 20px;">
    <div class="search_form"
         style="margin-bottom: 20px;">
      <div>
        <span class="search_title">客户名称:</span>
        <el-input
          v-model="searchForm.searchText"
          style="width: 240px"
          placeholder="输入客户名称搜索"
          @change="handleQuery"
          clearable
          prefix-icon="Search"
        />
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
          >搜索</el-button
        >
        <el-input v-model="searchForm.searchText"
                  style="width: 240px"
                  placeholder="输入客户名称搜索"
                  @change="handleQuery"
                  clearable
                  prefix-icon="Search" />
        <el-button type="primary"
                   @click="handleQuery"
                   style="margin-left: 10px">搜索</el-button>
      </div>
    </div>
    <div style="display: flex">
      <div class="table_list">
        <el-table
          :data="tableData"
          border
          v-loading="tableLoading"
          :row-key="(row) => row.id"
          show-summary
          :summary-method="summarizeMainTable"
          @row-click="rowClickMethod"
          height="calc(100vh - 18.5em)"
        >
          <el-table-column
            align="center"
            label="序号"
            type="index"
            width="60"
          />
          <el-table-column
            label="客户名称"
            prop="customerName"
            show-overflow-tooltip
                        width="200"
          />
          <el-table-column
            label="合同金额(元)"
            prop="invoiceTotal"
            show-overflow-tooltip
            :formatter="formattedNumber"
                        width="200"
          />
          <el-table-column
            label="回款金额(元)"
            prop="receiptPaymentAmount"
            show-overflow-tooltip
            :formatter="formattedNumber"
                        width="200"
          />
          <el-table-column
            label="应收金额(元)"
            prop="unReceiptPaymentAmount"
            show-overflow-tooltip
                        width="200"
          >
            <template #default="{ row, column }">
              <el-text type="danger">
                {{ formattedNumber(row, column, row.unReceiptPaymentAmount) }}
              </el-text>
            </template>
          </el-table-column>
        </el-table>
        <pagination
          v-show="total > 0"
          :total="total"
          layout="total, sizes, prev, pager, next, jumper"
          :page="page.current"
          :limit="page.size"
          @pagination="paginationChange"
        />
      </div>
      <div class="table_list">
        <el-table
          :data="receiptRecord"
          border
          :row-key="(row) => row.id"
          show-summary
          :summary-method="summarizeMainTable1"
          height="calc(100vh - 18.5em)"
        >
          <el-table-column
            align="center"
            label="序号"
            type="index"
            width="60"
          />
          <el-table-column
            label="发生日期"
            prop="receiptPaymentDate"
            show-overflow-tooltip
                        width="110"
          />
          <el-table-column
            label="销售合同号"
            prop="salesContractNo"
            show-overflow-tooltip
                        width="200"
          />
          <el-table-column
            label="合同金额(元)"
            prop="invoiceTotal"
            show-overflow-tooltip
            :formatter="formattedNumber"
                        width="200"
          />
          <el-table-column
            label="回款金额(元)"
            prop="receiptPaymentAmount"
            show-overflow-tooltip
            :formatter="formattedNumber"
                        width="200"
          />
          <el-table-column
            label="应收金额(元)"
            prop="unReceiptPaymentAmount"
            show-overflow-tooltip
                        width="200"
          >
            <template #default="{ row, column }">
              <el-text type="danger">
                {{ formattedNumber(row, column, row.unReceiptPaymentAmount) }}
              </el-text>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </div>
    <el-row :gutter="20">
      <el-col :span="12">
        <div class="table_list"
             style="width: 100%">
          <el-table :data="tableData"
                    border
                    v-loading="tableLoading"
                    :row-key="(row) => row.customerId"
                    show-summary
                    :summary-method="summarizeMainTable"
                    @row-click="rowClickMethod"
                    highlight-current-row
                    height="calc(100vh - 18.5em)">
            <el-table-column align="center"
                             label="序号"
                             type="index"
                             width="60" />
            <el-table-column label="客户名称"
                             prop="customerName"
                             show-overflow-tooltip
                             width="200" />
            <el-table-column label="合同金额(元)"
                             prop="invoiceTotal"
                             show-overflow-tooltip
                             :formatter="formattedNumber"
                             width="200" />
            <el-table-column label="回款金额(元)"
                             prop="receiptPaymentAmount"
                             show-overflow-tooltip
                             :formatter="formattedNumber"
                             width="200" />
            <el-table-column label="应收金额(元)"
                             prop="unReceiptPaymentAmount"
                             show-overflow-tooltip
                             width="200">
              <template #default="{ row, column }">
                <el-text type="danger">
                  {{ formattedNumber(row, column, row.unReceiptPaymentAmount) }}
                </el-text>
              </template>
            </el-table-column>
          </el-table>
          <pagination v-show="total > 0"
                      :total="total"
                      layout="total, sizes, prev, pager, next, jumper"
                      :page="page.current"
                      :limit="page.size"
                      @pagination="paginationChange" />
        </div>
      </el-col>
      <el-col :span="12">
        <div class="table_list"
             style="width: 100%">
          <el-table :data="receiptRecord"
                    border
                    :row-key="(row) => row.id"
                    show-summary
                    :summary-method="summarizeMainTable1"
                    height="calc(100vh - 18.5em)">
            <el-table-column align="center"
                             label="序号"
                             type="index"
                             width="60" />
            <el-table-column label="合同签订日期"
                             prop="executionDate"
                             show-overflow-tooltip
                             width="110" />
            <el-table-column label="销售合同号"
                             prop="salesContractNo"
                             show-overflow-tooltip
                             width="200" />
            <el-table-column label="合同金额(元)"
                             prop="contractAmount"
                             show-overflow-tooltip
                             :formatter="formattedNumber"
                             width="200" />
            <el-table-column label="回款金额(元)"
                             prop="receiptPaymentAmount"
                             show-overflow-tooltip
                             :formatter="formattedNumber"
                             width="200" />
            <el-table-column label="应收金额(元)"
                             prop="receiptableAmount"
                             show-overflow-tooltip
                             width="200">
              <template #default="{ row, column }">
                <el-text type="danger">
                  {{ formattedNumber(row, column, row.receiptableAmount) }}
                </el-text>
              </template>
            </el-table-column>
          </el-table>
          <pagination v-show="recordTotal > 0"
                      :total="recordTotal"
                      layout="total, sizes, prev, pager, next, jumper"
                      :page="recordPage.current"
                      :limit="recordPage.size"
                      @pagination="recordPaginationChange" />
        </div>
      </el-col>
    </el-row>
  </div>
</template>
<script setup>
import {onMounted, ref} from "vue";
import { invoiceLedgerSalesAccount } from "../../../api/salesManagement/invoiceLedger.js";
import { customerInteractions } from "../../../api/salesManagement/receiptPayment.js";
import Pagination from "../../../components/PIMTable/Pagination.vue";
const { proxy } = getCurrentInstance();
const tableData = ref([]);
const receiptRecord = ref([]);
const tableLoading = ref(false);
const page = reactive({
  current: 1,
  size: 100,
});
const recordPage = reactive({
  current: 1,
  size: 100,
});
const total = ref(0);
const recordTotal = ref(0);
const data = reactive({
  searchForm: {
    searchText: "",
    invoiceDate: "",
  },
});
const customerId = ref("");
const { searchForm } = toRefs(data);
const originReceiptRecord = ref([]);
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
const paginationChange = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  invoiceLedgerSalesAccount({ ...searchForm.value, ...page }).then((res) => {
    tableLoading.value = false;
    tableData.value = res.data.records;
    total.value = res.data.total;
    if (tableData.value.length > 0) {
      recordPage.current = 1;
      customerId.value = tableData.value[0].id;
      receiptPaymentList(customerId.value);
    }
  import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue";
  import {
    customewTransactions,
    customewTransactionsDetails,
  } from "@/api/salesManagement/indicatorStats.js";
  import Pagination from "../../../components/PIMTable/Pagination.vue";
  const { proxy } = getCurrentInstance();
  const tableData = ref([]);
  const receiptRecord = ref([]);
  const tableLoading = ref(false);
  const page = reactive({
    current: 1,
    size: 100,
  });
};
const formattedNumber = (row, column, cellValue) => {
  return parseFloat(cellValue).toFixed(2);
};
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = (param) => {
  return proxy.summarizeTable(
    param,
    ["invoiceTotal", "receiptPaymentAmount", "unReceiptPaymentAmount"],
    {
      ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
    }
  );
};
// å­è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable1 = (param) => {
  var summarizeTable = proxy.summarizeTable(
    param,
    ["invoiceAmount", "receiptAmount", "unReceiptAmount"],
    {
      ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
    }
  );
  // å–最后一行数据;
  if (receiptRecord.value?.length > 0) {
    const index = tableData.value.findIndex(
      (item) => item.id == customerId.value
    );
    summarizeTable[summarizeTable.length - 1] =
      tableData.value[index].unReceiptPaymentAmount.toFixed(2);
  } else {
    summarizeTable[summarizeTable.length - 1] = 0.0;
  }
  return summarizeTable;
};
const receiptPaymentList = (id) => {
  const param = {
    customerId: id,
  const recordPage = reactive({
    current: 1,
    size: 100,
  });
  const total = ref(0);
  const recordTotal = ref(0);
  const data = reactive({
    searchForm: {
      searchText: "",
      invoiceDate: "",
    },
  });
  const customerId = ref("");
  const { searchForm } = toRefs(data);
  const originReceiptRecord = ref([]);
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
  const handleQuery = () => {
    page.current = 1;
    getList();
  };
  console.log("param", param);
  customerInteractions(param).then((res) => {
    originReceiptRecord.value = res.data;
    handlePagination({ page: 1, limit: recordPage.size });
    recordTotal.value = res.data.length;
  const paginationChange = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
  };
  const getList = () => {
    tableLoading.value = true;
    customewTransactions({ ...searchForm.value, ...page }).then(res => {
      tableLoading.value = false;
      tableData.value = res.data.records;
      total.value = res.data.total;
      if (tableData.value.length > 0) {
        recordPage.current = 1;
        customerId.value = tableData.value[0].customerId;
        receiptPaymentList(customerId.value);
      }
    });
  };
  const formattedNumber = (row, column, cellValue) => {
    return cellValue ? parseFloat(cellValue).toFixed(2) : "0.00";
  };
  // ä¸»è¡¨åˆè®¡æ–¹æ³•
  const summarizeMainTable = param => {
    return proxy.summarizeTable(
      param,
      ["invoiceTotal", "receiptPaymentAmount", "unReceiptPaymentAmount"],
      {
        ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
        futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      }
    );
  };
  // å­è¡¨åˆè®¡æ–¹æ³•
  const summarizeMainTable1 = param => {
    var summarizeTable = proxy.summarizeTable(
      param,
      ["contractAmount", "receiptPaymentAmount", "receiptableAmount"],
      {
        ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
        futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      }
    );
    return summarizeTable;
  };
  const receiptPaymentList = id => {
    const param = {
      customerId: id,
      current: recordPage.current,
      size: recordPage.size,
    };
    customewTransactionsDetails(param).then(res => {
      if (Array.isArray(res.data)) {
        originReceiptRecord.value = res.data;
        recordTotal.value = res.data.length;
        handlePagination({ page: 1, limit: recordPage.size });
      } else {
        receiptRecord.value = res.data.records;
        recordTotal.value = res.data.total;
      }
    });
  };
  // æ±‡æ¬¾è®°å½•列表分页
  const recordPaginationChange = pagination => {
    recordPage.current = pagination.page;
    recordPage.size = pagination.limit;
    receiptPaymentList(customerId.value);
  };
  const rowClickMethod = row => {
    customerId.value = row.customerId;
    receiptPaymentList(customerId.value);
  };
  const handlePagination = ({ page, limit }) => {
    recordPage.current = page;
    recordPage.size = limit;
    const start = (page - 1) * limit;
    const end = start + limit;
    receiptRecord.value = originReceiptRecord.value.slice(start, end);
  };
  onMounted(() => {
    getList();
  });
};
// æ±‡æ¬¾è®°å½•列表分页
const recordPaginationChange = (pagination) => {
  handlePagination(pagination);
};
const rowClickMethod = (row) => {
  customerId.value = row.id;
  receiptPaymentList(customerId.value);
};
const handlePagination = ({ page, limit }) => {
  recordPage.current = page;
  recordPage.size = limit;
  const start = (page - 1) * limit;
  const end = start + limit;
  receiptRecord.value = originReceiptRecord.value.slice(start, end);
};
onMounted(() => {
    getList();
});
</script>
<style scoped lang="scss">
.table_list {
  width: 50%;
}
  .table_list {
    width: 50%;
  }
</style>
src/views/salesManagement/salesLedger/index.vue
@@ -284,10 +284,15 @@
          <el-col :span="12">
            <el-form-item label="销售合同号:"
                          prop="salesContractNo">
              <el-input v-model="form.salesContractNo"
                        placeholder="自动生成"
                        clearable
                        disabled />
              <div style="display: flex; align-items: center; gap: 12px;width: 100%;">
                <el-checkbox v-model="form.autoGenerateContractNo" v-if="operationType === 'add'">自动生成
                </el-checkbox>
                <el-input v-model="form.salesContractNo"
                          :placeholder="form.autoGenerateContractNo ? '自动生成' : '请输入'"
                          clearable
                          :disabled="form.autoGenerateContractNo" />
              </div>
            </el-form-item>
          </el-col>
          <el-col :span="12">
@@ -1069,6 +1074,7 @@
    },
    form: {
      salesContractNo: "",
      autoGenerateContractNo: true,
      salesman: "",
      customerId: "",
      entryPerson: "",
@@ -1587,6 +1593,8 @@
      form.value.entryDate = getCurrentDate();
      // ç­¾è®¢æ—¥æœŸé»˜è®¤ä¸ºå½“天
      form.value.executionDate = getCurrentDate();
      // é»˜è®¤è‡ªåŠ¨ç”Ÿæˆé”€å”®åˆåŒå·
      form.value.autoGenerateContractNo = true;
    } else {
      currentId.value = row.id;
      getSalesLedgerWithProducts({ id: row.id, type: 1 }).then(res => {
@@ -1594,6 +1602,8 @@
        form.value.entryPerson = Number(res.entryPerson);
        productData.value = form.value.productData;
        fileList.value = form.value.storageBlobVOs;
        // ç¼–辑时设置自动生成为false,允许手动修改
        form.value.autoGenerateContractNo = false;
      });
    }
    // let userAll = await userStore.getInfo()
@@ -1730,6 +1740,9 @@
        }
        form.value.storageBlobDTOs = fileList;
        form.value.type = 1;
        if (form.value.autoGenerateContractNo) {
          form.value.salesContractNo = '';
        }
        addOrUpdateSalesLedger(form.value).then(res => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
@@ -3007,4 +3020,4 @@
      page-break-after: avoid;
    }
  }
</style>
</style>