feat(contract): 新增产品内容组件并优化合同管理功能
refactor(contract): 重构合同预览和列表组件 fix(contract): 修复合同ID类型校验问题 style(contract): 优化合同列表样式 docs(contract): 更新合同默认内容模板
This commit is contained in:
44
klp-ui/src/api/crm/contractProduct.js
Normal file
44
klp-ui/src/api/crm/contractProduct.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询合同产品明细列表
|
||||
export function listContractProduct(query) {
|
||||
return request({
|
||||
url: '/crm/contractProduct/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询合同产品明细详细
|
||||
export function getContractProduct(contractProductId) {
|
||||
return request({
|
||||
url: '/crm/contractProduct/' + contractProductId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增合同产品明细
|
||||
export function addContractProduct(data) {
|
||||
return request({
|
||||
url: '/crm/contractProduct',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改合同产品明细
|
||||
export function updateContractProduct(data) {
|
||||
return request({
|
||||
url: '/crm/contractProduct',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除合同产品明细
|
||||
export function delContractProduct(contractProductId) {
|
||||
return request({
|
||||
url: '/crm/contractProduct/' + contractProductId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<KLPTable v-loading="loading" :data="data" :floatLayer="true" :floatLayerConfig="floatLayerConfig">
|
||||
<KLPTable :data="data" :floatLayer="true" :floatLayerConfig="floatLayerConfig">
|
||||
<el-table-column label="入场钢卷号" align="center" prop="enterCoilNo">
|
||||
<template slot-scope="scope">
|
||||
<coil-no :coil-no="scope.row.enterCoilNo"></coil-no>
|
||||
|
||||
@@ -3,32 +3,25 @@
|
||||
<!-- 筛选区和按钮操作区合并 -->
|
||||
<div class="filter-section" style="padding: 10px; border-bottom: 1px solid #e4e7ed;">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;">
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<el-input
|
||||
v-model="queryParams.contractName"
|
||||
placeholder="请输入合同名称"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
style="width: 200px;"
|
||||
/>
|
||||
<el-button
|
||||
icon="el-icon-search"
|
||||
size="mini"
|
||||
@click="toggleMoreFilter"
|
||||
:type="showMoreFilter ? 'primary' : 'default'"
|
||||
>
|
||||
{{ showMoreFilter ? '收起' : '更多' }}
|
||||
<div style="display: flex; align-items: center; gap: 4px;">
|
||||
<el-input v-model="queryParams.contractName" placeholder="请输入合同名称" clearable @keyup.enter.native="handleQuery"
|
||||
style="width: 200px;" />
|
||||
<el-button icon="el-icon-sort" size="mini" @click="toggleMoreFilter"
|
||||
:type="showMoreFilter ? 'primary' : 'default'">
|
||||
<!-- {{ showMoreFilter ? '收起' : '更多' }} -->
|
||||
</el-button>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="$emit('add')">新增</el-button>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
|
||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="$emit('add')"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 更多筛选条件 -->
|
||||
<div v-show="showMoreFilter" class="more-filter" style="margin-top: 10px; padding-top: 10px; border-top: 1px dashed #e4e7ed;">
|
||||
<div v-show="showMoreFilter" class="more-filter"
|
||||
style="margin-top: 10px; padding-top: 10px; border-top: 1px dashed #e4e7ed;">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="80px">
|
||||
<el-form-item label="合同编号" prop="contractNo">
|
||||
<el-input v-model="queryParams.contractNo" placeholder="请输入合同编号" clearable @keyup.enter.native="handleQuery" />
|
||||
<el-input v-model="queryParams.contractNo" placeholder="请输入合同编号" clearable
|
||||
@keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="供方" prop="supplier">
|
||||
<el-input v-model="queryParams.supplier" placeholder="请输入供方" clearable @keyup.enter.native="handleQuery" />
|
||||
@@ -67,32 +60,27 @@
|
||||
|
||||
<div class="custom-list" v-loading="loading">
|
||||
<div class="list-body">
|
||||
<div
|
||||
v-for="row in contractList"
|
||||
:key="row.contractId"
|
||||
class="list-item"
|
||||
<div v-for="row in contractList" :key="row.contractId" class="list-item"
|
||||
style="padding: 10px; border-bottom: 2px solid #dddddd; cursor: pointer;"
|
||||
:class="{ 'list-item-active': selectedRow === row }"
|
||||
@click="handleRowClick(row)"
|
||||
>
|
||||
:class="{ 'list-item-active': selectedRow === row }" @click="handleRowClick(row)">
|
||||
<!-- 合同名称和编号 -->
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
||||
<div style="font-weight: bold;">{{ row.contractName }}</div>
|
||||
<div style="font-size: 12px; color: #606266;">{{ row.contractNo }}</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 供方和需方 -->
|
||||
<div style="font-size: 12px; color: #909399; margin-bottom: 6px;">
|
||||
<span>供方: {{ row.supplier }}</span>
|
||||
<span style="margin-left: 20px;">需方: {{ row.customer }}</span>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 签订时间和交货日期 -->
|
||||
<div style="font-size: 12px; color: #909399; margin-bottom: 6px;">
|
||||
<span>签订时间: {{ parseTime(row.signTime, '{y}-{m}-{d}') }}</span>
|
||||
<span style="margin-left: 20px;">交货日期: {{ parseTime(row.deliveryDate, '{y}-{m}-{d}') }}</span>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 签订地点和状态 -->
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
||||
<div style="font-size: 12px; color: #909399;">
|
||||
@@ -104,11 +92,10 @@
|
||||
{{ row.status == 0 ? '草稿' : row.status == 1 ? '已生效' : row.status == 2 ? '已作废' : '已完成' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 操作按钮独占一行 -->
|
||||
<div style="display: flex; gap: 10px; padding-top: 8px; border-top: 1px dashed #f0f0f0;">
|
||||
<el-button size="mini" type="text" icon="el-icon-download"
|
||||
@click.stop="$emit('exportContract', row)">导出</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-download" @click.stop="handleExport(row)">导出</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-edit" @click.stop="$emit('update', row)">修改</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-delete" @click.stop="$emit('delete', row)">删除</el-button>
|
||||
</div>
|
||||
@@ -126,6 +113,8 @@
|
||||
|
||||
<script>
|
||||
import { listContract, updateContract } from "@/api/crm/contract";
|
||||
import * as ExcelJS from 'exceljs';
|
||||
import { saveAs } from 'file-saver';
|
||||
|
||||
export default {
|
||||
name: "ContractList",
|
||||
@@ -198,7 +187,492 @@ export default {
|
||||
// 切换更多筛选显示/隐藏
|
||||
toggleMoreFilter() {
|
||||
this.showMoreFilter = !this.showMoreFilter;
|
||||
}
|
||||
},
|
||||
/** 导出合同 */
|
||||
async handleExport(row) {
|
||||
// 1. 创建excel
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
const worksheet = workbook.addWorksheet('产品销售合同');
|
||||
|
||||
// 2. 设置列宽
|
||||
worksheet.columns = [
|
||||
{ width: 10 },
|
||||
{ width: 20 },
|
||||
{ width: 15 },
|
||||
{ width: 15 },
|
||||
{ width: 15 },
|
||||
{ width: 15 },
|
||||
{ width: 15 },
|
||||
{ width: 20 }
|
||||
];
|
||||
|
||||
// 3. 合并单元格并设置内容
|
||||
// 公司信息
|
||||
worksheet.mergeCells('A1:H1');
|
||||
worksheet.getCell('A1').value = '嘉祥科伦普重工有限公司';
|
||||
worksheet.getCell('A1').font = { size: 16, bold: true };
|
||||
worksheet.getCell('A1').alignment = { horizontal: 'center', vertical: 'middle' };
|
||||
|
||||
// 合同标题
|
||||
worksheet.mergeCells('A2:F2');
|
||||
worksheet.getCell('A2').value = '产品销售合同';
|
||||
worksheet.getCell('A2').font = { size: 18, bold: true };
|
||||
worksheet.getCell('A2').alignment = { horizontal: 'center', vertical: 'middle' };
|
||||
|
||||
// 合同编号
|
||||
worksheet.mergeCells('G2:H2');
|
||||
worksheet.getCell('G2').value = `合同编号:${row.contractNo || ''}`;
|
||||
worksheet.getCell('G2').alignment = { horizontal: 'right', vertical: 'middle' };
|
||||
|
||||
// 供方信息
|
||||
worksheet.getCell('A3').value = `供方(甲方):${row.supplier || '嘉祥科伦普重工有限公司'}`;
|
||||
worksheet.getCell('A3').alignment = { horizontal: 'left', vertical: 'middle' };
|
||||
|
||||
// 签订时间
|
||||
worksheet.getCell('E3').value = `签订时间:${row.signTime ? this.parseTime(row.signTime, '{y}年{m}月{d}日') : '2026年 月 日'}`;
|
||||
worksheet.getCell('E3').alignment = { horizontal: 'left', vertical: 'middle' };
|
||||
|
||||
// 需方信息
|
||||
worksheet.getCell('A4').value = `需方(乙方):${row.customer || ''}`;
|
||||
worksheet.getCell('A4').alignment = { horizontal: 'left', vertical: 'middle' };
|
||||
|
||||
// 签订地点
|
||||
worksheet.getCell('E4').value = `签订地点:${row.signLocation || '山东省济宁市嘉祥县'}`;
|
||||
worksheet.getCell('E4').alignment = { horizontal: 'left', vertical: 'middle' };
|
||||
|
||||
// 产品内容标题
|
||||
worksheet.getCell('A5').value = '一、产品内容';
|
||||
worksheet.getCell('A5').font = { bold: true };
|
||||
|
||||
// 产品名称和生产厂家
|
||||
worksheet.mergeCells('A6:C6');
|
||||
worksheet.getCell('A6').value = `产品名称:${row.productName || '冷硬钢卷'}`;
|
||||
worksheet.getCell('A6').alignment = { horizontal: 'left', vertical: 'middle' };
|
||||
worksheet.getCell('A6').border = {
|
||||
top: { style: 'thin' },
|
||||
left: { style: 'thin' },
|
||||
bottom: { style: 'thin' },
|
||||
right: { style: 'thin' }
|
||||
};
|
||||
|
||||
|
||||
worksheet.mergeCells('D6:H6');
|
||||
worksheet.getCell('D6').value = `生产厂家:${row.manufacturer || '嘉祥科伦普重工有限公司'}`;
|
||||
worksheet.getCell('D6').alignment = { horizontal: 'left', vertical: 'middle' };
|
||||
worksheet.getCell('D6').border = {
|
||||
top: { style: 'thin' },
|
||||
left: { style: 'thin' },
|
||||
bottom: { style: 'thin' },
|
||||
right: { style: 'thin' }
|
||||
};
|
||||
|
||||
// 产品表格标题(分为两行)
|
||||
// 合并序号、规格、材质、备注列的单元格(两行)
|
||||
const mergedHeaders = ['序号', '规格(mm)', '材质', '备注'];
|
||||
const mergedColumns = [1, 2, 3, 8];
|
||||
|
||||
mergedColumns.forEach((col, index) => {
|
||||
// 合并两行
|
||||
worksheet.mergeCells(7, col, 8, col);
|
||||
const cell = worksheet.getCell(7, col);
|
||||
cell.value = mergedHeaders[index];
|
||||
cell.font = { bold: true };
|
||||
cell.fill = { type: 'pattern', pattern: 'solid' };
|
||||
cell.border = {
|
||||
top: { style: 'thin' },
|
||||
left: { style: 'thin' },
|
||||
bottom: { style: 'thin' },
|
||||
right: { style: 'thin' }
|
||||
};
|
||||
cell.alignment = { horizontal: 'center', vertical: 'middle' };
|
||||
});
|
||||
|
||||
// 数量、含税单价、不含税单价、含税总额的标题和单位各占一行
|
||||
const quantityHeaders = ['数量', '(吨)'];
|
||||
const taxPriceHeaders = ['含税单价', '(元/吨)'];
|
||||
const noTaxPriceHeaders = ['不含税单价', '(元/吨)'];
|
||||
const taxTotalHeaders = ['含税总额', '(元)'];
|
||||
|
||||
// 数量列
|
||||
worksheet.getCell(7, 4).value = quantityHeaders[0];
|
||||
worksheet.getCell(8, 4).value = quantityHeaders[1];
|
||||
|
||||
// 含税单价列
|
||||
worksheet.getCell(7, 5).value = taxPriceHeaders[0];
|
||||
worksheet.getCell(8, 5).value = taxPriceHeaders[1];
|
||||
|
||||
// 不含税单价列
|
||||
worksheet.getCell(7, 6).value = noTaxPriceHeaders[0];
|
||||
worksheet.getCell(8, 6).value = noTaxPriceHeaders[1];
|
||||
|
||||
// 含税总额列
|
||||
worksheet.getCell(7, 7).value = taxTotalHeaders[0];
|
||||
worksheet.getCell(8, 7).value = taxTotalHeaders[1];
|
||||
|
||||
// 设置样式
|
||||
for (let row = 7; row <= 8; row++) {
|
||||
for (let col = 4; col <= 7; col++) {
|
||||
const cell = worksheet.getCell(row, col);
|
||||
cell.font = { bold: true };
|
||||
cell.fill = { type: 'pattern', pattern: 'solid' };
|
||||
cell.border = {
|
||||
top: { style: 'thin' },
|
||||
left: { style: 'thin' },
|
||||
bottom: { style: 'thin' },
|
||||
right: { style: 'thin' }
|
||||
};
|
||||
cell.alignment = { horizontal: 'center', vertical: 'middle' };
|
||||
}
|
||||
}
|
||||
|
||||
// 产品表格数据, 来源于一个json字符串(productContent,json结构参考ProductContent.vue)
|
||||
// 解析产品内容
|
||||
let productData = {
|
||||
products: [],
|
||||
productName: row.productName || '冷硬钢卷',
|
||||
remark: '',
|
||||
totalQuantity: 0,
|
||||
totalTaxTotal: 0,
|
||||
totalAmountInWords: '零元整'
|
||||
};
|
||||
|
||||
if (row.productContent) {
|
||||
try {
|
||||
productData = JSON.parse(row.productContent);
|
||||
} catch (error) {
|
||||
console.error('解析产品内容失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新产品名称
|
||||
worksheet.getCell('A6').value = `产品名称:${productData.productName}`;
|
||||
|
||||
// 产品表格数据
|
||||
let currentRow = 9;
|
||||
let totalQuantity = 0;
|
||||
let totalTaxTotal = 0;
|
||||
|
||||
if (productData.products && productData.products.length > 0) {
|
||||
productData.products.forEach((product, index) => {
|
||||
const rowNum = currentRow + index;
|
||||
worksheet.getCell(`A${rowNum}`).value = index + 1;
|
||||
worksheet.getCell(`B${rowNum}`).value = product.spec || '';
|
||||
worksheet.getCell(`C${rowNum}`).value = product.material || '';
|
||||
worksheet.getCell(`D${rowNum}`).value = product.quantity || 0;
|
||||
worksheet.getCell(`E${rowNum}`).value = product.taxPrice || 0;
|
||||
worksheet.getCell(`F${rowNum}`).value = product.noTaxPrice || 0;
|
||||
worksheet.getCell(`G${rowNum}`).value = product.taxTotal || 0;
|
||||
worksheet.getCell(`H${rowNum}`).value = product.remark || '';
|
||||
|
||||
// 设置数据行边框
|
||||
for (let i = 1; i <= 8; i++) {
|
||||
const cell = worksheet.getCell(rowNum, i);
|
||||
cell.border = {
|
||||
top: { style: 'thin' },
|
||||
left: { style: 'thin' },
|
||||
bottom: { style: 'thin' },
|
||||
right: { style: 'thin' }
|
||||
};
|
||||
cell.alignment = { horizontal: 'center', vertical: 'middle' };
|
||||
}
|
||||
|
||||
// 累加合计
|
||||
totalQuantity += parseFloat(product.quantity || 0);
|
||||
totalTaxTotal += parseFloat(product.taxTotal || 0);
|
||||
});
|
||||
|
||||
currentRow += productData.products.length;
|
||||
} else {
|
||||
// 至少一行空数据
|
||||
worksheet.getCell(`A${currentRow}`).value = 1;
|
||||
worksheet.getCell(`B${currentRow}`).value = '';
|
||||
worksheet.getCell(`C${currentRow}`).value = 'SPCC';
|
||||
worksheet.getCell(`D${currentRow}`).value = '';
|
||||
worksheet.getCell(`E${currentRow}`).value = '';
|
||||
worksheet.getCell(`F${currentRow}`).value = 0.00;
|
||||
worksheet.getCell(`G${currentRow}`).value = 0;
|
||||
worksheet.getCell(`H${currentRow}`).value = '';
|
||||
|
||||
// 设置数据行边框
|
||||
for (let i = 1; i <= 8; i++) {
|
||||
const cell = worksheet.getCell(currentRow, i);
|
||||
cell.border = {
|
||||
top: { style: 'thin' },
|
||||
left: { style: 'thin' },
|
||||
bottom: { style: 'thin' },
|
||||
right: { style: 'thin' }
|
||||
};
|
||||
cell.alignment = { horizontal: 'center', vertical: 'middle' };
|
||||
}
|
||||
currentRow++;
|
||||
}
|
||||
|
||||
// 合计行
|
||||
worksheet.getCell(`A${currentRow}`).value = '合 计';
|
||||
worksheet.getCell(`A${currentRow}`).font = { bold: true };
|
||||
worksheet.getCell(`A${currentRow}`).border = {
|
||||
top: { style: 'thin' },
|
||||
left: { style: 'thin' },
|
||||
bottom: { style: 'thin' },
|
||||
right: { style: 'thin' }
|
||||
};
|
||||
worksheet.getCell(`A${currentRow}`).alignment = { horizontal: 'center', vertical: 'middle' };
|
||||
|
||||
worksheet.getCell(`D${currentRow}`).value = productData.totalQuantity || totalQuantity;
|
||||
worksheet.getCell(`D${currentRow}`).border = {
|
||||
top: { style: 'thin' },
|
||||
left: { style: 'thin' },
|
||||
bottom: { style: 'thin' },
|
||||
right: { style: 'thin' }
|
||||
};
|
||||
worksheet.getCell(`D${currentRow}`).alignment = { horizontal: 'center', vertical: 'middle' };
|
||||
|
||||
worksheet.getCell(`G${currentRow}`).value = productData.totalTaxTotal || totalTaxTotal;
|
||||
worksheet.getCell(`G${currentRow}`).border = {
|
||||
top: { style: 'thin' },
|
||||
left: { style: 'thin' },
|
||||
bottom: { style: 'thin' },
|
||||
right: { style: 'thin' }
|
||||
};
|
||||
worksheet.getCell(`G${currentRow}`).alignment = { horizontal: 'center', vertical: 'middle' };
|
||||
|
||||
worksheet.getCell(`H${currentRow}`).value = '';
|
||||
worksheet.getCell(`H${currentRow}`).border = {
|
||||
top: { style: 'thin' },
|
||||
left: { style: 'thin' },
|
||||
bottom: { style: 'thin' },
|
||||
right: { style: 'thin' }
|
||||
};
|
||||
worksheet.getCell(`H${currentRow}`).alignment = { horizontal: 'center', vertical: 'middle' };
|
||||
|
||||
currentRow++;
|
||||
|
||||
// 大写金额
|
||||
worksheet.mergeCells(`A${currentRow}:B${currentRow}`);
|
||||
worksheet.getCell(`A${currentRow}`).value = `合计人民币(大写)`;
|
||||
worksheet.getCell(`A${currentRow}`).font = { bold: true };
|
||||
worksheet.getCell(`A${currentRow}`).border = {
|
||||
top: { style: 'thin' },
|
||||
left: { style: 'thin' },
|
||||
bottom: { style: 'thin' },
|
||||
right: { style: 'thin' }
|
||||
};
|
||||
worksheet.getCell(`A${currentRow}`).alignment = { horizontal: 'left', vertical: 'middle' };
|
||||
|
||||
worksheet.mergeCells(`C${currentRow}:H${currentRow}`);
|
||||
worksheet.getCell(`C${currentRow}`).value = `${productData.totalAmountInWords || row.totalAmountUpper || '零元整'}`;
|
||||
worksheet.getCell(`C${currentRow}`).font = { bold: true };
|
||||
worksheet.getCell(`C${currentRow}`).border = {
|
||||
top: { style: 'thin' },
|
||||
left: { style: 'thin' },
|
||||
bottom: { style: 'thin' },
|
||||
right: { style: 'thin' }
|
||||
};
|
||||
worksheet.getCell(`C${currentRow}`).alignment = { horizontal: 'left', vertical: 'middle' };
|
||||
|
||||
currentRow++;
|
||||
|
||||
// 备注行
|
||||
worksheet.mergeCells(`B${currentRow}:H${currentRow}`);
|
||||
worksheet.getCell(`A${currentRow}`).value = '备注:';
|
||||
worksheet.getCell(`A${currentRow}`).alignment = { horizontal: 'left', vertical: 'top' };
|
||||
worksheet.getCell(`A${currentRow}`).border = {
|
||||
top: { style: 'thin' },
|
||||
left: { style: 'thin' },
|
||||
bottom: { style: 'thin' },
|
||||
right: { style: 'thin' }
|
||||
};
|
||||
|
||||
worksheet.getCell(`B${currentRow}`).alignment = { horizontal: 'left', vertical: 'top' };
|
||||
worksheet.getCell(`B${currentRow}`).value = productData.remark || '';
|
||||
worksheet.getCell(`B${currentRow}`).border = {
|
||||
top: { style: 'thin' },
|
||||
left: { style: 'thin' },
|
||||
bottom: { style: 'thin' },
|
||||
right: { style: 'thin' }
|
||||
};
|
||||
worksheet.getCell(`B${currentRow}`).alignment = { horizontal: 'left', vertical: 'top', wrapText: true };
|
||||
|
||||
currentRow++;
|
||||
|
||||
// 空白行(保持至少5行空白)
|
||||
// const emptyRows = Math.max(5, 15 - currentRow + 1);
|
||||
// for (let i = 0; i < emptyRows; i++) {
|
||||
// const rowNum = currentRow + i;
|
||||
// for (let j = 1; j <= 8; j++) {
|
||||
// const cell = worksheet.getCell(rowNum, j);
|
||||
// cell.border = {
|
||||
// top: { style: 'thin' },
|
||||
// left: { style: 'thin' },
|
||||
// bottom: { style: 'thin' },
|
||||
// right: { style: 'thin' }
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
|
||||
// currentRow += emptyRows;
|
||||
|
||||
|
||||
|
||||
// 产品表格之后是其他合同内容(contractContent字段,存储的是HTML格式的富文本)
|
||||
if (row.contractContent) {
|
||||
// 优化HTML处理,保留p标签作为换行符,将多个p标签单元格合并
|
||||
let htmlContent = row.contractContent;
|
||||
|
||||
// 提取所有p标签内容
|
||||
const pTagRegex = /<p[^>]*>([\s\S]*?)<\/p>/g;
|
||||
let match;
|
||||
const pContents = [];
|
||||
|
||||
while ((match = pTagRegex.exec(htmlContent)) !== null) {
|
||||
let content = match[1];
|
||||
|
||||
// 移除其他HTML标签
|
||||
content = content.replace(/<[^>]*>/g, '');
|
||||
|
||||
// 处理HTML实体
|
||||
content = content.replace(/ /g, ' ');
|
||||
content = content.replace(/</g, '<');
|
||||
content = content.replace(/>/g, '>');
|
||||
content = content.replace(/&/g, '&');
|
||||
content = content.replace(/"/g, '"');
|
||||
content = content.replace(/'/g, "'");
|
||||
|
||||
// 清理空格和换行
|
||||
content = content.trim();
|
||||
|
||||
if (content) {
|
||||
pContents.push(content);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有提取到p标签内容,尝试提取所有文本内容
|
||||
if (pContents.length === 0) {
|
||||
let textContent = htmlContent.replace(/<[^>]*>/g, '');
|
||||
textContent = textContent.replace(/ /g, ' ').trim();
|
||||
|
||||
if (textContent) {
|
||||
pContents.push(textContent);
|
||||
}
|
||||
}
|
||||
|
||||
// 直接合并单元格并设置内容,避免合并已合并的单元格
|
||||
if (pContents.length > 0) {
|
||||
// 计算需要的行数
|
||||
const contentLines = pContents.reduce((total, content) => {
|
||||
return total + (content.match(/\n/g) || []).length + 1;
|
||||
}, 0);
|
||||
|
||||
// 合并单元格
|
||||
const endRow = currentRow + Math.ceil(contentLines / 2); // 估算需要的行数
|
||||
worksheet.mergeCells(`A${currentRow}:H${endRow}`);
|
||||
|
||||
// 设置合并后单元格的内容和样式
|
||||
const mergedContent = pContents.join('\n');
|
||||
worksheet.getCell(`A${currentRow}`).value = mergedContent;
|
||||
worksheet.getCell(`A${currentRow}`).alignment = { horizontal: 'left', vertical: 'top', wrapText: true };
|
||||
|
||||
// 调整合并后单元格的行高
|
||||
const lineCount = (mergedContent.match(/\n/g) || []).length + 1;
|
||||
const height = Math.max(60, lineCount * 15);
|
||||
worksheet.getRow(currentRow).height = height;
|
||||
|
||||
// 调整currentRow
|
||||
currentRow = endRow + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 设置合同尾部信息,每一行的前四列与后四列分别合并
|
||||
// 内容参考
|
||||
|
||||
// 设置合同尾部信息,每一行的前四列与后四列分别合并
|
||||
// 内容参考ContractPreview.vue
|
||||
if (currentRow > 0) {
|
||||
// 空行
|
||||
currentRow++;
|
||||
|
||||
// 供方(甲方)信息
|
||||
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
|
||||
worksheet.getCell(`A${currentRow}`).value = `供方(甲方):${row.supplier || '嘉祥科伦普重工有限公司'}`;
|
||||
worksheet.getCell(`A${currentRow}`).alignment = { horizontal: 'left', vertical: 'middle' };
|
||||
|
||||
worksheet.mergeCells(`E${currentRow}:H${currentRow}`);
|
||||
worksheet.getCell(`E${currentRow}`).value = `需方(乙方):${row.customer || ''}`;
|
||||
worksheet.getCell(`E${currentRow}`).alignment = { horizontal: 'left', vertical: 'middle' };
|
||||
currentRow++;
|
||||
|
||||
// 地址信息
|
||||
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
|
||||
worksheet.getCell(`A${currentRow}`).value = `地址:${row.supplierAddress || ''}`;
|
||||
worksheet.getCell(`A${currentRow}`).alignment = { horizontal: 'left', vertical: 'middle' };
|
||||
|
||||
worksheet.mergeCells(`E${currentRow}:H${currentRow}`);
|
||||
worksheet.getCell(`E${currentRow}`).value = `地址:${row.customerAddress || ''}`;
|
||||
worksheet.getCell(`E${currentRow}`).alignment = { horizontal: 'left', vertical: 'middle' };
|
||||
currentRow++;
|
||||
|
||||
// 电话信息
|
||||
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
|
||||
worksheet.getCell(`A${currentRow}`).value = `电话:${row.supplierPhone || ''}`;
|
||||
worksheet.getCell(`A${currentRow}`).alignment = { horizontal: 'left', vertical: 'middle' };
|
||||
|
||||
worksheet.mergeCells(`E${currentRow}:H${currentRow}`);
|
||||
worksheet.getCell(`E${currentRow}`).value = `电话:${row.customerPhone || ''}`;
|
||||
worksheet.getCell(`E${currentRow}`).alignment = { horizontal: 'left', vertical: 'middle' };
|
||||
currentRow++;
|
||||
|
||||
// 开户行信息
|
||||
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
|
||||
worksheet.getCell(`A${currentRow}`).value = `开户行:${row.supplierBank || ''}`;
|
||||
worksheet.getCell(`A${currentRow}`).alignment = { horizontal: 'left', vertical: 'middle' };
|
||||
|
||||
worksheet.mergeCells(`E${currentRow}:H${currentRow}`);
|
||||
worksheet.getCell(`E${currentRow}`).value = `开户行:${row.customerBank || ''}`;
|
||||
worksheet.getCell(`E${currentRow}`).alignment = { horizontal: 'left', vertical: 'middle' };
|
||||
currentRow++;
|
||||
|
||||
// 账号信息
|
||||
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
|
||||
worksheet.getCell(`A${currentRow}`).value = `账号:${row.supplierAccount || ''}`;
|
||||
worksheet.getCell(`A${currentRow}`).alignment = { horizontal: 'left', vertical: 'middle' };
|
||||
|
||||
worksheet.mergeCells(`E${currentRow}:H${currentRow}`);
|
||||
worksheet.getCell(`E${currentRow}`).value = `账号:${row.customerAccount || ''}`;
|
||||
worksheet.getCell(`E${currentRow}`).alignment = { horizontal: 'left', vertical: 'middle' };
|
||||
currentRow++;
|
||||
|
||||
// 税号信息
|
||||
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
|
||||
worksheet.getCell(`A${currentRow}`).value = `税号:${row.supplierTaxNo || ''}`;
|
||||
worksheet.getCell(`A${currentRow}`).alignment = { horizontal: 'left', vertical: 'middle' };
|
||||
|
||||
worksheet.mergeCells(`E${currentRow}:H${currentRow}`);
|
||||
worksheet.getCell(`E${currentRow}`).value = `税号:${row.customerTaxNo || ''}`;
|
||||
worksheet.getCell(`E${currentRow}`).alignment = { horizontal: 'left', vertical: 'middle' };
|
||||
currentRow++;
|
||||
}
|
||||
|
||||
// 4. 设置行高
|
||||
worksheet.getRow(1).height = 40;
|
||||
worksheet.getRow(2).height = 30;
|
||||
worksheet.getRow(3).height = 20;
|
||||
worksheet.getRow(4).height = 20;
|
||||
worksheet.getRow(5).height = 20;
|
||||
worksheet.getRow(6).height = 20;
|
||||
worksheet.getRow(7).height = 25;
|
||||
|
||||
// 自动调整数据行高
|
||||
for (let i = 8; i <= currentRow; i++) {
|
||||
if (!worksheet.getRow(i).height) {
|
||||
worksheet.getRow(i).height = 20;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 导出文件
|
||||
const buffer = await workbook.xlsx.writeBuffer();
|
||||
const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
||||
saveAs(blob, `合同_${row.contractNo || row.contractName || '未命名'}.xlsx`);
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -228,11 +702,11 @@ export default {
|
||||
.list-header {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
||||
.list-item {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
||||
.list-item .el-button {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
@@ -26,14 +26,32 @@
|
||||
</el-descriptions>
|
||||
|
||||
<div style="margin-top: 20px;">
|
||||
<h4 style="margin-bottom: 10px; color: #606266;">产品内容</h4>
|
||||
<div v-html="contract.productContent" style="border: 1px solid #e4e7ed; padding: 10px; border-radius: 4px;"></div>
|
||||
<h4 style="margin-bottom: 10px; color: #606266;">一、产品内容</h4>
|
||||
<ProductContent v-model="contract.productContent" readonly />
|
||||
<!-- <div v-html="contract.productContent" style="border: 1px solid #e4e7ed; padding: 10px; border-radius: 4px;"></div> -->
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 20px;">
|
||||
<h4 style="margin-bottom: 10px; color: #606266;">合同内容</h4>
|
||||
<div>
|
||||
<!-- <h4 style="margin-bottom: 10px; color: #606266;">合同内容</h4> -->
|
||||
<div v-html="contract.contractContent" style="border: 1px solid #e4e7ed; padding: 10px; border-radius: 4px;"></div>
|
||||
</div>
|
||||
|
||||
<div style="border: 1px solid #e4e7ed; padding: 10px; border-radius: 4px;">
|
||||
<el-descriptions :column="2">
|
||||
<el-descriptions-item label="供方(甲方)">{{ contract.supplier }}</el-descriptions-item>
|
||||
<el-descriptions-item label="需方(乙方)">{{ contract.customer }}</el-descriptions-item>
|
||||
<el-descriptions-item label="地址">{{ contract.supplierAddress }}</el-descriptions-item>
|
||||
<el-descriptions-item label="地址">{{ contract.customerAddress }}</el-descriptions-item>
|
||||
<el-descriptions-item label="电话">{{ contract.supplierPhone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="电话">{{ contract.customerPhone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="开户行">{{ contract.supplierBank }}</el-descriptions-item>
|
||||
<el-descriptions-item label="开户行">{{ contract.customerBank }}</el-descriptions-item>
|
||||
<el-descriptions-item label="账号">{{ contract.supplierAccount }}</el-descriptions-item>
|
||||
<el-descriptions-item label="账号">{{ contract.customerAccount }}</el-descriptions-item>
|
||||
<el-descriptions-item label="税号">{{ contract.supplierTaxNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="税号">{{ contract.customerTaxNo }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-selection" style="display: flex; align-items: center; justify-content: center; height: 100%;">
|
||||
<el-empty description="请先选择合同" />
|
||||
@@ -42,10 +60,13 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { updateContract } from '@/api/crm/contract';
|
||||
import ProductContent from './ProductContent.vue';
|
||||
|
||||
export default {
|
||||
name: "ContractPreview",
|
||||
components: {
|
||||
ProductContent
|
||||
},
|
||||
props: {
|
||||
contract: {
|
||||
type: Object,
|
||||
|
||||
@@ -58,7 +58,7 @@ export default {
|
||||
},
|
||||
props: {
|
||||
contractId: {
|
||||
type: Number,
|
||||
type: [Number, String],
|
||||
default: null
|
||||
},
|
||||
financeList: {
|
||||
|
||||
430
klp-ui/src/views/crm/contract/components/ProductContent.vue
Normal file
430
klp-ui/src/views/crm/contract/components/ProductContent.vue
Normal file
@@ -0,0 +1,430 @@
|
||||
<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 + (item.quantity || 0);
|
||||
}, 0);
|
||||
},
|
||||
// 计算总含税总额
|
||||
totalTaxTotal() {
|
||||
return this.products.reduce((total, item) => {
|
||||
return total + (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) {
|
||||
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>
|
||||
@@ -69,7 +69,7 @@
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="产品内容">
|
||||
<editor v-model="form.productContent" :min-height="192" />
|
||||
<ProductContent v-model="form.productContent" :readonly="false" />
|
||||
</el-form-item>
|
||||
<el-form-item label="合同内容">
|
||||
<editor v-model="form.contractContent" :min-height="192" />
|
||||
@@ -150,13 +150,15 @@ import { getContract, delContract, addContract, updateContract, listContractOrde
|
||||
import ContractList from "./components/ContractList.vue";
|
||||
import ContractPreview from "./components/ContractPreview.vue";
|
||||
import ContractTabs from "./components/ContractTabs.vue";
|
||||
import ProductContent from "./components/ProductContent.vue";
|
||||
|
||||
export default {
|
||||
name: "Contract",
|
||||
components: {
|
||||
ContractList,
|
||||
ContractPreview,
|
||||
ContractTabs
|
||||
ContractTabs,
|
||||
ProductContent
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -300,20 +302,78 @@ export default {
|
||||
reset() {
|
||||
this.form = {
|
||||
contractId: undefined,
|
||||
contractName: undefined,
|
||||
contractName: '产品销售合同',
|
||||
contractNo: undefined,
|
||||
supplier: undefined,
|
||||
supplier: '嘉祥科伦普重工有限公司',
|
||||
customer: undefined,
|
||||
signTime: undefined,
|
||||
deliveryDate: undefined,
|
||||
signLocation: undefined,
|
||||
productContent: undefined,
|
||||
contractContent: undefined,
|
||||
supplierAddress: undefined,
|
||||
supplierPhone: undefined,
|
||||
supplierBank: undefined,
|
||||
supplierAccount: undefined,
|
||||
supplierTaxNo: undefined,
|
||||
contractContent: `二、交(提)货方式:
|
||||
|
||||
◆交(提)货时间:自 2026年 月 日起至 2026年 月 日止
|
||||
|
||||
◆交(提)货地点:供方所在地仓库
|
||||
|
||||
◆交(提)货方式:需方委托供方代办运输。
|
||||
|
||||
◆货物所有权自出供方厂区时转移,但需方未履行支付全部价款义务的,供方可以留置全部待发货物且所有权仍归供方所有。
|
||||
|
||||
◆交(提)货公差:实际交货总重量不超出合同约定总重量的±10%。
|
||||
|
||||
◆交(提)货计量:以供方出厂计量为准,磅差不超过±3‰。
|
||||
|
||||
三、装车费用、运输费用和其他费用的分担:
|
||||
|
||||
◆委托供方代办运输:供方仓库的装车费用由供方承担,运输费用由需方承担。
|
||||
|
||||
四、技术要求、包装标准:
|
||||
|
||||
◆无特殊需求时,按供方厂家包装标准执行,包装物不回收。
|
||||
|
||||
◆未提及技术要求或超出相关规格标准的,按供方工厂现行标准执行。
|
||||
|
||||
五、对产品提出质量异议的期限和方法:
|
||||
|
||||
◆提出期限:需方提货之日起10日内向供方书面提出,并将产品封存(不包括提货日)。
|
||||
|
||||
◆提出方式:需方使用前须检查产品质量,发现质量问题需妥善保管异议产品,书面通知供方,并提供供方所需资料。经供方质量部现场确认后,对未使用的全新产品有质量问题的可换货,对使用中发现质量问题的产品须保持问题产品的原状,供方仅对有质量问题的单个钢卷产品参考供方正品和B级的价差予以补偿,供方对其包括但不限于可期得利益、下家客户损失、商誉损失等均不承担责任。
|
||||
|
||||
◆不接受异议的情况:
|
||||
|
||||
1)逾期反馈、运输不当、保管不善造成质量问题,产品非正品、需方加工不当所产生的问题。
|
||||
|
||||
2)因需方要求不包装,合同中供方对价格进行了折让,需方已知晓该产品不包装会产生包括但不限于氧化、变形、吊伤、擦伤、划伤、碰伤等质量问题的风险,需方仍要求不包装,基于以上原因,需方同意供方对包括但不限于氧化、变形、吊伤、擦伤、划伤、碰伤等质量问题不承担任何责任。
|
||||
|
||||
六、结算及付款:
|
||||
|
||||
◆本合同为锁价合同,合同单价为锁定现汇含税出厂价价格。
|
||||
|
||||
◆自合同签订之日(含)起1个工作日内需方预付全部货款,否则供方有权单方解除合同
|
||||
|
||||
七、违约责任:按《中华人民共和国民法典》有关规定。
|
||||
|
||||
◆需方未按时付款,应按未付款的日万分之五承担逾期付款违约金;
|
||||
|
||||
◆需方中途解除合同的按合同价款50%承担违约金;需方拒绝接收货物,产生的损失由需方承担;
|
||||
|
||||
◆需方如错填到货地点或接货人,应承担供方因此所受的损失。
|
||||
|
||||
八、不可抗力和解决合同纠纷的方式:
|
||||
|
||||
◆双方的任何一方由于不可抗力的原因不能履行合同时,应及时向对方通报不能履行或不能完全履行的理由,在取得有关主管机关证明以后,允许延期履行、部分履行或者不履行合同,并根据情况可部分或全部免予承担违约责任。
|
||||
|
||||
◆协商解决,如协商不成,双方可向供方所在地人民法院起诉。
|
||||
|
||||
九、本合同自双方盖章并预付全部货款生效,传真件和扫描件与原件具同等法律效力,手改无效。
|
||||
|
||||
十、其他事项及说明: `,
|
||||
supplierAddress: '山东省济宁市嘉祥县经济开发区生物产业园新民路北延',
|
||||
supplierPhone: '0537-6625069',
|
||||
supplierBank: '中国建设银行山东省济宁市嘉祥县华府支行',
|
||||
supplierAccount: '3705 0168 6408 0000 1169',
|
||||
supplierTaxNo: '91370829MA3FCC3C1F',
|
||||
customerAddress: undefined,
|
||||
customerPhone: undefined,
|
||||
customerBank: undefined,
|
||||
|
||||
@@ -9,6 +9,20 @@
|
||||
<el-form-item label="创建人" prop="createBy">
|
||||
<el-input v-model="queryParams.createBy" placeholder="请输入创建人" clearable @keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="出入库类型" prop="inOutType">
|
||||
<el-select v-model="queryParams.inOutType" placeholder="请选择类型" clearable @keyup.enter.native="handleQuery">
|
||||
<el-option label="入库" :value="1" />
|
||||
<el-option label="出库" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="业务类型" prop="operationType">
|
||||
<el-select v-model="queryParams.operationType" placeholder="请选择类型" clearable @keyup.enter.native="handleQuery">
|
||||
<el-option label="收货" :value="1" />
|
||||
<el-option label="加工" :value="2" />
|
||||
<el-option label="调拨" :value="3" />
|
||||
<el-option label="发货" :value="4" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
|
||||
Reference in New Issue
Block a user