Files
klp-oa/klp-ui/src/views/crm/contract/components/ProductContent.vue
砂糖 84c26a2990 feat(contract): 启用产品内容组件并优化合同相关功能
- 在合同页面启用ProductContent组件替代注释代码
- 优化ProductContent组件数值计算和空值处理
- 修改ContractList组件从productContent字段获取数据
- 在OrderDetail组件添加"写入合同"功能
- 优化ReceiveTable组件未收款金额计算逻辑
2026-04-23 11:47:30 +08:00

436 lines
12 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-input style="width: 50%" v-model="productName" placeholder="请输入产品名称" size="small" :readonly="readonly"/></div>
</div>
<div class="table-cell" colspan="5">
<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">
备注
<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="calculateTotals"
/>
</div>
<div class="table-cell">
<el-input
v-model.number="item.taxPrice"
placeholder="请输入含税单价"
type="number"
:readonly="readonly"
size="small"
@change="calculateTotals"
/>
</div>
<div class="table-cell">
<el-input
v-model.number="item.noTaxPrice"
placeholder="请输入不含税单价"
type="number"
:readonly="readonly"
size="small"
@change="calculateTotals"
/>
</div>
<div class="table-cell">
<el-input
v-model.number="item.taxTotal"
placeholder="含税总额"
type="number"
:readonly="true"
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 }}</div>
<div class="table-cell"></div>
<div class="table-cell"></div>
<div class="table-cell">{{ totalTaxTotal }}</div>
<div class="table-cell"></div>
</div>
<!-- 合计人民币(大写) -->
<div class="table-row">
<div class="table-cell" colspan="8">
<span>合计人民币(大写)</span>
<span>{{ totalAmountInWords }}</span>
</div>
</div>
<!-- 备注 -->
<div class="table-row">
<div class="table-cell" colspan="8">
<span>备注</span>
<el-input
v-model="remark"
type="textarea"
placeholder="请输入备注"
:readonly="readonly"
:autosize="{ minRows: 2 }"
/>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'ProductContent',
props: {
value: {
// 一个json字符串
type: String,
required: true,
},
readonly: {
type: Boolean,
default: false
}
},
data() {
return {
loading: false,
products: [],
remark: '',
productName: '',
}
},
computed: {
// 计算总数量
totalQuantity() {
return this.products.reduce((total, item) => {
return total + (parseFloat(item.quantity) || 0);
}, 0);
},
// 计算总含税总额
totalTaxTotal() {
return this.products.reduce((total, item) => {
return total + (parseFloat(item.taxTotal) || 0);
}, 0);
},
// 计算大写金额
totalAmountInWords() {
const amount = 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字符串
jsonContent() {
// 只存储非空行
const nonEmptyProducts = this.products.filter(item => {
return item.spec || item.material || item.quantity || item.taxPrice || item.noTaxPrice || item.remark;
});
const data = {
products: nonEmptyProducts,
productName: this.productName,
remark: this.remark,
totalQuantity: this.totalQuantity,
totalTaxTotal: this.totalTaxTotal,
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) {
this.products = [{}];
this.remark = '';
this.productName = '';
return;
}
this.parseContent(newValue);
},
immediate: true
}
},
methods: {
// 解析content字符串
parseContent(content) {
try {
if (!content) {
return {
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.productName = data.productName || '';
} catch (error) {
console.error('解析content失败:', error);
this.products = [{}];
this.remark = '';
}
},
// 计算金额
calculateTotals() {
this.products.forEach(item => {
if (item.quantity && item.taxPrice) {
item.taxTotal = item.quantity * item.taxPrice;
} else {
item.taxTotal = 0;
}
});
},
// 添加产品行
addProduct() {
this.products.push({});
},
// 删除产品行
removeProduct(index) {
if (this.products.length > 1) {
this.products.splice(index, 1);
this.calculateTotals();
}
}
},
mounted() {
// 确保至少有一个空行
if (this.products.length === 0) {
this.products.push({});
}
}
}
</script>
<style scoped>
.product-content {
border: 1px solid #e4e7ed;
border-radius: 4px;
overflow: hidden;
}
.table-header {
background-color: #f5f7fa;
text-align: center;
border-bottom: 1px solid #e4e7ed;
}
.company-name {
font-size: 16px;
font-weight: bold;
}
.table-row {
display: grid;
grid-template-columns: 80px 150px 120px 100px 140px 150px 120px 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;
}
.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%;
}
</style>