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

436 lines
12 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-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>