Prechádzať zdrojové kódy

Merge branch 'dev' of 21.tcp.vip.cpolar.cn:hzy-starrysky/zkhx/gubersail/gubersail-platform-ui into dev

yz 4 mesiacov pred
rodič
commit
33d5de317f

+ 6 - 17
src/components/forecast-form/forecast-form-mixin.js

@@ -79,6 +79,7 @@ import { getCustomerList, getItemList, getCustomerInfo } from '@/api/common'
 // 表单配置导入
 import { getFormOption } from './form-option'
 import { safeBigInt } from '@/util/util'
+import { getFilenameFromContentDisposition } from '@/utils/content-disposition'
 
 /**
  * 销售预测表单事件常量
@@ -1311,15 +1312,8 @@ export default {
 
         const res = await exportSalesForecastTemplate()
 
-        // 处理文件名:优先取响应头,其次回退默认名
-        const disposition = res?.headers?.['content-disposition'] || res?.headers?.['Content-Disposition']
-        let filename = '销售预测模板.xlsx'
-        if (disposition) {
-          const utf8Match = /filename\*=UTF-8''([^;\n]+)/i.exec(disposition)
-          const plainMatch = /filename="?([^;\n"]+)"?/i.exec(disposition)
-          const raw = utf8Match ? decodeURIComponent(utf8Match[1]) : (plainMatch ? plainMatch[1] : '')
-          if (raw) filename = raw
-        }
+        const disposition = res?.headers?.['content-disposition'] || res?.headers?.['Content-Disposition'] || ''
+        const filename = getFilenameFromContentDisposition(disposition, '销售预测模板.xlsx')
 
         const blob = new Blob([res.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
         const url = window.URL.createObjectURL(blob)
@@ -1596,15 +1590,10 @@ export default {
         this.dataExportLoading = true
         const res = await exportSalesForecastSummaryByYearMonth(year, month)
 
-        const disposition = res?.headers?.['content-disposition'] || res?.headers?.['Content-Disposition']
         const defaultMonth = String(month).padStart(2, '0')
-        let filename = `销售预测汇总_${year}-${defaultMonth}.xlsx`
-        if (disposition) {
-          const utf8Match = /filename\*=UTF-8''([^;\n]+)/i.exec(disposition)
-          const plainMatch = /filename="?([^;\n"]+)"?/i.exec(disposition)
-          const raw = utf8Match ? decodeURIComponent(utf8Match[1]) : (plainMatch ? plainMatch[1] : '')
-          if (raw) filename = raw
-        }
+        const fallbackFilename = `销售预测汇总_${year}-${defaultMonth}.xlsx`
+        const disposition = res?.headers?.['content-disposition'] || res?.headers?.['Content-Disposition'] || ''
+        const filename = getFilenameFromContentDisposition(disposition, fallbackFilename)
 
         const blob = new Blob([res.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
         const url = window.URL.createObjectURL(blob)

+ 3 - 8
src/components/order-form/material-detail-mixin.js

@@ -24,6 +24,7 @@ import {
 import { MATERIAL_DETAIL_EVENTS, DIALOG_EVENTS } from './events'
 import { getMaterialFullList, downloadSalesOrderTemplate, importSalesOrderById } from '@/api/order/sales-order'
 import { getBrandStockList } from '@/api/forecast/brand-stock'
+import { getFilenameFromContentDisposition } from '@/utils/content-disposition'
 import {
   formatAmount,
   formatFloatNumber,
@@ -607,14 +608,8 @@ export default {
 
         const res = await downloadSalesOrderTemplate()
 
-        const disposition = res?.headers?.['content-disposition'] || res?.headers?.['Content-Disposition']
-        let filename = '销售订单模板.xlsx'
-        if (disposition) {
-          const utf8Match = /filename\*=UTF-8''([^;\n]+)/i.exec(disposition)
-          const plainMatch = /filename="?([^;\n"]+)"?/i.exec(disposition)
-          const raw = utf8Match ? decodeURIComponent(utf8Match[1]) : (plainMatch ? plainMatch[1] : '')
-          if (raw) filename = raw
-        }
+        const disposition = res?.headers?.['content-disposition'] || res?.headers?.['Content-Disposition'] || ''
+        const filename = getFilenameFromContentDisposition(disposition, '销售订单模板.xlsx')
 
         const blob = new Blob([res.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
         const url = window.URL.createObjectURL(blob)

+ 187 - 544
src/mixins/order/invoiceMixin.js

@@ -1,51 +1,12 @@
-import { getList, add, update, remove, getDetail, updateStatus } from '@/api/order/invoice'
-import { mapGetters } from 'vuex'
-import {
-  INVOICE_TYPES,
-  INVOICE_STATUS,
-  INVOICE_TYPE_OPTIONS,
-  INVOICE_STATUS_OPTIONS,
-  getInvoiceTypeLabel,
-  getInvoiceTypeTagType,
-  getInvoiceStatusLabel,
-  getInvoiceStatusTagType
-} from '@/constants'
+// @ts-check
+import { getARBillHeadStatusInquiry } from '@/api/comprehensive'
 
 /**
- * 发票查询参数类型定义
- * @typedef {Object} InvoiceQueryParams
- * @property {string} [orderId] - 订单ID
- * @property {string} [invoiceId] - 发票ID
- * @property {string} [invoiceNo] - 发票号码
- * @property {string} [customerName] - 客户名称
- * @property {number} [invoiceStatus] - 开票状态
- * @property {string} [startDate] - 开始日期
- * @property {string} [endDate] - 结束日期
+ * @typedef {import('@/api/types/comprehensive').ARBillHeadStatusInquiryParams} ARBillHeadStatusInquiryParams
+ * @typedef {import('@/api/types/comprehensive').ARBillHeadStatusInquiryRecord} ARBillHeadStatusInquiryRecord
  */
 
 /**
- * 发票表单数据类型定义
- * @typedef {Object} InvoiceForm
- * @property {string} [id] - 发票ID
- * @property {string} invoiceNo - 发票号码
- * @property {string} invoiceCode - 发票代码
- * @property {number} orderId - 订单ID
- * @property {string} orderCode - 订单编码
- * @property {number} customerId - 客户ID
- * @property {string} customerCode - 客户编码
- * @property {string} customerName - 客户名称
- * @property {string} taxNo - 税号
- * @property {string} invoiceTitle - 发票抬头
- * @property {string} amount - 金额
- * @property {string} taxAmount - 税额
- * @property {string} totalAmount - 总金额
- * @property {string} invoiceType - 发票类型
- * @property {number} invoiceStatus - 开票状态
- * @property {string} invoiceDate - 开票日期
- */
-
-/**
- * 分页信息类型定义
  * @typedef {Object} PageInfo
  * @property {number} pageSize - 每页大小
  * @property {number} currentPage - 当前页码
@@ -53,16 +14,18 @@ import {
  */
 
 /**
- * 发票管理混入
- * @mixin InvoiceMixin
+ * 发票及开票信息查询混入(应收单)
+ * @description 使用 U9Cloud 综合查询接口查询应收单开票状态
  */
 export default {
+  name: 'InvoiceStatusQuery',
+
   data() {
     return {
-      /** @type {InvoiceForm} 表单数据 */
+      /** @type {Record<string, any>} 表单数据 */
       form: {},
 
-      /** @type {InvoiceQueryParams} 查询参数 */
+      /** @type {Partial<ARBillHeadStatusInquiryParams> & { businessDate?: string | [string, string], fuzzyValue?: string }} 查询参数 */
       query: {},
 
       /** @type {boolean} 加载状态 */
@@ -75,612 +38,292 @@ export default {
         total: 0
       },
 
-      /** @type {Array<InvoiceItem>} 选中的数据列表 */
-      selectionList: [],
-
-      /** @type {Array<InvoiceItem>} 表格数据 */
+      /** @type {ARBillHeadStatusInquiryRecord[]} 表格数据 */
       data: [],
 
+      /** @type {ARBillHeadStatusInquiryRecord[]} 查询原始数据(用于前端分页) */
+      rawData: [],
+
+      /** @type {boolean} 是否后端分页 */
+      serverPaging: false,
+
       /** @type {Object} 表格配置选项 */
       option: {
         height: 'auto',
         calcHeight: 30,
-        dialogWidth: 1000,
-        labelWidth: 120,
         tip: false,
         searchShow: true,
         searchMenuSpan: 6,
         border: true,
         index: true,
-        selection: true,
-        viewBtn: true,
-        editBtn: true,
-        addBtn: true,
-        delBtn: true,
-        dialogClickModal: false,
+        menu: false,
+        viewBtn: false,
+        editBtn: false,
+        delBtn: false,
+        addBtn: false,
         column: [
           {
-            label: '发票号码',
-            prop: 'invoiceNo',
+            label: '单据编号',
+            prop: 'docNo',
             search: true,
-            rules: [
-              {
-                required: true,
-                message: '请输入发票号码',
-                trigger: 'blur'
-              }
-            ]
+            width: 150
           },
           {
-            label: '发票代码',
-            prop: 'invoiceCode',
+            label: '销售订单编号',
+            prop: 'soDocNo',
             search: true,
-            rules: [
-              {
-                required: true,
-                message: '请输入发票代码',
-                trigger: 'blur'
-              }
-            ]
+            width: 150
           },
           {
-            label: '订单ID',
-            prop: 'orderId',
+            label: '销售订单行号',
+            prop: 'soDocLineNo',
             search: true,
-            type: 'number',
-            rules: [
-              {
-                required: true,
-                message: '请输入订单ID',
-                trigger: 'blur'
-              },
-              {
-                type: 'number',
-                message: '请输入数字',
-                trigger: 'blur'
-              },
-              {
-                min: 0,
-                message: '请输入大于等于0的数字',
-                trigger: 'blur'
-              }
-            ]
+            width: 120
           },
           {
-            label: '订单编码',
-            prop: 'orderCode',
+            label: '业务日期',
+            prop: 'businessDate',
+            type: 'datetime',
+            format: 'YYYY-MM-DD HH:mm:ss',
+            valueFormat: 'YYYY-MM-DD HH:mm:ss',
             search: true,
-            addDisplay: false,
-            editDisplay: false
+            searchRange: true,
+            width: 170
           },
           {
-            label: '客户名称',
-            prop: 'customerName',
+            label: '组织名称',
+            prop: 'orgName',
             search: true,
-            rules: [
-              {
-                required: true,
-                message: '请输入客户名称',
-                trigger: 'blur'
-              },
-              {
-                min: 2,
-                max: 50,
-                message: '客户名称长度必须在2到50个字符之间',
-                trigger: 'blur'
-              }
-            ]
+            width: 120
           },
           {
             label: '客户编码',
             prop: 'customerCode',
             search: true,
-            rules: [
-              {
-                required: true,
-                message: '请输入客户编码',
-                trigger: 'blur'
-              },
-              {
-                min: 2,
-                max: 50,
-                message: '客户编码长度必须在2到50个字符之间',
-                trigger: 'blur'
-              },
-              {
-                pattern: /^[a-zA-Z0-9_-]+$/,
-                message: '客户编码只能包含字母、数字、下划线和中横线',
-                trigger: 'blur'
-              }
-            ]
+            width: 140
           },
           {
-            label: '税号',
-            prop: 'taxNo',
-            rules: [
-              {
-                required: true,
-                message: '请输入税号',
-                trigger: 'blur'
-              }
-            ]
+            label: '客户名称',
+            prop: 'customerName',
+            search: true,
+            width: 160
           },
           {
-            label: '发票抬头',
-            prop: 'invoiceTitle',
-            rules: [
-              {
-                required: true,
-                message: '请输入发票抬头',
-                trigger: 'blur'
-              }
-            ]
+            label: '客户站点名称',
+            prop: 'custSiteName',
+            search: true,
+            width: 160
           },
           {
-            label: '金额',
-            prop: 'amount',
-            slot: true,
-            type: 'number',
-            precision: 2,
-            rules: [
-              {
-                required: true,
-                message: '请输入金额',
-                trigger: 'blur'
-              },
-              {
-                type: 'number',
-                message: '请输入数字',
-                trigger: 'blur'
-              },
-              {
-                min: 0,
-                message: '请输入大于等于0的数字',
-                trigger: 'blur'
-              }
-            ]
+            label: '物料编码',
+            prop: 'itemCode',
+            search: true,
+            width: 140
           },
           {
-            label: '税额',
-            prop: 'taxAmount',
-            slot: true,
-            type: 'number',
-            precision: 2,
-            rules: [
-              {
-                required: true,
-                message: '请输入税额',
-                trigger: 'blur'
-              },
-              {
-                type: 'number',
-                message: '请输入数字',
-                trigger: 'blur'
-              },
-              {
-                min: 0,
-                message: '请输入大于等于0的数字',
-                trigger: 'blur'
-              }
-            ]
+            label: '物料名称',
+            prop: 'itemName',
+            search: true,
+            width: 160
           },
           {
-            label: '总金额',
-            prop: 'totalAmount',
-            slot: true,
-            type: 'number',
-            precision: 2,
-            rules: [
-              {
-                required: true,
-                message: '请输入总金额',
-                trigger: 'blur'
-              },
-              {
-                type: 'number',
-                message: '请输入数字',
-                trigger: 'blur'
-              },
-              {
-                min: 0,
-                message: '请输入大于等于0的数字',
-                trigger: 'blur'
-              }
-            ]
+            label: '规格',
+            prop: 'specs',
+            width: 160
           },
           {
-            label: '发票类型',
-            prop: 'invoiceType',
-            slot: true,
-            type: 'select',
-            search: true,
-            width: 120,
-            dicData: INVOICE_TYPE_OPTIONS,
-            rules: [
-              {
-                required: true,
-                message: '请选择发票类型',
-                trigger: 'change'
-              }
-            ]
+            label: '数量',
+            prop: 'qty',
+            width: 100
           },
           {
-            label: '开票状态',
-            prop: 'invoiceStatus',
-            slot: true,
-            search: true,
-            type: 'select',
-            dicData: INVOICE_STATUS_OPTIONS,
-            rules: [
-              {
-                required: true,
-                message: '请选择开票状态',
-                trigger: 'change'
-              }
-            ]
+            label: '含税单价',
+            prop: 'taxPrice',
+            width: 110
           },
           {
-            label: '开票日期',
-            prop: 'invoiceDate',
-            type: 'date',
-            format: 'yyyy-MM-dd',
-            valueFormat: 'yyyy-MM-dd',
-            rules: [
-              {
-                required: true,
-                message: '请选择开票日期',
-                trigger: 'change'
-              }
-            ]
+            label: '含税金额',
+            prop: 'puAmount',
+            width: 110
           },
           {
-            label: '创建时间',
-            prop: 'createTime',
-            type: 'datetime',
-            format: 'yyyy-MM-dd HH:mm:ss',
-            valueFormat: 'yyyy-MM-dd HH:mm:ss',
-            addDisplay: false,
-            editDisplay: false,
-            width: 160
+            label: '应收不含税金额',
+            prop: 'arocMoneyNonTax',
+            width: 130
           },
           {
-            label: '更新时间',
-            prop: 'updateTime',
-            type: 'datetime',
-            format: 'yyyy-MM-dd HH:mm:ss',
-            valueFormat: 'yyyy-MM-dd HH:mm:ss',
-            addDisplay: false,
-            editDisplay: false,
-            width: 160
+            label: '应收税额',
+            prop: 'arocMoneyGoodsTax',
+            width: 110
+          },
+          {
+            label: '应收含税金额',
+            prop: 'arocMoneyTotalMoney',
+            width: 130
+          },
+          {
+            label: '开票不含税金额',
+            prop: 'arfcMoneyNonTax',
+            width: 130
+          },
+          {
+            label: '开票税额',
+            prop: 'arfcMoneyGoodsTax',
+            width: 110
+          },
+          {
+            label: '开票含税金额',
+            prop: 'arfcMoneyTotalMoney',
+            width: 130
+          },
+          {
+            label: '单据状态',
+            prop: 'docStatus',
+            search: true,
+            width: 110
+          },
+          {
+            label: '模糊关键字',
+            prop: 'fuzzyValue',
+            search: true,
+            hide: true
           }
         ]
       }
     }
   },
 
-  computed: {
-    ...mapGetters(['permission']),
-
-    /**
-     * 权限列表
-     * @returns {Object} 权限配置对象
-     */
-    permissionList() {
-      return {
-        addBtn: this.vaildData(this.permission.order_invoice_add, false),
-        viewBtn: this.vaildData(this.permission.order_invoice_view, false),
-        editBtn: this.vaildData(this.permission.order_invoice_edit, false),
-        delBtn: this.vaildData(this.permission.order_invoice_delete, false)
-      }
-    },
-
-    /**
-     * 是否有选中的数据
-     * @returns {boolean} 是否有选中数据
-     */
-    hasSelection() {
-      return this.selectionList.length > 0
-    }
+  created() {
+    this.onLoad()
   },
 
   methods: {
     /**
-     * 获取发票类型标签文本
-     * @param {string} type - 发票类型
-     * @returns {string} 标签文本
+     * 前端分页应用
      */
-    getInvoiceTypeLabel,
-
-    /**
-     * 获取发票类型标签样式
-     * @param {string} type - 发票类型
-     * @returns {string} 标签样式类型
-     */
-    getInvoiceTypeTagType,
-
-    /**
-     * 获取发票状态标签文本
-     * @param {number} status - 发票状态
-     * @returns {string} 标签文本
-     */
-    getInvoiceStatusLabel,
-
-    /**
-     * 获取发票状态标签样式
-     * @param {number} status - 发票状态
-     * @returns {string} 标签样式类型
-     */
-    getInvoiceStatusTagType,
-
-    /**
-     * 格式化金额显示
-     * @param {string|number} amount - 金额
-     * @returns {string} 格式化后的金额
-     */
-    formatAmount(amount) {
-      if (!amount) return '0.00'
-      return parseFloat(amount).toLocaleString('zh-CN', {
-        minimumFractionDigits: 2,
-        maximumFractionDigits: 2
-      })
-    },
-
-    /**
-     * 批量删除处理
-     * @returns {Promise<void>}
-     */
-    async handleDelete() {
-      if (!this.hasSelection) {
-        this.$message.warning('请选择要删除的数据')
-        return
-      }
-
-      try {
-        await this.$confirm('确定删除选中的发票信息吗?', '提示', {
-          confirmButtonText: '确定',
-          cancelButtonText: '取消',
-          type: 'warning'
-        })
-
-        const ids = this.selectionList.map(item => item.id).join(',')
-        const res = await remove(ids)
-
-        if (res.data.success) {
-          this.$message.success('删除成功')
-          await this.onLoad(this.page)
-        } else {
-          this.$message.error(res.data.msg || '删除失败')
-        }
-      } catch (error) {
-        if (error !== 'cancel') {
-          console.error('删除发票失败:', error)
-          this.$message.error('删除失败,请重试')
-        }
-      }
-    },
-
-    /**
-     * 批量更新发票状态
-     * @param {number} status - 新状态
-     * @returns {Promise<void>}
-     */
-    async handleBatchUpdateStatus(status) {
-      if (!this.hasSelection) {
-        this.$message.warning('请选择要更新状态的数据')
-        return
-      }
-
-      const statusText = getInvoiceStatusLabel(status)
-
-      try {
-        await this.$confirm(`确定将选中的发票状态更新为"${statusText}"吗?`, '提示', {
-          confirmButtonText: '确定',
-          cancelButtonText: '取消',
-          type: 'warning'
-        })
-
-        const ids = this.selectionList.map(item => item.id)
-        const res = await updateStatus(ids, status)
-
-        if (res.data.success) {
-          this.$message.success('状态更新成功')
-          await this.onLoad(this.page)
-        } else {
-          this.$message.error(res.data.msg || '状态更新失败')
-        }
-      } catch (error) {
-        if (error !== 'cancel') {
-          console.error('更新发票状态失败:', error)
-          this.$message.error('状态更新失败,请重试')
-        }
-      }
-    },
-
-    /**
-     * 行删除处理
-     * @param {InvoiceItem} row - 要删除的行数据
-     * @param {number} index - 行索引
-     * @returns {Promise<void>}
-     */
-    async rowDel(row, index) {
-      try {
-        const res = await remove(row.id)
-        if (res.data.success) {
-          this.$message.success('删除成功')
-          this.onLoad(this.page)
-        } else {
-          this.$message.error(res.data.msg || '删除失败')
-        }
-      } catch (error) {
-        console.error('删除发票失败:', error)
-        this.$message.error('删除失败,请重试')
-      }
-    },
-
-    /**
-     * 行更新处理
-     * @param {InvoiceForm} row - 更新的行数据
-     * @param {number} index - 行索引
-     * @param {Function} done - 完成回调
-     * @param {Function} loading - 加载状态回调
-     * @returns {Promise<void>}
-     */
-    async rowUpdate(row, index, done, loading) {
-      try {
-        loading()
-        const res = await update(row)
-
-        if (res.data.success) {
-          this.$message.success('修改成功')
-          done()
-          this.onLoad(this.page)
-        } else {
-          this.$message.error(res.data.msg || '修改失败')
-          loading()
-        }
-      } catch (error) {
-        console.error('修改发票失败:', error)
-        this.$message.error('修改失败,请重试')
-        loading()
-      }
-    },
-
-    /**
-     * 行保存处理
-     * @param {InvoiceForm} row - 保存的行数据
-     * @param {Function} done - 完成回调
-     * @param {Function} loading - 加载状态回调
-     * @returns {Promise<void>}
-     */
-    async rowSave(row, done, loading) {
-      try {
-        loading()
-        const res = await add(row)
-
-        if (res.data.success) {
-          this.$message.success('添加成功')
-          done()
-          this.onLoad(this.page)
-        } else {
-          this.$message.error(res.data.msg || '添加失败')
-          loading()
-        }
-      } catch (error) {
-        console.error('添加发票失败:', error)
-        this.$message.error('添加失败,请重试')
-        loading()
-      }
+    applyPagination() {
+      const start = (this.page.currentPage - 1) * this.page.pageSize
+      const end = start + this.page.pageSize
+      this.page.total = this.rawData.length
+      this.data = this.rawData.slice(start, end)
     },
 
     /**
      * 搜索变化处理
-     * @param {InvoiceQueryParams} params - 搜索参数
+     * @param {any} params - 搜索参数
      * @param {Function} done - 完成回调
-     * @returns {void}
      */
     searchChange(params, done) {
       this.query = params
-      this.onLoad(this.page, params)
+      this.page.currentPage = 1
+      this.onLoad()
       done()
     },
 
     /**
      * 搜索重置处理
-     * @returns {void}
      */
     searchReset() {
       this.query = {}
-      this.onLoad(this.page)
-    },
-
-    /**
-     * 选择变化处理
-     * @param {Array<InvoiceItem>} list - 选中的数据列表
-     * @returns {void}
-     */
-    selectionChange(list) {
-      this.selectionList = list
+      this.page.currentPage = 1
+      this.onLoad()
     },
 
     /**
      * 当前页变化处理
      * @param {number} currentPage - 当前页码
-     * @returns {void}
      */
     currentChange(currentPage) {
       this.page.currentPage = currentPage
+      if (this.serverPaging) {
+        this.onLoad()
+        return
+      }
+      this.applyPagination()
     },
 
     /**
      * 页大小变化处理
      * @param {number} pageSize - 页大小
-     * @returns {void}
      */
     sizeChange(pageSize) {
       this.page.pageSize = pageSize
-    },
-
-    /**
-     * 刷新变化处理
-     * @returns {void}
-     */
-    refreshChange() {
-      this.onLoad(this.page, this.query)
-    },
-
-    /**
-     * 打开前处理
-     * @param {Function} done - 完成回调
-     * @param {string} type - 操作类型
-     * @returns {void}
-     */
-    beforeOpen(done, type) {
-      done()
+      this.page.currentPage = 1
+      if (this.serverPaging) {
+        this.onLoad()
+        return
+      }
+      this.applyPagination()
     },
 
     /**
      * 加载数据
-     * @param {PageInfo} page - 分页信息
-     * @param {InvoiceQueryParams} params - 查询参数
      * @returns {Promise<void>}
      */
-    async onLoad(page, params = {}) {
+    async onLoad() {
       this.loading = true
 
       try {
-        const res = await getList(page.currentPage || 1, page.pageSize || 10, {
-          ...this.query,
-          ...params
-        })
+        const { businessDate, fuzzyValue, ...rest } = this.query || {}
+        /** @type {ARBillHeadStatusInquiryParams} */
+        const params = { ...rest }
 
-        if (res.data.success) {
-          const { records, total } = res.data.data
-          this.data = records || []
-          this.page.total = total || 0
-        } else {
-          this.$message.error(res.data.msg || '获取数据失败')
-          this.data = []
-          this.page.total = 0
+        params.pageNum = this.page.currentPage
+        params.pageSize = this.page.pageSize
+
+        if (params.docStatus !== undefined && params.docStatus !== null && params.docStatus !== '') {
+          params.docStatusList = String(params.docStatus)
+          delete params.docStatus
         }
+
+        if (Array.isArray(businessDate) && businessDate.length === 2) {
+          params.businessDateStart = businessDate[0]
+          params.businessDateEnd = businessDate[1]
+        }
+
+        if (fuzzyValue) {
+          params.fuzzyValue = fuzzyValue
+          params.fuzzyFields = params.fuzzyFields || 'Customer_Name,Item_Name'
+        }
+
+        const res = await getARBillHeadStatusInquiry(params)
+        const respData = res && res.data ? res.data.data : null
+
+        const isPaged =
+          respData &&
+          typeof respData === 'object' &&
+          Array.isArray(respData.records) &&
+          typeof respData.total === 'number'
+
+        this.serverPaging = Boolean(isPaged)
+
+        if (isPaged) {
+          const records = respData.records
+          this.data = records.slice().sort((a, b) => {
+            const at = a && a.businessDate ? new Date(a.businessDate).getTime() : 0
+            const bt = b && b.businessDate ? new Date(b.businessDate).getTime() : 0
+            return bt - at
+          })
+          this.page.total = respData.total
+          this.rawData = []
+          return
+        }
+
+        const list = Array.isArray(respData) ? respData : []
+        this.rawData = list.slice().sort((a, b) => {
+          const at = a && a.businessDate ? new Date(a.businessDate).getTime() : 0
+          const bt = b && b.businessDate ? new Date(b.businessDate).getTime() : 0
+          return bt - at
+        })
+        this.applyPagination()
       } catch (error) {
-        console.error('获取发票列表失败:', error)
-        this.$message.error('获取数据失败,请重试')
-        this.data = []
-        this.page.total = 0
+        console.error('加载发票及开票信息失败:', error)
+        this.serverPaging = false
+        this.rawData = []
+        this.applyPagination()
       } finally {
         this.loading = false
       }
     }
-  },
-
-  /**
-   * 组件挂载后初始化
-   * @returns {void}
-   */
-  mounted() {
-    // 默认不加载数据,需要通过搜索条件触发
-    this.loading = false
-    this.data = []
-    this.page.total = 0
   }
 }

+ 57 - 0
src/utils/content-disposition.js

@@ -0,0 +1,57 @@
+/**
+ * 从 Content-Disposition 响应头中解析文件名
+ * 兼容:
+ * - RFC 5987: filename*=UTF-8''%E4%B8%AD%E6%96%87.xlsx
+ * - 常规写法: filename="%E4%B8%AD%E6%96%87.xlsx" 或 filename=%E4%B8%AD%E6%96%87.xlsx
+ * @param {string} contentDisposition
+ * @param {string} [fallbackFilename]
+ * @returns {string}
+ */
+export const getFilenameFromContentDisposition = (contentDisposition, fallbackFilename = '导出文件.xlsx') => {
+  if (!contentDisposition || typeof contentDisposition !== 'string') return fallbackFilename
+
+  const cd = contentDisposition
+
+  const decodeMaybe = (value) => {
+    const raw = String(value || '').trim()
+    if (!raw) return ''
+
+    // 一些服务端会把空格编码为 +(更像 querystring),做一次兼容
+    const plusFixed = raw.includes('+') ? raw.replace(/\+/g, '%20') : raw
+
+    try {
+      // 部分服务端可能会重复 encode(例如 %25E7%2589...),这里最多解码 3 次
+      let decoded = plusFixed
+      for (let i = 0; i < 3; i += 1) {
+        const next = decodeURIComponent(decoded)
+        if (next === decoded) break
+        decoded = next
+      }
+      return decoded
+    } catch (e) {
+      // 可能包含非法 % 序列,回退原值
+      return raw
+    }
+  }
+
+  // RFC 5987: filename*=UTF-8''xxxx
+  const starMatch = /filename\*\s*=\s*([^']*)''([^;\n]+)/i.exec(cd)
+  if (starMatch && starMatch[2]) {
+    const decoded = decodeMaybe(starMatch[2])
+    if (decoded) return sanitizeFilename(decoded)
+  }
+
+  // 常规:filename="..." 或 filename=...
+  const normalMatch = /filename\s*=\s*"?([^";\n]+)"?/i.exec(cd)
+  if (normalMatch && normalMatch[1]) {
+    const decoded = decodeMaybe(normalMatch[1])
+    if (decoded) return sanitizeFilename(decoded)
+  }
+
+  return fallbackFilename
+}
+
+const sanitizeFilename = (filename) => {
+  // 防止把路径分隔符写进 download 属性导致奇怪行为
+  return String(filename).replace(/[\\/]/g, '_')
+}

+ 44 - 12
src/views/claim/claimMixin.js

@@ -5,8 +5,8 @@
  */
 
 /**
- * @typedef {import('@/api/claim/index').ClaimItem} ClaimItem - 理赔申请数据项
- * @typedef {import('@/api/claim/index').ClaimQueryParams} ClaimQueryParams - 理赔查询参数
+ * @typedef {import('@/api/types/claim-search').ClaimMainRecord} ClaimItem - 理赔申请数据项(综合查询主表)
+ * @typedef {import('@/api/types/claim-search').ClaimSearchComprehensiveParams} ClaimQueryParams - 理赔查询参数(以综合查询接口为准)
  * @typedef {import('@/api/claim/index').ClaimAuditItem} ClaimAuditItem - 审核记录数据项
  * @typedef {import('@/api/claim/index').ClaimAttachmentItem} ClaimAttachmentItem - 附件信息
  */
@@ -63,7 +63,8 @@
  * @property {Array<Object>} column - 列配置
  */
 
-import { getClaimList, getClaimDetail, getClaimAttachments, getClaimAuditList, addClaimAudit, updateClaimAudit, removeClaimAudit } from '@/api/claim/index'
+import { getClaimSearchComprehensive } from '@/api/claimSearch'
+import { getClaimDetail, getClaimAttachments, getClaimAuditList, addClaimAudit, updateClaimAudit, removeClaimAudit } from '@/api/claim/index'
 import { formatFileSize } from '@/util/util'
 import { mapGetters } from 'vuex'
 import {
@@ -208,12 +209,12 @@ export default {
           {
             label: '来源方名称',
             prop: 'sourceName',
-            search: true
+            search: false
           },
           {
             label: '来源编码',
             prop: 'sourceCode',
-            search: true
+            search: false
           },
           {
             label: '联系人',
@@ -232,11 +233,17 @@ export default {
           },
           {
             label: '规格型号',
-            prop: 'tyreSpecs'
+            prop: 'tyreSpecs',
+            search: true
           },
           {
             label: '购买日期',
             prop: 'purchaseDate',
+            type: 'datetime',
+            format: 'yyyy-MM-dd HH:mm:ss',
+            valueFormat: 'yyyy-MM-dd HH:mm:ss',
+            search: true,
+            searchRange: true,
             formatter: function(row, value) {
               if (!value || typeof value !== 'string') return value || ''
               return value.substring(0, 10)
@@ -252,12 +259,13 @@ export default {
           },
           {
             label: '行驶里程(km)',
-            prop: 'runMileage'
+            prop: 'runMileage',
+            type: 'number'
           },
           {
             label: '车牌号',
             prop: 'vehicleNumber',
-            search: true
+            search: false
           },
           {
             label: '轮胎数量',
@@ -328,10 +336,29 @@ export default {
     async onLoad(page, params = {}) {
       try {
         this.loading = true
-        const res = await getClaimList(page.currentPage, page.pageSize, Object.assign(params, this.query))
-        const data = res.data.data
-        this.page.total = data.total
-        this.data = data.records
+        const mergedQuery = Object.assign({}, this.query, params)
+        const { purchaseDate, ...rest } = mergedQuery || {}
+
+        /** @type {ClaimQueryParams & Record<string, any>} */
+        const apiParams = { ...rest }
+
+        // 购买日期区间:页面用 purchaseDate,接口用 purchaseDateStartStr/purchaseDateEndStr
+        if (Array.isArray(purchaseDate) && purchaseDate.length === 2) {
+          apiParams.purchaseDateStartStr = purchaseDate[0]
+          apiParams.purchaseDateEndStr = purchaseDate[1]
+        }
+
+        const res = await getClaimSearchComprehensive(page.pageSize, page.currentPage, apiParams)
+        const pageData = res?.data?.data
+        const records = (pageData && Array.isArray(pageData.records)) ? pageData.records : []
+
+        this.page.total = pageData?.total || 0
+        // 综合查询返回 records[].claimMain,列表展示按主表字段铺平
+        this.data = records.map((r) => ({
+          ...(r && r.claimMain ? r.claimMain : {}),
+          attachments: (r && Array.isArray(r.attachments)) ? r.attachments : [],
+          auditRecords: (r && Array.isArray(r.auditRecords)) ? r.auditRecords : []
+        }))
       } catch (error) {
         console.error('获取理赔列表失败:', error)
         this.$message.error('获取理赔列表失败')
@@ -349,6 +376,7 @@ export default {
      */
     searchChange(params, done) {
       this.query = params
+      this.page.currentPage = 1
       this.onLoad(this.page, params)
       done()
     },
@@ -360,6 +388,7 @@ export default {
      */
     searchReset() {
       this.query = {}
+      this.page.currentPage = 1
       this.onLoad(this.page)
     },
 
@@ -381,6 +410,7 @@ export default {
      */
     currentChange(currentPage) {
       this.page.currentPage = currentPage
+      this.onLoad(this.page, this.query)
     },
 
     /**
@@ -391,6 +421,8 @@ export default {
      */
     sizeChange(pageSize) {
       this.page.pageSize = pageSize
+      this.page.currentPage = 1
+      this.onLoad(this.page, this.query)
     },
 
     /**

+ 1 - 4
src/views/claim/index.vue

@@ -12,10 +12,7 @@
                @current-change="currentChange"
                @size-change="sizeChange"
                @refresh-change="refreshChange"
-               @on-load="onLoad"
-               @row-update="rowUpdate"
-               @row-save="rowSave"
-               @row-del="rowDel">
+               @on-load="onLoad">
       <template slot-scope="scope" slot="menu">
         <el-button type="text"
                    size="small"

+ 84 - 56
src/views/claim/types.d.ts

@@ -6,46 +6,69 @@
  * 理赔申请查询参数类型定义
  */
 export interface ClaimQueryParams {
-  claimNo?: string; // 理赔编号
-  claimSourceType?: number; // 理赔来源类型 1-经销商 2-门店
-  sourceCode?: string; // 来源编码
-  sourceName?: string; // 来源名称
-  consumerName?: string; // 消费者姓名
-  consumerPhone?: string; // 消费者电话
-  tyreNo?: string; // 轮胎编号
-  auditStatus?: number; // 审核状态 0-待审核 1-审核通过 2-审核拒绝
-  startDate?: string; // 开始日期
-  endDate?: string; // 结束日期
+  /** 理赔单号(精准匹配) */
+  claimNo?: string;
+  /** 来源类型:1 - 经销商、2 - 门店、3 - 终端消费者 */
+  claimSourceType?: number;
+  /** 审核状态:0 - 待审核、1 - 审核中、2 - 已通过、3 - 已拒绝 */
+  auditStatus?: number;
+  /** 是否已提交:0 - 未提交、1 - 已提交 */
+  isSubmitTime?: number;
+  /** 消费者姓名(模糊匹配) */
+  consumerName?: string;
+  /** 消费者电话(模糊匹配) */
+  consumerPhone?: string;
+  /** 胎号 / 轮胎宝编号(模糊匹配) */
+  tyreNo?: string;
+  /** 规格型号(模糊匹配) */
+  tyreSpecs?: string;
+  /**
+   * 页面用购买日期区间(avue searchRange 回传)
+   * 接口参数会被转换为 purchaseDateStartStr/purchaseDateEndStr
+   */
+  purchaseDate?: string | [string, string];
+  /** 购买日期开始(格式:yyyy-MM-dd HH:mm:ss) */
+  purchaseDateStartStr?: string;
+  /** 购买日期结束(格式:yyyy-MM-dd HH:mm:ss) */
+  purchaseDateEndStr?: string;
+  /** 索赔金额最小值 */
+  claimAmountMin?: number;
+  /** 索赔金额最大值 */
+  claimAmountMax?: number;
+  /** 行驶里程最小值 (km) */
+  runMileageMin?: number;
+  /** 行驶里程最大值 (km) */
+  runMileageMax?: number;
 }
 
 /**
  * 理赔申请数据项类型定义
  */
 export interface ClaimItem {
-  id: string; // 理赔ID
-  claimNo: string; // 理赔编号
-  claimSourceType: number; // 理赔来源类型
-  sourceId: number; // 来源ID
-  sourceCode: string; // 来源编码
-  sourceName: string; // 来源名称
-  consumerName: string; // 消费者姓名
-  consumerPhone: string; // 消费者电话
-  tyreNo: string; // 轮胎编号
-  tyreSpecs: string; // 轮胎规格
-  purchaseDate: string; // 购买日期
-  mountDate: string; // 安装日期
-  runMileage: number; // 行驶里程
-  vehicleNumber?: string; // 车牌号
-  tireQuantity?: number; // 轮胎数量
-  brandItem?: string; // 花纹
-  brandName?: string; // 品牌
-  claimReason: string; // 理赔原因
-  claimAmount: string; // 理赔金额
-  auditStatus: number; // 审核状态
-  isSubmitTime?: number; // 是否已提交 0-未提交 1-已提交
-  submitTime: string; // 提交时间
-  createTime: string; // 创建时间
-  updateTime: string; // 更新时间
+  id: number | string;
+  claimNo: string;
+  claimSourceType: number;
+  sourceId?: number | string;
+  sourceCode?: string;
+  sourceName?: string;
+  consumerName?: string;
+  consumerPhone?: string;
+  tyreNo?: string;
+  tyreSpecs?: string;
+  purchaseDate?: string;
+  mountDate?: string;
+  runMileage?: number;
+  vehicleNumber?: string;
+  tireQuantity?: number;
+  brandItem?: string;
+  brandName?: string;
+  claimReason?: string;
+  claimAmount?: number;
+  auditStatus?: number;
+  isSubmitTime?: number;
+  submitTime?: string;
+  createTime?: string;
+  updateTime?: string;
 }
 
 /**
@@ -133,42 +156,47 @@ export interface ClaimComponentData {
 export interface ClaimQueryParams {
   claimNo?: string;
   claimSourceType?: number;
-  sourceCode?: string;
-  sourceName?: string;
+  auditStatus?: number;
+  isSubmitTime?: number;
   consumerName?: string;
   consumerPhone?: string;
   tyreNo?: string;
-  auditStatus?: number;
-  startDate?: string;
-  endDate?: string;
+  tyreSpecs?: string;
+  purchaseDate?: string | [string, string];
+  purchaseDateStartStr?: string;
+  purchaseDateEndStr?: string;
+  claimAmountMin?: number;
+  claimAmountMax?: number;
+  runMileageMin?: number;
+  runMileageMax?: number;
 }
 
 // 列表项
 export interface ClaimItem {
-  id: string;
+  id: number | string;
   claimNo: string;
   claimSourceType: number;
-  sourceId: number;
-  sourceCode: string;
-  sourceName: string;
-  consumerName: string;
-  consumerPhone: string;
-  tyreNo: string;
-  tyreSpecs: string;
-  purchaseDate: string;
-  mountDate: string;
-  runMileage: number;
+  sourceId?: number | string;
+  sourceCode?: string;
+  sourceName?: string;
+  consumerName?: string;
+  consumerPhone?: string;
+  tyreNo?: string;
+  tyreSpecs?: string;
+  purchaseDate?: string;
+  mountDate?: string;
+  runMileage?: number;
   vehicleNumber?: string;
   tireQuantity?: number;
   brandItem?: string;
   brandName?: string;
-  claimReason: string;
-  claimAmount: string;
-  auditStatus: number;
+  claimReason?: string;
+  claimAmount?: number;
+  auditStatus?: number;
   isSubmitTime?: number;
-  submitTime: string;
-  createTime: string;
-  updateTime: string;
+  submitTime?: string;
+  createTime?: string;
+  updateTime?: string;
 }
 
 // 审核记录

+ 6 - 17
src/views/forecast-summary/summaryIndex.js

@@ -22,6 +22,7 @@ import {
 } from '@/constants/forecast'
 import { mapGetters } from 'vuex'
 import { safeBigInt } from '@/util/util'
+import { getFilenameFromContentDisposition } from '@/utils/content-disposition'
 
 /**
  * 经销商销售预测汇总页面业务逻辑混入
@@ -375,15 +376,9 @@ import { safeBigInt } from '@/util/util'
 
          const res = await exportSalesForecastByMonth(year, month)
 
-         // 处理文件名
-         const disposition = res?.headers?.['content-disposition'] || res?.headers?.['Content-Disposition']
-         let filename = `销售预测汇总_${year}-${String(month).padStart(2, '0')}.xlsx`
-         if (disposition) {
-           const utf8Match = /filename\*=UTF-8''([^;\n]+)/i.exec(disposition)
-           const plainMatch = /filename="?([^;\n"]+)"?/i.exec(disposition)
-           const raw = utf8Match ? decodeURIComponent(utf8Match[1]) : (plainMatch ? plainMatch[1] : '')
-           if (raw) filename = raw
-         }
+         const disposition = res?.headers?.['content-disposition'] || res?.headers?.['Content-Disposition'] || ''
+         const fallbackFilename = `销售预测汇总_${year}-${String(month).padStart(2, '0')}.xlsx`
+         const filename = getFilenameFromContentDisposition(disposition, fallbackFilename)
 
          const blob = new Blob([res.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
          const url = window.URL.createObjectURL(blob)
@@ -598,16 +593,10 @@ import { safeBigInt } from '@/util/util'
          const res = await exportSalesForecastByMainId(mainId)
 
          // 处理文件名:优先从响应头解析,其次使用“客户名称_yyyy-MM.xlsx”
-         const disposition = res?.headers?.['content-disposition'] || res?.headers?.['Content-Disposition']
+         const disposition = res?.headers?.['content-disposition'] || res?.headers?.['Content-Disposition'] || ''
          const ym = this.formatYearMonth(Number(row.year), Number(row.month))
          const defaultName = `${row.customerName ? row.customerName + '_' : ''}${ym || ''}销售预测汇总.xlsx`
-         let filename = defaultName
-         if (disposition) {
-           const utf8Match = /filename\*=UTF-8''([^;\n]+)/i.exec(disposition)
-           const plainMatch = /filename="?([^;\n"]+)"?/i.exec(disposition)
-           const raw = utf8Match ? decodeURIComponent(utf8Match[1]) : (plainMatch ? plainMatch[1] : '')
-           if (raw) filename = raw
-         }
+         const filename = getFilenameFromContentDisposition(disposition, defaultName)
 
          const blob = new Blob([res.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
          const url = window.URL.createObjectURL(blob)

+ 9 - 70
src/views/order/invoice/index.vue

@@ -6,75 +6,22 @@
       ref="crud"
       v-model="form"
       :page.sync="page"
-      :permission="permissionList"
-      :before-open="beforeOpen"
       :table-loading="loading"
-      @row-del="rowDel"
-      @row-update="rowUpdate"
-      @row-save="rowSave"
       @search-change="searchChange"
       @search-reset="searchReset"
-      @selection-change="selectionChange"
       @current-change="currentChange"
       @size-change="sizeChange"
-      @refresh-change="refreshChange"
-      @on-load="onLoad"
     >
       <template slot="menuLeft">
         <el-button
-          type="danger"
+          type="primary"
           size="small"
           plain
-          icon="el-icon-delete"
-          v-if="permission.order_invoice_delete"
-          @click="handleDelete"
+          icon="el-icon-refresh"
+          @click="onLoad"
         >
-          删除
+          刷新
         </el-button>
-        <el-button
-          type="success"
-          size="small"
-          plain
-          icon="el-icon-check"
-          v-if="permission.order_invoice_update"
-          @click="handleBatchUpdateStatus(1)"
-        >
-          批量标记已开票
-        </el-button>
-        <el-button
-          type="warning"
-          size="small"
-          plain
-          icon="el-icon-close"
-          v-if="permission.order_invoice_update"
-          @click="handleBatchUpdateStatus(0)"
-        >
-          批量标记未开票
-        </el-button>
-      </template>
-
-      <template slot-scope="{row}" slot="invoiceStatus">
-        <el-tag :type="getInvoiceStatusTagType(row.invoiceStatus)">
-          {{ getInvoiceStatusLabel(row.invoiceStatus) }}
-        </el-tag>
-      </template>
-
-      <template slot-scope="{row}" slot="invoiceType">
-        <el-tag :type="getInvoiceTypeTagType(row.invoiceType)">
-          {{ getInvoiceTypeLabel(row.invoiceType) }}
-        </el-tag>
-      </template>
-
-      <template slot-scope="{row}" slot="amount">
-        <span class="amount-text">¥{{ formatAmount(row.amount) }}</span>
-      </template>
-
-      <template slot-scope="{row}" slot="taxAmount">
-        <span class="amount-text">¥{{ formatAmount(row.taxAmount) }}</span>
-      </template>
-
-      <template slot-scope="{row}" slot="totalAmount">
-        <span class="amount-text total-amount">¥{{ formatAmount(row.totalAmount) }}</span>
       </template>
     </avue-crud>
   </basic-container>
@@ -94,19 +41,11 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.amount-text {
-  // font-family: 'Courier New', monospace;
-  font-weight: 500;
-  
-  &.total-amount {
-    color: #e6a23c;
-    font-weight: 600;
-  }
+::v-deep .el-dialog__body {
+  padding: 10px 20px;
 }
 
-::v-deep .el-table {
-  .amount-text {
-    text-align: right;
-  }
+::v-deep .avue-crud__search .el-form-item {
+  margin-bottom: 10px;
 }
-</style>
+</style>