Files
klp-oa/klp-ui/src/views/crm/contract/components/ProductContent.vue
砂糖 050dd1a965 feat(crm): 合同含税总金额自动填入订单总金额 & 移除冗余页面
- feat(crm/contract): 含税总额变化后自动填写订单总金额(可配置开关)
- fix(crm/receive): 修复金额单位错误(万元→元);清理未使用导入
- fix(contract/product): 产品备注设置默认值
- chore: 移除已废弃的 OrderDashboard 组件和 finance/order 页面
- feat(wms/hrm): 新增考勤异常管理页面(attendanceAbnormal.vue)
- chore: 移除 trae git 提交规则配置
2026-06-06 13:01:38 +08:00

393 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div v-loading="loading">
<!-- 网格布局实现的表格共8列 -->
<div class="product-content">
<!-- 第一行合并所有单元格 -->
<div class="table-row table-header">
<div class="table-cell" colspan="3">
<div class="company-name">产品名称
<el-select style="width: 120px;" v-model="productName" placeholder="请选择产品名称" size="small" :readonly="readonly" filterable allow-create clearable>
<el-option v-for="item in productOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
</div>
<div class="table-cell" colspan="8">
<div class="company-name">生产厂家嘉祥科伦普重工有限公司</div>
</div>
</div>
<!-- 第二行为表头 -->
<div class="table-row">
<div class="table-cell">序号</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">含税总额</div>
<div class="table-cell">无税总额</div>
<div class="table-cell">税额</div>
<div class="table-cell">
备注
<el-button v-if="!readonly" type="primary" size="mini" icon="el-icon-plus" @click="addProduct"
style="margin-left: 10px;">
添加行
</el-button>
</div>
</div>
<!-- 产品行 -->
<div v-for="(item, index) in products" :key="index" class="table-row" :class="{ 'table-row-hover': !readonly }">
<div class="table-cell">
<div class="serial-number">
<span>{{ index + 1 }}</span>
<el-button v-if="!readonly && products.length > 1" type="text" size="mini" icon="el-icon-delete"
class="delete-btn" @click="removeProduct(index)" />
</div>
</div>
<div class="table-cell">
<el-input v-model="item.spec" placeholder="请输入规格" :readonly="readonly" size="small" />
</div>
<div class="table-cell">
<el-input v-model="item.material" placeholder="请输入材质" :readonly="readonly" size="small" />
</div>
<div class="table-cell">
<el-input v-model.number="item.quantity" placeholder="请输入数量" type="number" :readonly="readonly" size="small"
@change="onQuantityChange(item)" />
</div>
<div class="table-cell">
<el-input v-model.number="item.taxPrice" placeholder="请输入含税单价" type="number" :readonly="readonly" size="small"
@change="onTaxPriceChange(item)" />
</div>
<div class="table-cell">
<el-input v-model.number="item.taxDivisor" placeholder="税率除数" type="number" :readonly="readonly"
size="small" />
</div>
<div class="table-cell">
<el-input v-model.number="item.noTaxPrice" placeholder="无税单价" type="number" :readonly="readonly"
size="small" />
</div>
<div class="table-cell">
<el-input v-model.number="item.taxTotal" placeholder="含税总额" type="number" :readonly="readonly" size="small" />
</div>
<div class="table-cell">
<el-input v-model.number="item.noTaxTotal" placeholder="无税总额" type="number" :readonly="readonly"
size="small" />
</div>
<div class="table-cell">
<el-input v-model.number="item.taxAmount" placeholder="税额" type="number" :readonly="readonly" size="small" />
</div>
<div class="table-cell">
<el-input v-model="item.remark" placeholder="请输入备注" :readonly="readonly" size="small" />
</div>
</div>
<!-- 合计行 -->
<div class="table-row table-total-row">
<div class="table-cell" colspan="3">合计</div>
<div class="table-cell">{{ totalQuantity.toFixed(3) }}</div>
<div class="table-cell"></div>
<div class="table-cell"></div>
<div class="table-cell"></div>
<div class="table-cell">{{ totalTaxTotal.toFixed(3) }}</div>
<div class="table-cell">{{ totalNoTaxTotal.toFixed(3) }}</div>
<div class="table-cell">{{ totalTaxAmount.toFixed(3) }}</div>
<div class="table-cell"></div>
</div>
<!-- 合计人民币(大写) -->
<div class="table-row">
<div class="table-cell" colspan="11">
<span>合计人民币(大写)</span>
<span>{{ totalAmountInWords }}</span>
</div>
</div>
<!-- 备注 -->
<div class="table-row">
<div class="table-cell" colspan="11">
<span>备注</span>
<el-input v-model="remark" type="textarea" placeholder="请输入备注" :readonly="readonly"
:autosize="{ minRows: 2 }" />
</div>
</div>
</div>
</div>
</template>
<script>
import {
parseProductContent,
calculateTotalQuantity,
calculateTotalTaxTotal,
calculateProductFields,
convertToChinese
} from '@/utils/productContent';
export default {
name: 'ProductContent',
props: {
value: {
// 一个json字符串
type: String,
required: true,
},
readonly: {
type: Boolean,
default: false
}
},
data() {
return {
loading: false,
products: [],
remark: '',
productName: '',
productOptions: [
{ label: '冷硬钢卷', value: '冷硬钢卷' },
{ label: '镀锌钢卷', value: '镀锌钢卷' },
{ label: '冷轧钢卷', value: '冷轧钢卷' },
{ label: '镀铬钢卷', value: '镀铬钢卷' },
]
}
},
computed: {
// 计算总数量
totalQuantity() {
return calculateTotalQuantity(this.products);
},
// 计算总含税总额
totalTaxTotal() {
return calculateTotalTaxTotal(this.products);
},
// 计算总无税总额
totalNoTaxTotal() {
return this.products.reduce((total, item) => {
return total + (parseFloat(item.noTaxTotal) || 0);
}, 0);
},
// 计算总税额
totalTaxAmount() {
return this.products.reduce((total, item) => {
return total + (parseFloat(item.taxAmount) || 0);
}, 0);
},
// 计算大写金额
totalAmountInWords() {
return convertToChinese(this.totalTaxTotal);
},
// 生成JSON字符串
jsonContent() {
// 只存储非空行
const nonEmptyProducts = this.products.filter(item => {
return item.spec || item.material || item.quantity || item.taxPrice || item.remark;
});
const data = {
products: nonEmptyProducts,
productName: this.productName,
remark: this.remark,
totalQuantity: this.totalQuantity,
totalTaxTotal: this.totalTaxTotal,
totalNoTaxTotal: this.totalNoTaxTotal,
totalTaxAmount: this.totalTaxAmount,
totalAmountInWords: this.totalAmountInWords
};
return JSON.stringify(data, null, 2);
},
},
watch: {
// 监听jsonContent变化触发update事件
jsonContent: {
handler(newValue) {
this.$emit('input', newValue);
},
deep: true
},
// 监听value变化更新内部数据
value: {
handler(newValue) {
if (!newValue) {
const defaultItem = { taxDivisor: 1.13 };
this.initProduct(defaultItem);
this.products = [defaultItem];
this.remark = '';
this.productName = '';
return;
}
this.parseContent(newValue);
},
immediate: true
}
},
methods: {
// 初始化产品的默认字段
initProduct(item) {
if (item.taxDivisor === undefined || item.taxDivisor === null) {
item.taxDivisor = 1.13;
}
if (item.noTaxPrice === undefined) item.noTaxPrice = 0;
if (item.noTaxTotal === undefined) item.noTaxTotal = 0;
if (item.taxAmount === undefined) item.taxAmount = 0;
if (item.taxTotal === undefined) item.taxTotal = 0;
},
// 解析content字符串
parseContent(content) {
try {
const data = parseProductContent(content);
const products = data.products.length > 0 ? data.products : [{}];
products.forEach(item => {
this.initProduct(item);
Object.assign(item, calculateProductFields(item, 'quantity'));
});
this.products = products;
this.remark = data.remark || '净边料/毛边料、简包/裸包、卷重结算';
this.productName = data.productName || '';
} catch (error) {
console.error('解析content失败:', error);
this.products = [{}];
this.remark = '净边料/毛边料、简包/裸包、卷重结算';
}
},
// 数量变更
onQuantityChange(item) {
Object.assign(item, calculateProductFields(item, 'quantity'));
},
// 含税单价变更
onTaxPriceChange(item) {
Object.assign(item, calculateProductFields(item, 'taxPrice'));
},
// 计算金额
calculateTotals() {
this.products.forEach(item => {
Object.assign(item, calculateProductFields(item, 'quantity'));
});
},
// 添加产品行
addProduct() {
const newItem = { taxDivisor: 1.13 };
this.initProduct(newItem);
this.products.push(newItem);
},
// 删除产品行
removeProduct(index) {
if (this.products.length > 1) {
this.products.splice(index, 1);
this.calculateTotals();
}
}
},
mounted() {
// 确保至少有一个空行
if (this.products.length === 0) {
const newItem = { taxDivisor: 1.13 };
this.initProduct(newItem);
this.products.push(newItem);
}
}
}
</script>
<style scoped>
.product-content {
border: 1px solid #e4e7ed;
border-radius: 4px;
overflow-x: auto;
}
.table-header {
background-color: #f5f7fa;
text-align: center;
border-bottom: 1px solid #e4e7ed;
}
.company-name {
font-size: 16px;
font-weight: bold;
display: flex;
align-items: center;
}
.table-row {
display: grid;
grid-template-columns: 45px 100px 90px 75px 100px 90px 100px 110px 110px 110px 1fr;
border-bottom: 1px solid #e4e7ed;
}
.table-row-hover:hover {
background-color: #f5f7fa;
}
.table-header-row {
background-color: #f5f7fa;
font-weight: bold;
}
.table-total-row {
background-color: #f5f7fa;
font-weight: bold;
}
.table-cell {
padding: 8px;
border-right: 1px solid #e4e7ed;
display: flex;
align-items: center;
}
.table-cell:last-child {
border-right: none;
}
.table-cell[colspan="3"] {
grid-column: span 3;
}
.table-cell[colspan="5"] {
grid-column: span 5;
}
.table-cell[colspan="8"] {
grid-column: span 8;
}
.table-cell[colspan="9"] {
grid-column: span 9;
}
.table-cell[colspan="11"] {
grid-column: span 11;
}
.serial-number {
position: relative;
display: inline-flex;
align-items: center;
}
.delete-btn {
opacity: 0;
margin-left: 10px;
}
.serial-number:hover .delete-btn {
opacity: 1;
}
.el-input {
width: 100%;
}
/* 隐藏 number 输入框的上下箭头 */
.el-input /deep/ input[type="number"]::-webkit-outer-spin-button,
.el-input /deep/ input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
.el-input /deep/ input[type="number"] {
-moz-appearance: textfield;
}
</style>