Files
klp-oa/klp-ui/src/views/crm/contract/components/ProductContent.vue

393 lines
11 KiB
Vue
Raw Normal View History

<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>