feat(金额显示): 统一金额单位为万元并优化产品内容处理
refactor(产品内容): 提取产品内容处理逻辑到独立工具类 style(合同表单): 调整技术附件和商务附件顺序
This commit is contained in:
315
klp-ui/src/utils/productContent.js
Normal file
315
klp-ui/src/utils/productContent.js
Normal file
@@ -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
|
||||||
|
};
|
||||||
@@ -143,6 +143,12 @@ import { listOrderItem, getOrderItem } from "@/api/crm/orderItem";
|
|||||||
import { actions, ORDER_ACTIONS } from "../js/actions";
|
import { actions, ORDER_ACTIONS } from "../js/actions";
|
||||||
import { getOrder, updateOrder } from "@/api/crm/order";
|
import { getOrder, updateOrder } from "@/api/crm/order";
|
||||||
import OrderPrinter from "./OrderPrinter.vue";
|
import OrderPrinter from "./OrderPrinter.vue";
|
||||||
|
import {
|
||||||
|
parseProductContent,
|
||||||
|
stringifyProductContent,
|
||||||
|
convertOrderItemToProduct,
|
||||||
|
addProduct
|
||||||
|
} from '@/utils/productContent';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "OrderItem",
|
name: "OrderItem",
|
||||||
@@ -284,113 +290,14 @@ export default {
|
|||||||
this.orderLoading = false;
|
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) {
|
async handleWriteToContract(row) {
|
||||||
const { data } = await getOrder(this.orderId);
|
const { data } = await getOrder(this.orderId);
|
||||||
let productContent = data.productContent;
|
|
||||||
try {
|
try {
|
||||||
if (!productContent) {
|
let content = parseProductContent(data.productContent);
|
||||||
productContent = {
|
const product = convertOrderItemToProduct(row);
|
||||||
productName: '',
|
content = addProduct(content, product);
|
||||||
products: [{
|
|
||||||
spec: row.finishedProductSpec || '',
|
const jsonContent = stringifyProductContent(content);
|
||||||
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);
|
|
||||||
updateOrder({
|
updateOrder({
|
||||||
...this.orderContent,
|
...this.orderContent,
|
||||||
productContent: jsonContent
|
productContent: jsonContent
|
||||||
@@ -402,8 +309,6 @@ export default {
|
|||||||
this.$modal.msgError("产品内容格式错误");
|
this.$modal.msgError("产品内容格式错误");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(productContent.products);
|
|
||||||
},
|
},
|
||||||
printOrder() {
|
printOrder() {
|
||||||
this.$refs["printer"].print();
|
this.$refs["printer"].print();
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<el-descriptions :column="3" title="财务状态" border>
|
<el-descriptions :column="3" title="财务状态" border>
|
||||||
<el-descriptions-item label="订单总金额">{{ order.orderAmount }}</el-descriptions-item>
|
<el-descriptions-item label="订单总金额">{{ order.orderAmount }}万元</el-descriptions-item>
|
||||||
<el-descriptions-item label="已收款金额">{{ receivedAmount }}</el-descriptions-item>
|
<el-descriptions-item label="已收款金额">{{ receivedAmount }}万元</el-descriptions-item>
|
||||||
<el-descriptions-item label="未收款金额">{{ unreceivedAmount }}</el-descriptions-item>
|
<el-descriptions-item label="未收款金额">{{ unreceivedAmount }}万元</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
|
|
||||||
<el-descriptions title="收款明细"></el-descriptions>
|
<el-descriptions title="收款明细"></el-descriptions>
|
||||||
|
|||||||
@@ -127,6 +127,10 @@
|
|||||||
import { listOrder, updateOrder } from "@/api/crm/order";
|
import { listOrder, updateOrder } from "@/api/crm/order";
|
||||||
import * as ExcelJS from 'exceljs';
|
import * as ExcelJS from 'exceljs';
|
||||||
import { saveAs } from 'file-saver';
|
import { saveAs } from 'file-saver';
|
||||||
|
import {
|
||||||
|
parseProductContent,
|
||||||
|
convertToChinese
|
||||||
|
} from '@/utils/productContent';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ContractList",
|
name: "ContractList",
|
||||||
@@ -213,62 +217,7 @@ export default {
|
|||||||
this.showMoreFilter = !this.showMoreFilter;
|
this.showMoreFilter = !this.showMoreFilter;
|
||||||
},
|
},
|
||||||
convertToChinese(amount) {
|
convertToChinese(amount) {
|
||||||
if (amount === 0) return '零元整'
|
return convertToChinese(amount);
|
||||||
|
|
||||||
const digits = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']
|
|
||||||
const units = ['', '拾', '佰', '仟']
|
|
||||||
const bigUnits = ['', '万', '亿']
|
|
||||||
|
|
||||||
let integerPart = Math.floor(amount)
|
|
||||||
let decimalPart = Math.round((amount - integerPart) * 100)
|
|
||||||
|
|
||||||
let result = ''
|
|
||||||
let unitIndex = 0
|
|
||||||
let bigUnitIndex = 0
|
|
||||||
|
|
||||||
if (integerPart === 0) {
|
|
||||||
result = '零'
|
|
||||||
} else {
|
|
||||||
while (integerPart > 0) {
|
|
||||||
let section = integerPart % 10000
|
|
||||||
if (section > 0) {
|
|
||||||
let sectionResult = ''
|
|
||||||
let sectionUnitIndex = 0
|
|
||||||
|
|
||||||
while (section > 0) {
|
|
||||||
let digit = section % 10
|
|
||||||
if (digit > 0) {
|
|
||||||
sectionResult = digits[digit] + units[sectionUnitIndex] + sectionResult
|
|
||||||
} else if (sectionResult && !sectionResult.startsWith('零')) {
|
|
||||||
sectionResult = '零' + sectionResult
|
|
||||||
}
|
|
||||||
section = Math.floor(section / 10)
|
|
||||||
sectionUnitIndex++
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
},
|
},
|
||||||
/** 导出合同 */
|
/** 导出合同 */
|
||||||
async handleExport(row) {
|
async handleExport(row) {
|
||||||
@@ -413,21 +362,9 @@ export default {
|
|||||||
|
|
||||||
// 产品表格数据, 来源于一个json字符串(productContent,json结构参考ProductContent.vue)
|
// 产品表格数据, 来源于一个json字符串(productContent,json结构参考ProductContent.vue)
|
||||||
// 解析产品内容
|
// 解析产品内容
|
||||||
let productData = {
|
let productData = parseProductContent(row.productContent);
|
||||||
products: [],
|
if (!productData.productName && row.productName) {
|
||||||
productName: row.productName || '冷硬钢卷',
|
productData.productName = row.productName;
|
||||||
remark: '',
|
|
||||||
totalQuantity: 0,
|
|
||||||
totalTaxTotal: 0,
|
|
||||||
totalAmountInWords: '零元整'
|
|
||||||
};
|
|
||||||
|
|
||||||
if (row.productContent) {
|
|
||||||
try {
|
|
||||||
productData = JSON.parse(row.productContent);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('解析产品内容失败:', error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (orderItems) {
|
// if (orderItems) {
|
||||||
|
|||||||
@@ -18,9 +18,10 @@
|
|||||||
<div class="table-cell">规格(mm)</div>
|
<div class="table-cell">规格(mm)</div>
|
||||||
<div class="table-cell">材质</div>
|
<div class="table-cell">材质</div>
|
||||||
<div class="table-cell">数量(吨)</div>
|
<div class="table-cell">数量(吨)</div>
|
||||||
<div class="table-cell">含税单价(元/吨)</div>
|
<div class="table-cell" v-if="false">含税单价(元/吨)</div>
|
||||||
<div class="table-cell">不含税单价(元/吨)</div>
|
<div class="table-cell">不含税单价(元/吨)</div>
|
||||||
<div class="table-cell">含税总额(元)</div>
|
<div class="table-cell">含税总额(元)</div>
|
||||||
|
<div class="table-cell">不含税总额(元)</div>
|
||||||
<div class="table-cell">
|
<div class="table-cell">
|
||||||
备注
|
备注
|
||||||
<el-button
|
<el-button
|
||||||
@@ -82,7 +83,7 @@
|
|||||||
@change="calculateTotals"
|
@change="calculateTotals"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-cell">
|
<div class="table-cell" v-if="false">
|
||||||
<el-input
|
<el-input
|
||||||
v-model.number="item.taxPrice"
|
v-model.number="item.taxPrice"
|
||||||
placeholder="请输入含税单价"
|
placeholder="请输入含税单价"
|
||||||
@@ -111,6 +112,15 @@
|
|||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="table-cell">
|
||||||
|
<el-input
|
||||||
|
:value="((item.taxTotal || 0) / 1.13).toFixed(2)"
|
||||||
|
placeholder="不含税总额"
|
||||||
|
type="number"
|
||||||
|
:readonly="true"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="table-cell">
|
<div class="table-cell">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="item.remark"
|
v-model="item.remark"
|
||||||
@@ -125,9 +135,10 @@
|
|||||||
<div class="table-row table-total-row">
|
<div class="table-row table-total-row">
|
||||||
<div class="table-cell" colspan="3">合计</div>
|
<div class="table-cell" colspan="3">合计</div>
|
||||||
<div class="table-cell">{{ totalQuantity }}</div>
|
<div class="table-cell">{{ totalQuantity }}</div>
|
||||||
<div class="table-cell"></div>
|
<div class="table-cell" v-if="false"></div>
|
||||||
<div class="table-cell"></div>
|
<div class="table-cell"></div>
|
||||||
<div class="table-cell">{{ totalTaxTotal }}</div>
|
<div class="table-cell">{{ totalTaxTotal }}</div>
|
||||||
|
<div class="table-cell">{{ ((totalTaxTotal || 0) / 1.13).toFixed(2) }}</div>
|
||||||
<div class="table-cell"></div>
|
<div class="table-cell"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -157,6 +168,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import {
|
||||||
|
parseProductContent,
|
||||||
|
calculateTotalQuantity,
|
||||||
|
calculateTotalTaxTotal,
|
||||||
|
calculateProductTaxTotal,
|
||||||
|
convertToChinese
|
||||||
|
} from '@/utils/productContent';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ProductContent',
|
name: 'ProductContent',
|
||||||
props: {
|
props: {
|
||||||
@@ -181,79 +200,15 @@
|
|||||||
computed: {
|
computed: {
|
||||||
// 计算总数量
|
// 计算总数量
|
||||||
totalQuantity() {
|
totalQuantity() {
|
||||||
return this.products.reduce((total, item) => {
|
return calculateTotalQuantity(this.products);
|
||||||
return total + (parseFloat(item.quantity) || 0);
|
|
||||||
}, 0);
|
|
||||||
},
|
},
|
||||||
// 计算总含税总额
|
// 计算总含税总额
|
||||||
totalTaxTotal() {
|
totalTaxTotal() {
|
||||||
return this.products.reduce((total, item) => {
|
return calculateTotalTaxTotal(this.products);
|
||||||
return total + (parseFloat(item.taxTotal) || 0);
|
|
||||||
}, 0);
|
|
||||||
},
|
},
|
||||||
// 计算大写金额
|
// 计算大写金额
|
||||||
totalAmountInWords() {
|
totalAmountInWords() {
|
||||||
const amount = this.totalTaxTotal;
|
return convertToChinese(this.totalTaxTotal);
|
||||||
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;
|
|
||||||
},
|
},
|
||||||
// 生成JSON字符串
|
// 生成JSON字符串
|
||||||
jsonContent() {
|
jsonContent() {
|
||||||
@@ -298,23 +253,8 @@
|
|||||||
// 解析content字符串
|
// 解析content字符串
|
||||||
parseContent(content) {
|
parseContent(content) {
|
||||||
try {
|
try {
|
||||||
if (!content) {
|
const data = parseProductContent(content);
|
||||||
return {
|
this.products = data.products.length > 0 ? data.products : [{}];
|
||||||
productName: '',
|
|
||||||
products: [{
|
|
||||||
_isEmpty: true,
|
|
||||||
spec: '',
|
|
||||||
material: '',
|
|
||||||
quantity: '',
|
|
||||||
taxPrice: '',
|
|
||||||
noTaxPrice: '',
|
|
||||||
remark: ''
|
|
||||||
}],
|
|
||||||
remark: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const data = JSON.parse(content);
|
|
||||||
this.products = data.products || [{}];
|
|
||||||
this.remark = data.remark || '';
|
this.remark = data.remark || '';
|
||||||
this.productName = data.productName || '';
|
this.productName = data.productName || '';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -326,11 +266,7 @@
|
|||||||
// 计算金额
|
// 计算金额
|
||||||
calculateTotals() {
|
calculateTotals() {
|
||||||
this.products.forEach(item => {
|
this.products.forEach(item => {
|
||||||
if (item.quantity && item.taxPrice) {
|
item.taxTotal = calculateProductTaxTotal(item);
|
||||||
item.taxTotal = item.quantity * item.taxPrice;
|
|
||||||
} else {
|
|
||||||
item.taxTotal = 0;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// 添加产品行
|
// 添加产品行
|
||||||
@@ -374,7 +310,7 @@
|
|||||||
|
|
||||||
.table-row {
|
.table-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 80px 150px 120px 100px 140px 150px 120px 1fr;
|
grid-template-columns: 80px 150px 120px 100px 160px 120px 140px 1fr;
|
||||||
border-bottom: 1px solid #e4e7ed;
|
border-bottom: 1px solid #e4e7ed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,6 +350,9 @@
|
|||||||
.table-cell[colspan="8"] {
|
.table-cell[colspan="8"] {
|
||||||
grid-column: span 8;
|
grid-column: span 8;
|
||||||
}
|
}
|
||||||
|
.table-cell[colspan="9"] {
|
||||||
|
grid-column: span 9;
|
||||||
|
}
|
||||||
|
|
||||||
.serial-number {
|
.serial-number {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -147,12 +147,12 @@
|
|||||||
<el-form-item label="备注" prop="remark">
|
<el-form-item label="备注" prop="remark">
|
||||||
<el-input v-model="form.remark" placeholder="请输入备注" />
|
<el-input v-model="form.remark" placeholder="请输入备注" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="技术附件" prop="techAnnex">
|
|
||||||
<file-upload v-model="form.techAnnex" :fileType="['pdf', 'jpeg', 'jpg', 'png', 'webp']"></file-upload>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="商务附件" prop="businessAnnex">
|
<el-form-item label="商务附件" prop="businessAnnex">
|
||||||
<file-upload v-model="form.businessAnnex" :fileType="['pdf', 'jpeg', 'jpg', 'png', 'webp']"></file-upload>
|
<file-upload v-model="form.businessAnnex" :fileType="['pdf', 'jpeg', 'jpg', 'png', 'webp']"></file-upload>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="技术附件" prop="techAnnex">
|
||||||
|
<file-upload v-model="form.techAnnex" :fileType="['pdf', 'jpeg', 'jpg', 'png', 'webp']"></file-upload>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="排产函" prop="productionSchedule">
|
<el-form-item label="排产函" prop="productionSchedule">
|
||||||
<file-upload v-model="form.productionSchedule"
|
<file-upload v-model="form.productionSchedule"
|
||||||
:fileType="['pdf', 'jpeg', 'jpg', 'png', 'webp']"></file-upload>
|
:fileType="['pdf', 'jpeg', 'jpg', 'png', 'webp']"></file-upload>
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export default {
|
|||||||
const option = {
|
const option = {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: "item",
|
trigger: "item",
|
||||||
formatter: "{b}<br/>销售金额:{c} 元<br/>占比:{d}%"
|
formatter: "{b}<br/>销售金额:{c} 万元<br/>占比:{d}%"
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
orient: "vertical",
|
orient: "vertical",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<el-table-column prop="contactPerson" label="联系人" min-width="80"></el-table-column>
|
<el-table-column prop="contactPerson" label="联系人" min-width="80"></el-table-column>
|
||||||
<el-table-column prop="customerLevel" label="客户等级" min-width="100"></el-table-column>
|
<el-table-column prop="customerLevel" label="客户等级" min-width="100"></el-table-column>
|
||||||
<el-table-column prop="industry" label="所属行业" min-width="120"></el-table-column>
|
<el-table-column prop="industry" label="所属行业" min-width="120"></el-table-column>
|
||||||
<el-table-column prop="orderAmount" label="订单金额">
|
<el-table-column prop="orderAmount" label="订单金额(万元)">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ formatAmount(scope.row.orderAmount) }}
|
{{ formatAmount(scope.row.orderAmount) }}
|
||||||
</template>
|
</template>
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
<el-tag v-else type="danger">未结款</el-tag>
|
<el-tag v-else type="danger">未结款</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="unpaidAmount" label="未结金额" min-width="100">
|
<el-table-column prop="unpaidAmount" label="未结金额(万元)" min-width="100">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ formatAmount(scope.row.unpaidAmount) }}
|
{{ formatAmount(scope.row.unpaidAmount) }}
|
||||||
</template>
|
</template>
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
<el-table-column prop="productName" label="产品名称" min-width="150"></el-table-column>
|
<el-table-column prop="productName" label="产品名称" min-width="150"></el-table-column>
|
||||||
<el-table-column prop="specification" label="产品规格" min-width="120"></el-table-column>
|
<el-table-column prop="specification" label="产品规格" min-width="120"></el-table-column>
|
||||||
<el-table-column prop="productCount" label="产品数量" min-width="80"></el-table-column>
|
<el-table-column prop="productCount" label="产品数量" min-width="80"></el-table-column>
|
||||||
<el-table-column prop="unitPrice" label="单价(元)" min-width="100">
|
<el-table-column prop="unitPrice" label="单价(元 / 吨)" min-width="100">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ formatAmount(scope.row.unitPrice) }}
|
{{ formatAmount(scope.row.unitPrice) }}
|
||||||
</template>
|
</template>
|
||||||
@@ -115,7 +115,7 @@
|
|||||||
<span class="customer-info">客户编码:{{ customer.customerCode }}</span>
|
<span class="customer-info">客户编码:{{ customer.customerCode }}</span>
|
||||||
<span class="customer-info">客户等级:{{ customer.customerLevel }}</span>
|
<span class="customer-info">客户等级:{{ customer.customerLevel }}</span>
|
||||||
<span class="customer-info">所属行业:{{ customer.industry }}</span>
|
<span class="customer-info">所属行业:{{ customer.industry }}</span>
|
||||||
<span class="summary-info">总订单金额:{{ formatAmount(customer.totalOrderAmount) }} 元</span>
|
<span class="summary-info">总订单金额:{{ formatAmount(customer.totalOrderAmount) }} 万元</span>
|
||||||
<span class="summary-info">订单数量:{{ customer.orderCount }} 单</span>
|
<span class="summary-info">订单数量:{{ customer.orderCount }} 单</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
empty-text="该客户暂无订单数据"
|
empty-text="该客户暂无订单数据"
|
||||||
>
|
>
|
||||||
<el-table-column prop="orderCode" label="订单编号" min-width="120"></el-table-column>
|
<el-table-column prop="orderCode" label="订单编号" min-width="120"></el-table-column>
|
||||||
<el-table-column prop="orderAmount" label="订单金额(元)" min-width="120">
|
<el-table-column prop="orderAmount" label="订单金额(万元)" min-width="120">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ formatAmount(scope.row.orderAmount) }}
|
{{ formatAmount(scope.row.orderAmount) }}
|
||||||
</template>
|
</template>
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
<el-tag v-else type="danger">未结款</el-tag>
|
<el-tag v-else type="danger">未结款</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="unpaidAmount" label="未结金额(元)" min-width="120">
|
<el-table-column prop="unpaidAmount" label="未结金额(万元)" min-width="120">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ formatAmount(scope.row.unpaidAmount) }}
|
{{ formatAmount(scope.row.unpaidAmount) }}
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<span class="item-value">{{ summaryData.totalOrderCount || 0 }}</span>
|
<span class="item-value">{{ summaryData.totalOrderCount || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="summary-item">
|
<div class="summary-item">
|
||||||
<span class="item-label">总销售金额(元)</span>
|
<span class="item-label">总销售金额(万元)</span>
|
||||||
<span class="item-value">{{ formatAmount(summaryData.totalSalesAmount) }}</span>
|
<span class="item-value">{{ formatAmount(summaryData.totalSalesAmount) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="summary-item">
|
<div class="summary-item">
|
||||||
@@ -15,15 +15,15 @@
|
|||||||
<span class="item-value">{{ summaryData.completedOrderCount || 0 }}</span>
|
<span class="item-value">{{ summaryData.completedOrderCount || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="summary-item">
|
<div class="summary-item">
|
||||||
<span class="item-label">已完成销售金额(元)</span>
|
<span class="item-label">已完成销售金额(万元)</span>
|
||||||
<span class="item-value">{{ formatAmount(summaryData.completedSalesAmount) }}</span>
|
<span class="item-value">{{ formatAmount(summaryData.completedSalesAmount) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="summary-item">
|
<div class="summary-item">
|
||||||
<span class="item-label">未结款总金额(元)</span>
|
<span class="item-label">未结款总金额(万元)</span>
|
||||||
<span class="item-value">{{ formatAmount(summaryData.totalUnpaidAmount) }}</span>
|
<span class="item-value">{{ formatAmount(summaryData.totalUnpaidAmount) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="summary-item">
|
<div class="summary-item">
|
||||||
<span class="item-label">平均订单金额(元)</span>
|
<span class="item-label">平均订单金额(万元)</span>
|
||||||
<span class="item-value">{{ formatAmount(summaryData.avgOrderAmount) }}</span>
|
<span class="item-value">{{ formatAmount(summaryData.avgOrderAmount) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user