- 在合同页面启用ProductContent组件替代注释代码 - 优化ProductContent组件数值计算和空值处理 - 修改ContractList组件从productContent字段获取数据 - 在OrderDetail组件添加"写入合同"功能 - 优化ReceiveTable组件未收款金额计算逻辑
436 lines
12 KiB
Vue
436 lines
12 KiB
Vue
<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> |