feat(contract): 新增合同打印功能,重构产品金额计算逻辑
1. 新增合同打印预览功能,支持A4格式打印,包含合同完整信息和产品明细 2. 新增多个产品金额计算工具函数,统一管理产品税额、无税单价等字段计算 3. 重构产品内容组件,新增税率除数、无税单价、税额列,实现字段联动自动计算 4. 新增合同logo静态资源,优化表格布局和样式
This commit is contained in:
@@ -2,16 +2,16 @@
|
||||
<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 v-model="productName" placeholder="请选择产品名称" size="small" :readonly="readonly" filterable allow-create clearable>
|
||||
<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="5">
|
||||
<div class="table-cell" colspan="8">
|
||||
<div class="company-name">生产厂家:嘉祥科伦普重工有限公司</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -23,9 +23,11 @@
|
||||
<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>
|
||||
<div class="table-cell">
|
||||
备注
|
||||
<el-button v-if="!readonly" type="primary" size="mini" icon="el-icon-plus" @click="addProduct"
|
||||
@@ -52,23 +54,32 @@
|
||||
</div>
|
||||
<div class="table-cell">
|
||||
<el-input v-model.number="item.quantity" placeholder="请输入数量" type="number" :readonly="readonly" size="small"
|
||||
@change="calculateTotals" />
|
||||
@change="onQuantityChange(item)" />
|
||||
</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" v-if="false">
|
||||
<el-input v-model.number="item.noTaxPrice" placeholder="请输入不含税单价" type="number" :readonly="readonly"
|
||||
size="small" @change="calculateTotals" />
|
||||
@change="onTaxPriceChange(item)" />
|
||||
</div>
|
||||
<div class="table-cell">
|
||||
<el-input v-model.number="item.taxTotal" placeholder="含税总额" type="number" :readonly="true" size="small" />
|
||||
<el-input v-model.number="item.taxDivisor" placeholder="税率除数" type="number" :readonly="readonly"
|
||||
size="small" @change="onTaxDivisorChange(item)" />
|
||||
</div>
|
||||
<div class="table-cell">
|
||||
<el-input :value="((item.taxTotal || 0) / 1.13).toFixed(2)" placeholder="不含税总额" type="number" :readonly="true"
|
||||
size="small" />
|
||||
<el-input v-model.number="item.noTaxPrice" placeholder="无税单价" type="number" :readonly="readonly"
|
||||
size="small" @change="onNoTaxPriceChange(item)" />
|
||||
</div>
|
||||
<div class="table-cell">
|
||||
<el-input v-model.number="item.taxTotal" placeholder="含税总额" type="number" :readonly="readonly" size="small"
|
||||
@change="onTaxTotalChange(item)" />
|
||||
</div>
|
||||
<div class="table-cell">
|
||||
<el-input v-model.number="item.noTaxTotal" placeholder="无税总额" type="number" :readonly="readonly"
|
||||
size="small" @change="onNoTaxTotalChange(item)" />
|
||||
</div>
|
||||
<div class="table-cell">
|
||||
<el-input v-model.number="item.taxAmount" placeholder="税额" type="number" :readonly="readonly" size="small"
|
||||
@change="onTaxAmountChange(item)" />
|
||||
</div>
|
||||
<div class="table-cell">
|
||||
<el-input v-model="item.remark" placeholder="请输入备注" :readonly="readonly" size="small" />
|
||||
@@ -78,17 +89,19 @@
|
||||
<!-- 合计行 -->
|
||||
<div class="table-row table-total-row">
|
||||
<div class="table-cell" colspan="3">合计</div>
|
||||
<div class="table-cell">{{ totalQuantity }}</div>
|
||||
<div class="table-cell" v-if="false"></div>
|
||||
<div class="table-cell">{{ totalQuantity.toFixed(2) }}</div>
|
||||
<div class="table-cell"></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 class="table-cell">{{ totalTaxTotal.toFixed(2) }}</div>
|
||||
<div class="table-cell">{{ totalNoTaxTotal.toFixed(2) }}</div>
|
||||
<div class="table-cell">{{ totalTaxAmount.toFixed(2) }}</div>
|
||||
<div class="table-cell"></div>
|
||||
</div>
|
||||
|
||||
<!-- 合计人民币(大写) -->
|
||||
<div class="table-row">
|
||||
<div class="table-cell" colspan="8">
|
||||
<div class="table-cell" colspan="11">
|
||||
<span>合计人民币(大写):</span>
|
||||
<span>{{ totalAmountInWords }}</span>
|
||||
</div>
|
||||
@@ -96,7 +109,7 @@
|
||||
|
||||
<!-- 备注 -->
|
||||
<div class="table-row">
|
||||
<div class="table-cell" colspan="8">
|
||||
<div class="table-cell" colspan="11">
|
||||
<span>备注:</span>
|
||||
<el-input v-model="remark" type="textarea" placeholder="请输入备注" :readonly="readonly"
|
||||
:autosize="{ minRows: 2 }" />
|
||||
@@ -111,7 +124,7 @@ import {
|
||||
parseProductContent,
|
||||
calculateTotalQuantity,
|
||||
calculateTotalTaxTotal,
|
||||
calculateProductTaxTotal,
|
||||
calculateProductFields,
|
||||
convertToChinese
|
||||
} from '@/utils/productContent';
|
||||
|
||||
@@ -151,6 +164,18 @@ export default {
|
||||
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);
|
||||
@@ -159,7 +184,7 @@ export default {
|
||||
jsonContent() {
|
||||
// 只存储非空行
|
||||
const nonEmptyProducts = this.products.filter(item => {
|
||||
return item.spec || item.material || item.quantity || item.taxPrice || item.noTaxPrice || item.remark;
|
||||
return item.spec || item.material || item.quantity || item.taxPrice || item.remark;
|
||||
});
|
||||
const data = {
|
||||
products: nonEmptyProducts,
|
||||
@@ -167,6 +192,8 @@ export default {
|
||||
remark: this.remark,
|
||||
totalQuantity: this.totalQuantity,
|
||||
totalTaxTotal: this.totalTaxTotal,
|
||||
totalNoTaxTotal: this.totalNoTaxTotal,
|
||||
totalTaxAmount: this.totalTaxAmount,
|
||||
totalAmountInWords: this.totalAmountInWords
|
||||
};
|
||||
return JSON.stringify(data, null, 2);
|
||||
@@ -184,7 +211,9 @@ export default {
|
||||
value: {
|
||||
handler(newValue) {
|
||||
if (!newValue) {
|
||||
this.products = [{}];
|
||||
const defaultItem = { taxDivisor: 1.13 };
|
||||
this.initProduct(defaultItem);
|
||||
this.products = [defaultItem];
|
||||
this.remark = '';
|
||||
this.productName = '';
|
||||
return;
|
||||
@@ -195,11 +224,26 @@ export default {
|
||||
}
|
||||
},
|
||||
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);
|
||||
this.products = data.products.length > 0 ? data.products : [{}];
|
||||
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) {
|
||||
@@ -208,15 +252,45 @@ export default {
|
||||
this.remark = '';
|
||||
}
|
||||
},
|
||||
// 数量变更
|
||||
onQuantityChange(item) {
|
||||
Object.assign(item, calculateProductFields(item, 'quantity'));
|
||||
},
|
||||
// 含税单价变更
|
||||
onTaxPriceChange(item) {
|
||||
Object.assign(item, calculateProductFields(item, 'taxPrice'));
|
||||
},
|
||||
// 税率除数变更
|
||||
onTaxDivisorChange(item) {
|
||||
Object.assign(item, calculateProductFields(item, 'taxDivisor'));
|
||||
},
|
||||
// 无税单价变更
|
||||
onNoTaxPriceChange(item) {
|
||||
Object.assign(item, calculateProductFields(item, 'noTaxPrice'));
|
||||
},
|
||||
// 含税总额变更
|
||||
onTaxTotalChange(item) {
|
||||
Object.assign(item, calculateProductFields(item, 'taxTotal'));
|
||||
},
|
||||
// 无税总额变更
|
||||
onNoTaxTotalChange(item) {
|
||||
Object.assign(item, calculateProductFields(item, 'noTaxTotal'));
|
||||
},
|
||||
// 税额变更
|
||||
onTaxAmountChange(item) {
|
||||
Object.assign(item, calculateProductFields(item, 'taxAmount'));
|
||||
},
|
||||
// 计算金额
|
||||
calculateTotals() {
|
||||
this.products.forEach(item => {
|
||||
item.taxTotal = calculateProductTaxTotal(item);
|
||||
Object.assign(item, calculateProductFields(item, 'quantity'));
|
||||
});
|
||||
},
|
||||
// 添加产品行
|
||||
addProduct() {
|
||||
this.products.push({});
|
||||
const newItem = { taxDivisor: 1.13 };
|
||||
this.initProduct(newItem);
|
||||
this.products.push(newItem);
|
||||
},
|
||||
// 删除产品行
|
||||
removeProduct(index) {
|
||||
@@ -229,7 +303,9 @@ export default {
|
||||
mounted() {
|
||||
// 确保至少有一个空行
|
||||
if (this.products.length === 0) {
|
||||
this.products.push({});
|
||||
const newItem = { taxDivisor: 1.13 };
|
||||
this.initProduct(newItem);
|
||||
this.products.push(newItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,7 +315,7 @@ export default {
|
||||
.product-content {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
@@ -251,11 +327,14 @@ export default {
|
||||
.company-name {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: grid;
|
||||
grid-template-columns: 80px 150px 120px 100px 160px 120px 140px 1fr;
|
||||
grid-template-columns: 45px 100px 90px 75px 100px 90px 100px 110px 110px 110px 1fr;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
@@ -300,6 +379,10 @@ export default {
|
||||
grid-column: span 9;
|
||||
}
|
||||
|
||||
.table-cell[colspan="11"] {
|
||||
grid-column: span 11;
|
||||
}
|
||||
|
||||
.serial-number {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
@@ -318,4 +401,14 @@ export default {
|
||||
.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>
|
||||
Reference in New Issue
Block a user