From 20cefe115d39a2000f3450e5e371ea3e6f6fbe2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A0=82=E7=B3=96?= <2178503051@qq.com> Date: Mon, 11 May 2026 10:38:29 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E9=87=91=E9=A2=9D=E6=98=BE=E7=A4=BA):=20?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E9=87=91=E9=A2=9D=E5=8D=95=E4=BD=8D=E4=B8=BA?= =?UTF-8?q?=E4=B8=87=E5=85=83=E5=B9=B6=E4=BC=98=E5=8C=96=E4=BA=A7=E5=93=81?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor(产品内容): 提取产品内容处理逻辑到独立工具类 style(合同表单): 调整技术附件和商务附件顺序 --- klp-ui/src/utils/productContent.js | 315 ++++++++++++++++++ .../src/views/crm/components/OrderDetail.vue | 117 +------ .../src/views/crm/components/ReceiveTable.vue | 6 +- .../crm/contract/components/ContractList.vue | 79 +---- .../contract/components/ProductContent.vue | 275 ++++++--------- klp-ui/src/views/crm/contract/index.vue | 6 +- .../views/crm/report/CustomerLevelChart.vue | 2 +- .../crm/report/SalesReportOrderDetail.vue | 12 +- .../crm/report/SalesReportSummaryCard.vue | 8 +- 9 files changed, 458 insertions(+), 362 deletions(-) create mode 100644 klp-ui/src/utils/productContent.js diff --git a/klp-ui/src/utils/productContent.js b/klp-ui/src/utils/productContent.js new file mode 100644 index 00000000..16f3ab3f --- /dev/null +++ b/klp-ui/src/utils/productContent.js @@ -0,0 +1,315 @@ +/** + * productContent 工具类 + * 用于处理合同产品内容的各种操作 + */ + +// 默认产品内容结构 +const DEFAULT_PRODUCT_CONTENT = { + products: [], + productName: '', + remark: '', + totalQuantity: 0, + totalTaxTotal: 0, + totalAmountInWords: '零元整' +}; + +/** + * 解析 productContent JSON 字符串 + * @param {string} jsonStr - JSON 字符串 + * @returns {Object} 解析后的对象 + */ +export function parseProductContent(jsonStr) { + if (!jsonStr) { + return { ...DEFAULT_PRODUCT_CONTENT }; + } + + try { + const data = JSON.parse(jsonStr); + return { + ...DEFAULT_PRODUCT_CONTENT, + ...data, + products: data.products || [] + }; + } catch (error) { + console.error('解析 productContent 失败:', error); + return { ...DEFAULT_PRODUCT_CONTENT }; + } +} + +/** + * 将对象转换为 productContent JSON 字符串 + * @param {Object} data - 产品内容对象 + * @returns {string} JSON 字符串 + */ +export function stringifyProductContent(data) { + const content = { + ...DEFAULT_PRODUCT_CONTENT, + ...data + }; + return JSON.stringify(content, null, 2); +} + +/** + * 计算产品列表的总数量 + * @param {Array} products - 产品列表 + * @returns {number} 总数量 + */ +export function calculateTotalQuantity(products) { + if (!products || !Array.isArray(products)) { + return 0; + } + return products.reduce((total, item) => { + return total + (parseFloat(item.quantity) || 0); + }, 0); +} + +/** + * 计算产品列表的含税总额 + * @param {Array} products - 产品列表 + * @returns {number} 含税总额 + */ +export function calculateTotalTaxTotal(products) { + if (!products || !Array.isArray(products)) { + return 0; + } + return products.reduce((total, item) => { + return total + (parseFloat(item.taxTotal) || 0); + }, 0); +} + +/** + * 计算单个产品的含税总额 + * @param {Object} product - 产品对象 + * @returns {number} 含税总额 + */ +export function calculateProductTaxTotal(product) { + if (!product) return 0; + const quantity = parseFloat(product.quantity) || 0; + const taxPrice = parseFloat(product.taxPrice) || 0; + return quantity * taxPrice; +} + +/** + * 重新计算所有产品的含税总额和总计 + * @param {Object} content - 产品内容对象 + * @returns {Object} 更新后的产品内容对象 + */ +export function recalculateTotals(content) { + if (!content) { + return { ...DEFAULT_PRODUCT_CONTENT }; + } + + const products = (content.products || []).map(product => ({ + ...product, + taxTotal: calculateProductTaxTotal(product) + })); + + const totalQuantity = calculateTotalQuantity(products); + const totalTaxTotal = calculateTotalTaxTotal(products); + + return { + ...content, + products, + totalQuantity, + totalTaxTotal, + totalAmountInWords: convertToChinese(totalTaxTotal) + }; +} + +/** + * 将订单明细项转换为产品项 + * @param {Object} orderItem - 订单明细对象 + * @returns {Object} 产品项对象 + */ +export function convertOrderItemToProduct(orderItem) { + if (!orderItem) return {}; + + return { + spec: orderItem.finishedProductSpec || '', + material: orderItem.material || '', + quantity: parseFloat(orderItem.weight) || 0, + taxPrice: parseFloat(orderItem.contractPrice) || 0, + noTaxPrice: parseFloat(orderItem.itemAmount) || 0, + taxTotal: (parseFloat(orderItem.contractPrice) || 0) * (parseFloat(orderItem.weight) || 0), + remark: orderItem.remark || '' + }; +} + +/** + * 将订单明细列表转换为产品内容对象 + * @param {Array} orderItems - 订单明细列表 + * @param {string} productName - 产品名称 + * @param {string} remark - 备注 + * @returns {Object} 产品内容对象 + */ +export function convertOrderItemsToContent(orderItems, productName = '', remark = '') { + const products = (orderItems || []).map(convertOrderItemToProduct); + + const content = { + products, + productName, + remark + }; + + return recalculateTotals(content); +} + +/** + * 添加产品项到产品内容 + * @param {Object} content - 产品内容对象 + * @param {Object} product - 要添加的产品项 + * @returns {Object} 更新后的产品内容对象 + */ +export function addProduct(content, product) { + if (!content) content = { ...DEFAULT_PRODUCT_CONTENT }; + if (!product) return content; + + const newContent = { + ...content, + products: [...(content.products || []), { ...product }] + }; + + return recalculateTotals(newContent); +} + +/** + * 删除产品内容中的产品项 + * @param {Object} content - 产品内容对象 + * @param {number} index - 要删除的索引 + * @returns {Object} 更新后的产品内容对象 + */ +export function removeProduct(content, index) { + if (!content || !content.products || index < 0 || index >= content.products.length) { + return content; + } + + const newProducts = [...content.products]; + newProducts.splice(index, 1); + + const newContent = { + ...content, + products: newProducts + }; + + return recalculateTotals(newContent); +} + +/** + * 更新产品内容中的产品项 + * @param {Object} content - 产品内容对象 + * @param {number} index - 要更新的索引 + * @param {Object} product - 新的产品项数据 + * @returns {Object} 更新后的产品内容对象 + */ +export function updateProduct(content, index, product) { + if (!content || !content.products || index < 0 || index >= content.products.length || !product) { + return content; + } + + const newProducts = [...content.products]; + newProducts[index] = { ...newProducts[index], ...product }; + + const newContent = { + ...content, + products: newProducts + }; + + return recalculateTotals(newContent); +} + +/** + * 数字金额转中文大写 + * @param {number} amount - 数字金额 + * @returns {string} 中文大写金额 + */ +export function convertToChinese(amount) { + if (amount === 0) return '零元整'; + + const digits = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']; + const units = ['', '拾', '佰', '仟']; + const bigUnits = ['', '万', '亿']; + + let integerPart = Math.floor(amount); + let decimalPart = Math.round((amount - integerPart) * 100); + + let result = ''; + + // 处理整数部分 + if (integerPart > 0) { + let unitIndex = 0; + let bigUnitIndex = 0; + + while (integerPart > 0) { + let section = integerPart % 10000; + if (section > 0) { + let sectionResult = ''; + let temp = section; + let unitIndexInSection = 0; + + while (temp > 0) { + let digit = temp % 10; + if (digit > 0) { + sectionResult = digits[digit] + units[unitIndexInSection] + sectionResult; + } else { + // 避免连续的零 + if (sectionResult && !sectionResult.startsWith('零')) { + sectionResult = '零' + sectionResult; + } + } + temp = Math.floor(temp / 10); + unitIndexInSection++; + } + + result = sectionResult + bigUnits[bigUnitIndex] + result; + } + + integerPart = Math.floor(integerPart / 10000); + bigUnitIndex++; + } + + result += '元'; + } + + // 处理小数部分 + if (decimalPart === 0) { + result += '整'; + } else { + const jiao = Math.floor(decimalPart / 10); + const fen = decimalPart % 10; + if (jiao > 0) { + result += digits[jiao] + '角'; + } + if (fen > 0) { + result += digits[fen] + '分'; + } + } + + return result; +} + +/** + * 获取默认产品内容对象 + * @returns {Object} 默认产品内容 + */ +export function getDefaultProductContent() { + return { ...DEFAULT_PRODUCT_CONTENT }; +} + +/** + * ProductContent 工具类 + */ +export default { + parse: parseProductContent, + stringify: stringifyProductContent, + calculateTotalQuantity, + calculateTotalTaxTotal, + calculateProductTaxTotal, + recalculateTotals, + convertOrderItemToProduct, + convertOrderItemsToContent, + addProduct, + removeProduct, + updateProduct, + convertToChinese, + getDefault: getDefaultProductContent +}; diff --git a/klp-ui/src/views/crm/components/OrderDetail.vue b/klp-ui/src/views/crm/components/OrderDetail.vue index 97db5ae8..debfa7f5 100644 --- a/klp-ui/src/views/crm/components/OrderDetail.vue +++ b/klp-ui/src/views/crm/components/OrderDetail.vue @@ -143,6 +143,12 @@ import { listOrderItem, getOrderItem } from "@/api/crm/orderItem"; import { actions, ORDER_ACTIONS } from "../js/actions"; import { getOrder, updateOrder } from "@/api/crm/order"; import OrderPrinter from "./OrderPrinter.vue"; +import { + parseProductContent, + stringifyProductContent, + convertOrderItemToProduct, + addProduct +} from '@/utils/productContent'; export default { name: "OrderItem", @@ -284,113 +290,14 @@ export default { this.orderLoading = false; }); }, - // 计算大写金额 - totalAmountInWords(amountStr) { - const amount = parseFloat(amountStr); - if (isNaN(amount)) return '无效的金额格式'; - - if (amount === 0) return '零元整'; - - const digits = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']; - const units = ['', '拾', '佰', '仟']; - const bigUnits = ['', '万', '亿']; - - let integerPart = Math.floor(amount); - let decimalPart = Math.round((amount - integerPart) * 100); - - let result = ''; - - // 处理整数部分 - if (integerPart > 0) { - let unitIndex = 0; - let bigUnitIndex = 0; - - while (integerPart > 0) { - let section = integerPart % 10000; - if (section > 0) { - let sectionResult = ''; - let temp = section; - let unitIndexInSection = 0; - - while (temp > 0) { - let digit = temp % 10; - if (digit > 0) { - sectionResult = digits[digit] + units[unitIndexInSection] + sectionResult; - } else { - // 避免连续的零 - if (sectionResult && !sectionResult.startsWith('零')) { - sectionResult = '零' + sectionResult; - } - } - temp = Math.floor(temp / 10); - unitIndexInSection++; - } - - result = sectionResult + bigUnits[bigUnitIndex] + result; - } - - integerPart = Math.floor(integerPart / 10000); - bigUnitIndex++; - } - - result += '元'; - } - - // 处理小数部分 - if (decimalPart === 0) { - result += '整'; - } else { - if (decimalPart >= 10) { - result += digits[Math.floor(decimalPart / 10)] + '角'; - } - if (decimalPart % 10 > 0) { - result += digits[decimalPart % 10] + '分'; - } - } - - return result; - }, async handleWriteToContract(row) { const { data } = await getOrder(this.orderId); - let productContent = data.productContent; try { - if (!productContent) { - productContent = { - productName: '', - products: [{ - spec: row.finishedProductSpec || '', - material: row.material || '', - quantity: parseFloat(row.weight) || 0, - taxPrice: parseFloat(row.contractPrice) || 0, - noTaxPrice: parseFloat(row.itemAmount) || 0, - taxTotal: parseFloat(row.contractPrice) || 0 * (parseFloat(row.weight) || 0), - remark: row.remark || '' - }], - remark: '', - totalQuantity: parseFloat(row.weight) || 0, - totalTaxTotal: parseFloat(row.contractPrice) || 0 * (parseFloat(row.weight) || 0), - totalAmountInWords: this.totalAmountInWords(parseFloat(row.contractPrice) || 0 * (parseFloat(row.weight) || 0)).toString() - }; - } else { - productContent = JSON.parse(productContent); - productContent.products.push({ - spec: row.finishedProductSpec || '', - material: row.material || '', - quantity: parseFloat(row.weight) || 0, - taxPrice: parseFloat(row.contractPrice) || 0, - noTaxPrice: parseFloat(row.itemAmount) || 0, - taxTotal: parseFloat(row.contractPrice) || 0 * (parseFloat(row.weight) || 0), - remark: row.remark || '' - }); - // 将现有的全部加和计算 - productContent.totalQuantity = productContent.products.reduce((acc, cur) => acc + (parseFloat(cur.quantity) || 0), 0); - console.log(productContent.totalQuantity); - productContent.totalTaxTotal = productContent.products.reduce((acc, cur) => acc + (parseFloat(cur.taxTotal) || 0), 0); - productContent.totalAmountInWords = this.totalAmountInWords(productContent.totalTaxTotal).toString(); - } - console.log(productContent); - const jsonContent = JSON.stringify(productContent, null, 2); - console.log(jsonContent); + let content = parseProductContent(data.productContent); + const product = convertOrderItemToProduct(row); + content = addProduct(content, product); + + const jsonContent = stringifyProductContent(content); updateOrder({ ...this.orderContent, productContent: jsonContent @@ -402,8 +309,6 @@ export default { this.$modal.msgError("产品内容格式错误"); return; } - - console.log(productContent.products); }, printOrder() { this.$refs["printer"].print(); diff --git a/klp-ui/src/views/crm/components/ReceiveTable.vue b/klp-ui/src/views/crm/components/ReceiveTable.vue index f95c738f..120e2f74 100644 --- a/klp-ui/src/views/crm/components/ReceiveTable.vue +++ b/klp-ui/src/views/crm/components/ReceiveTable.vue @@ -1,9 +1,9 @@