Files
klp-oa/klp-ui/src/views/crm/contract/index.vue
砂糖 9f3d402174 refactor(crm): 重构合同模块为订单模块并优化相关功能
重构合同模块为订单模块,包括以下主要变更:
1. 将合同编号字段从contractNo统一改为contractCode
2. 在CrmOrderBo中添加日期格式化注解
3. 重构ContractTabs组件为订单详情页
4. 添加销售员字段和相关选择器
5. 优化订单列表查询条件和展示
6. 调整订单附件管理功能
2026-04-13 17:48:19 +08:00

578 lines
23 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="app-container" style="height: calc(100vh - 84px); display: flex;">
<!-- 左侧合同列表 -->
<div class="left-panel" style="width: 30%; border-right: 1px solid #e4e7ed; overflow-y: auto;">
<ContractList ref="orderList" @add="handleAdd" @update="handleUpdate" @delete="handleDelete"
@export="handleExport" @exportContract="handleExportContract" @rowClick="handleRowClick" />
</div>
<!-- 右侧内容区域 -->
<div class="right-panel" v-if="form.orderId" style="flex: 1; display: flex; flex-direction: column;">
<!-- 右侧上方合同内容信息预览 -->
<div class="preview-panel" ref="previewPanel"
style="flex: 1; overflow-y: auto; border-bottom: 1px solid #e4e7ed;">
<ContractPreview :contract="form" @updateStatus="handleStatusChange" />
</div>
<!-- 拖拽调整条 -->
<div class="resize-handle" ref="resizeHandle"
style="height: 8px; background-color: #f5f7fa; cursor: row-resize; display: flex; align-items: center; justify-content: center;"
@mousedown="startResize">
<div style="width: 40px; height: 2px; background-color: #dcdfe6;"></div>
</div>
<!-- 右侧下方Tab标签页 -->
<div class="tab-panel" ref="tabPanel" style="flex: 1; overflow-y: auto;">
<ContractTabs :orderId="form.orderId" :customerId="form.customerId" :financeList="financeList"
:objectionList="objectionList" :wmsDeliveryWaybills="wmsDeliveryWaybills" :coilList="coilList"
:tabLoading="tabLoading" :contract-attachment="form.businessAnnex" :technical-agreement="form.techAnnex"
:other-attachment="form.productionSchedule" :currentOrder="form" />
</div>
</div>
<div v-else style="flex: 1; display: flex; flex-direction: column;">
<el-empty description="选择订单后查看内容" />
</div>
<!-- 添加或修改合同信息对话框 -->
<el-dialog :title="title" :visible.sync="open" width="1200px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<!-- 前四个字段占一排 -->
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="合同名称" prop="contractName">
<el-input v-model="form.contractName" placeholder="请输入合同名称" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="合同编号" prop="contractCode">
<el-input v-model="form.contractCode" placeholder="请输入合同编号" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="签订时间" prop="signTime">
<el-date-picker clearable v-model="form.signTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择签订时间">
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="订单总金额" prop="orderAmount">
<el-input v-model="form.orderAmount" placeholder="请输入订单总金额" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="交货日期" prop="deliveryDate">
<el-date-picker clearable v-model="form.deliveryDate" type="date" value-format="yyyy-MM-dd"
placeholder="请选择交货日期">
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="销售员" prop="salesman">
<el-select style="width: 100%;" v-model="form.salesman" placeholder="请选择销售员">
<el-option v-for="item in dict.type.wip_pack_saleman" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="签订地点" prop="signLocation">
<el-input v-model="form.signLocation" placeholder="请输入签订地点" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="产品内容">
<ProductContent v-model="form.productContent" :readonly="false" />
</el-form-item>
<el-form-item label="合同内容">
<editor v-model="form.contractContent" :min-height="192" />
</el-form-item>
<!-- 供方和需方左右布局 -->
<el-row :gutter="20">
<!-- 供方信息 -->
<el-col :span="12">
<el-form-item label="供方" prop="supplier">
<el-input v-model="form.supplier" placeholder="请输入供方" />
</el-form-item>
<el-form-item label="供方地址" prop="supplierAddress">
<el-input v-model="form.supplierAddress" placeholder="请输入供方地址" />
</el-form-item>
<el-form-item label="供方电话" prop="supplierPhone">
<el-input v-model="form.supplierPhone" placeholder="请输入供方电话" />
</el-form-item>
<el-form-item label="供方开户行" prop="supplierBank">
<el-input v-model="form.supplierBank" placeholder="请输入供方开户行" />
</el-form-item>
<el-form-item label="供方账号" prop="supplierAccount">
<el-input v-model="form.supplierAccount" placeholder="请输入供方账号" />
</el-form-item>
<el-form-item label="供方税号" prop="supplierTaxNo">
<el-input v-model="form.supplierTaxNo" placeholder="请输入供方税号" />
</el-form-item>
</el-col>
<!-- 需方信息 -->
<el-col :span="12">
<el-form-item label="需方" prop="customer">
<CustomerSelect v-model="form.customer" bindField="companyName" @change="handleCustomerChange"
:style="{ width: '100%' }" />
<!-- <el-input v-model="form.customer" placeholder="请输入需方" /> -->
</el-form-item>
<el-form-item label="需方地址" prop="customerAddress">
<el-input v-model="form.customerAddress" placeholder="请输入需方地址" />
</el-form-item>
<el-form-item label="需方电话" prop="customerPhone">
<el-input v-model="form.customerPhone" placeholder="请输入需方电话" />
</el-form-item>
<el-form-item label="需方开户行" prop="customerBank">
<el-input v-model="form.customerBank" placeholder="请输入需方开户行" />
</el-form-item>
<el-form-item label="需方账号" prop="customerAccount">
<el-input v-model="form.customerAccount" placeholder="请输入需方账号" />
</el-form-item>
<el-form-item label="需方税号" prop="customerTaxNo">
<el-input v-model="form.customerTaxNo" placeholder="请输入需方税号" />
</el-form-item>
</el-col>
</el-row>
<!-- 最下方的附件和备注等信息保持原样 -->
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="技术附件" prop="techAnnex">
<file-upload v-model="form.techAnnex" :fileType="['pdf', 'jpeg', 'jpg', 'png', 'webp']"></file-upload>
</el-form-item>
<el-form-item label="商务附件" prop="businessAnnex">
<file-upload v-model="form.businessAnnex" :fileType="['pdf', 'jpeg', 'jpg', 'png', 'webp']"></file-upload>
</el-form-item>
<el-form-item label="排产函" prop="productionSchedule">
<file-upload v-model="form.productionSchedule"
:fileType="['pdf', 'jpeg', 'jpg', 'png', 'webp']"></file-upload>
</el-form-item>
<el-form-item label="其他附件" prop="annexFiles">
<file-upload v-model="form.annexFiles"
:fileType="['pdf', 'jpeg', 'jpg', 'png', 'webp', 'xlsx', 'xls', 'docx', 'doc']"></file-upload>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { delOrder, listOrderPackaging, updateOrder, getOrder, addOrder } from "@/api/crm/order";
import { listDeliveryWaybill } from "@/api/wms/deliveryWaybill";
import ContractList from "./components/ContractList.vue";
import ContractPreview from "./components/ContractPreview.vue";
import ContractTabs from "./components/ContractTabs.vue";
import ProductContent from "./components/ProductContent.vue";
import CustomerSelect from "@/components/KLPService/CustomerSelect/index.vue";
export default {
name: "Contract",
components: {
ContractList,
ContractPreview,
ContractTabs,
ProductContent,
CustomerSelect,
},
dicts: ['wip_pack_saleman'],
data() {
return {
financeList: [],
objectionList: [],
wmsDeliveryWaybills: [],
coilList: [],
loading: false,
tabLoading: false,
// 按钮loading
buttonLoading: false,
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
contractName: undefined,
contractCode: undefined,
supplier: undefined,
customer: undefined,
signTime: undefined,
deliveryDate: undefined,
signLocation: undefined,
status: undefined,
},
// 表单参数
form: {},
// 表单校验
rules: {
contractId: [
{ required: true, message: "合同主键ID不能为空", trigger: "blur" }
],
contractName: [
{ required: true, message: "合同名称不能为空", trigger: "blur" }
],
contractCode: [
{ required: true, message: "合同编号不能为空", trigger: "blur" }
],
orderAmount: [
{ required: true, message: "订单总金额不能为空", trigger: "blur" }
],
salesman: [
{ required: true, message: "销售员不能为空", trigger: "blur" }
],
supplier: [
{ required: true, message: "供方不能为空", trigger: "blur" }
],
customer: [
{ required: true, message: "需方不能为空", trigger: "blur" }
],
signTime: [
{ required: true, message: "签订时间不能为空", trigger: "blur" }
],
deliveryDate: [
{ required: true, message: "交货日期不能为空", trigger: "blur" }
],
signLocation: [
{ required: true, message: "签订地点不能为空", trigger: "blur" }
],
productContent: [
{ required: true, message: "产品内容不能为空", trigger: "blur" }
],
contractContent: [
{ required: true, message: "合同内容不能为空", trigger: "blur" }
],
supplierAddress: [
{ required: true, message: "供方地址不能为空", trigger: "blur" }
],
supplierPhone: [
{ required: true, message: "供方电话不能为空", trigger: "blur" }
],
supplierBank: [
{ required: true, message: "供方开户行不能为空", trigger: "blur" }
],
supplierAccount: [
{ required: true, message: "供方账号不能为空", trigger: "blur" }
],
supplierTaxNo: [
{ required: true, message: "供方税号不能为空", trigger: "blur" }
],
customerAddress: [
{ required: true, message: "需方地址不能为空", trigger: "blur" }
],
customerPhone: [
{ required: true, message: "需方电话不能为空", trigger: "blur" }
],
customerBank: [
{ required: true, message: "需方开户行不能为空", trigger: "blur" }
],
customerAccount: [
{ required: true, message: "需方账号不能为空", trigger: "blur" }
],
customerTaxNo: [
{ required: true, message: "需方税号不能为空", trigger: "blur" }
],
status: [
{ required: true, message: "合同状态不能为空", trigger: "change" }
],
}
};
},
methods: {
/** 处理客户选择 */
handleCustomerChange(customer) {
console.log(customer);
this.form.customerAddress = customer.address;
this.form.customerPhone = customer.contactWay;
this.form.customerTaxNo = customer.taxNumber;
this.form.customerId = customer.customerId;
// 处理银行信息
if (customer.bankInfo) {
try {
const bankList = JSON.parse(customer.bankInfo);
if (bankList && bankList.length > 0) {
const firstBank = bankList[0];
this.form.customerBank = firstBank.bankName;
this.form.customerAccount = firstBank.bankAccount;
}
} catch (error) {
console.error('解析银行信息失败:', error);
}
}
// 开户行和银行账号在客户信息中是一个json数组字符串如果存在则默认选中第一个将输入框改为下拉选可以快速切换
console.log(customer);
},
/** 处理合同状态更新 */
handleStatusChange(status) {
this.form.status = status;
updateContract({
...this.form,
status: status
}).then(res => {
if (res.code == 200) {
this.$message({
message: '合同状态更新成功',
type: 'success'
});
}
})
this.$refs.contractList.getList();
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 行点击事件 */
handleRowClick(row) {
this.form = row;
// this.tabLoading = true;
this.getCoilList();
// listContractOrderObjection(row.contractId).then(response => {
// this.financeList = response.data.financeList || [];
// this.objectionList = response.data.oobjectionList || [];
// this.wmsDeliveryWaybills = response.data.wmsDeliveryWaybills || [];
// }).finally(() => {
// this.tabLoading = false;
// })
},
/** 查询合同配卷列表 */
getCoilList() {
listOrderPackaging(this.form.orderId).then(response => {
this.coilList = response.data || [];
})
},
/** 表单重置 */
reset() {
this.form = {
contractId: undefined,
contractName: '产品销售合同',
contractNo: undefined,
supplier: '嘉祥科伦普重工有限公司',
customer: undefined,
signTime: undefined,
deliveryDate: undefined,
signLocation: undefined,
productContent: 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,
customerAccount: undefined,
customerTaxNo: undefined,
techAnnex: undefined,
businessAnnex: undefined,
productionSchedule: undefined,
status: 0,
remark: undefined,
createBy: undefined,
createTime: undefined,
updateBy: undefined,
updateTime: undefined,
delFlag: undefined
};
this.resetForm("form");
},
/** 多选框选中数据 */
handleSelectionChange(selection) {
this.ids = selection.map(item => item.contractId)
this.single = selection.length !== 1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加订单信息";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.loading = true;
this.reset();
const orderId = row.orderId || this.ids
getOrder(orderId).then(response => {
this.loading = false;
this.form = response.data;
this.open = true;
this.title = "修改订单信息";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
this.buttonLoading = true;
if (this.form.orderId != null) {
updateOrder(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.$refs.orderList.getList();
}).finally(() => {
this.buttonLoading = false;
});
} else {
addOrder(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.$refs.orderList.getList();
}).finally(() => {
this.buttonLoading = false;
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const orderId = row.orderId || this.ids;
this.$modal.confirm('是否确认删除订单信息编号为"' + orderId + '"的数据项?').then(() => {
return delOrder(orderId);
}).then(() => {
this.$refs.orderList.getList();
this.$modal.msgSuccess("删除成功");
})
},
/** 导出按钮操作 */
handleExport() {
this.download('crm/order/export', {
...this.queryParams
}, `order_${new Date().getTime()}.xlsx`)
},
/** 导出合同操作 */
handleExportContract(row) {
// 合同导出
},
/** 开始拖拽调整 */
startResize(e) {
e.preventDefault();
const previewPanel = this.$refs.previewPanel;
const tabPanel = this.$refs.tabPanel;
const startY = e.clientY;
const startPreviewHeight = previewPanel.offsetHeight;
const parentHeight = previewPanel.parentElement.offsetHeight;
const handleMouseMove = (event) => {
const deltaY = event.clientY - startY;
const newPreviewHeight = startPreviewHeight + deltaY;
const newTabHeight = parentHeight - newPreviewHeight - 8; // 8是拖拽条的高度
if (newPreviewHeight > 100 && newTabHeight > 100) { // 最小高度限制
previewPanel.style.flex = `0 0 ${newPreviewHeight}px`;
tabPanel.style.flex = `0 0 ${newTabHeight}px`;
}
};
const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}
}
};
</script>
<style scoped>
.app-container {
overflow: hidden;
padding: 0;
}
.left-panel {
height: calc(100vh - 84px);
box-sizing: border-box;
overflow-y: auto;
}
.right-panel {
height: calc(100vh - 84px);
overflow: hidden;
}
.preview-panel {
overflow-y: auto;
}
.tab-panel {
overflow-y: auto;
}
</style>