Files
klp-oa/klp-ui/src/views/crm/contract/selectConfig/index.vue
砂糖 041e5aef0e feat(contract-select): 为合同选择组件添加分页功能并优化搜索逻辑
1.  移除了vis-network依赖包
2.  重构两个合同选择页面的搜索与列表逻辑,移除本地合并合同数据的操作,改为直接使用接口返回数据
3.  为所有合同tab添加分页组件,支持分页查询、搜索重置页码
4.  调整表格高度优化布局,更新搜索触发事件为失焦、回车和清空按钮
5.  新增搜索查询按钮,优化搜索交互体验
2026-06-18 15:39:42 +08:00

360 lines
11 KiB
Vue

<template>
<div class="select-config-page">
<div class="page-header">
<h3>可选合同配置</h3>
<div class="header-stats">
<span class="stat-item">
<span class="stat-label">已配置</span>
<span class="stat-value">{{ contractList.length }}</span>
</span>
<span class="stat-item">
<span class="stat-label">手动添加</span>
<span class="stat-value manual">{{contractList.filter(i => i.isManual).length}}</span>
</span>
<span class="stat-item">
<span class="stat-label">接口同步</span>
<span class="stat-value api">{{contractList.filter(i => !i.isManual).length}}</span>
</span>
</div>
</div>
<div class="content-card">
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
<el-tab-pane label="可选合同" name="configured">
<div v-if="contractList.length === 0" class="empty-state">
<i class="el-icon-document"></i>
<p>暂无已配置合同</p>
<span>切换至"所有合同"标签页添加合同</span>
</div>
<el-table v-else v-loading="configLoading" :data="contractList" style="width: 100%" size="small" stripe
max-height="calc(100vh - 280px)">
<el-table-column prop="contractCode" label="合同编号" width="160" />
<el-table-column prop="contractName" label="合同名称" min-width="180" show-overflow-tooltip />
<el-table-column prop="customer" label="客户" width="140" />
<el-table-column prop="salesman" label="销售人员" width="100" />
<el-table-column prop="deliveryDate" label="交付日期" width="110">
<template slot-scope="scope">
<span :class="{ 'text-muted': !scope.row.deliveryDate }">
{{ scope.row.deliveryDate || '-' }}
</span>
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="140" show-overflow-tooltip>
<template slot-scope="scope">
<span :class="{ 'text-muted': !scope.row.remark }">
{{ scope.row.remark || '-' }}
</span>
</template>
</el-table-column>
<el-table-column label="添加方式" width="90" align="center">
<template slot-scope="scope">
<el-tag v-if="scope.row.isManual" type="success" size="small">手动</el-tag>
<el-tag v-else type="info" size="small">接口</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center">
<template slot-scope="scope">
<el-button type="text" size="small" @click="removeContract(scope.row.orderId)" style="color: #f56c6c;">
移除
</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="所有合同" v-loading="allLoading" name="all">
<div class="search-bar">
<el-input v-model="searchKeyword" placeholder="搜索合同编号 / 名称 / 客户" @blur="handleSearch"
@keyup.enter.native="handleSearch" @clear="handleSearch" clearable
size="small" class="search-input">
<i slot="prefix" class="el-input__icon el-icon-search"></i>
</el-input>
<el-button type="primary" size="small" @click="handleSearch" style="margin-left: 8px;">查询</el-button>
</div>
<div v-if="allContracts.length === 0" class="empty-state">
<i class="el-icon-folder-opened"></i>
<p>暂无合同数据</p>
</div>
<template v-else>
<el-table :data="allContracts" size="small" stripe height="calc(100vh - 400px)">
<el-table-column prop="contractCode" label="合同编号" width="160" />
<el-table-column prop="contractName" label="合同名称" min-width="180" show-overflow-tooltip />
<el-table-column prop="customer" label="客户" width="140" />
<el-table-column prop="salesman" label="销售人员" width="100" />
<el-table-column prop="deliveryDate" label="交付日期" width="110">
<template slot-scope="scope">
<span :class="{ 'text-muted': !scope.row.deliveryDate }">
{{ scope.row.deliveryDate || '-' }}
</span>
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="140" show-overflow-tooltip>
<template slot-scope="scope">
<span :class="{ 'text-muted': !scope.row.remark }">
{{ scope.row.remark || '-' }}
</span>
</template>
</el-table-column>
<el-table-column label="状态" width="80" align="center">
<template slot-scope="scope">
<el-tag v-if="isContractInList(scope.row.orderId)" type="success" size="small">已添加</el-tag>
<el-tag v-else type="info" size="small">未添加</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center">
<template slot-scope="scope">
<el-button v-if="!isContractInList(scope.row.orderId)" type="primary" size="mini"
@click="addContract(scope.row)" plain>
添加
</el-button>
<el-button v-else type="text" size="small" @click="removeContract(scope.row.orderId)"
style="color: #f56c6c;">
移除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-wrapper">
<el-pagination background layout="total, prev, pager, next, jumper" :total="total"
:page-size="pageSize" :current-page.sync="pageNum" @current-change="handlePageChange" />
</div>
</template>
</el-tab-pane>
</el-tabs>
</div>
</div>
</template>
<script>
import { listOrder, listTodayOrder } from '@/api/crm/order';
export default {
name: "SelectConfig",
data() {
return {
contractList: [],
configLoading: false,
searchKeyword: '',
allContracts: [],
allLoading: false,
activeTab: 'configured',
pageNum: 1,
pageSize: 20,
total: 0,
}
},
mounted() {
this.loadFromLocalStorage();
},
methods: {
handleTabClick(tab) {
if (tab.name === 'all' && this.allContracts.length === 0) {
this.loadAllContracts();
}
},
loadFromLocalStorage() {
try {
const storedContracts = localStorage.getItem('todayContracts');
if (storedContracts) {
this.contractList = JSON.parse(storedContracts);
}
this.loadContractList();
} catch (error) {
console.error('Failed to load contracts from localStorage:', error);
this.loadContractList();
}
},
saveToLocalStorage() {
try {
localStorage.setItem('todayContracts', JSON.stringify(this.contractList));
} catch (error) {
console.error('Failed to save contracts to localStorage:', error);
}
},
async loadContractList() {
this.configLoading = true;
try {
const res = await listTodayOrder();
const existingManualContracts = this.contractList.filter(item => item.isManual);
const apiContracts = (res.data || []).map(item => ({
...item,
isManual: false
}));
this.contractList = [...apiContracts, ...existingManualContracts];
this.contractList = this.contractList.filter((item, index, self) =>
index === self.findIndex(t => t.orderId === item.orderId)
);
this.saveToLocalStorage();
} finally {
this.configLoading = false;
}
},
async loadAllContracts() {
this.allLoading = true;
try {
const res = await listOrder({
pageNum: this.pageNum,
pageSize: this.pageSize,
keyword: this.searchKeyword || undefined,
});
this.total = res.total || 0;
this.allContracts = res.rows || [];
} catch (error) {
console.error('Failed to load all contracts:', error);
this.allContracts = [];
} finally {
this.allLoading = false;
}
},
handleSearch() {
this.pageNum = 1;
this.loadAllContracts();
},
handlePageChange(page) {
this.pageNum = page;
this.loadAllContracts();
},
isContractInList(orderId) {
return this.contractList.some(item => item.orderId === orderId);
},
addContract(contract) {
if (!this.isContractInList(contract.orderId)) {
this.contractList.push({
...contract,
isManual: true
});
this.saveToLocalStorage();
}
},
removeContract(orderId) {
this.contractList = this.contractList.filter(item => item.orderId !== orderId);
this.saveToLocalStorage();
},
}
}
</script>
<style scoped>
.select-config-page {
padding: 16px;
min-height: 100%;
background: #f5f7fa;
}
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
padding: 16px 20px;
background: #fff;
border-radius: 6px;
border: 1px solid #ebeef5;
}
.page-header h3 {
margin: 0;
font-size: 16px;
color: #303133;
font-weight: 600;
}
.header-stats {
display: flex;
gap: 24px;
}
.stat-item {
display: flex;
align-items: center;
gap: 6px;
}
.stat-label {
font-size: 13px;
color: #909399;
}
.stat-value {
font-size: 18px;
font-weight: 600;
color: #303133;
}
.stat-value.manual {
color: #67c23a;
}
.stat-value.api {
color: #909399;
}
.content-card {
background: #fff;
border-radius: 6px;
border: 1px solid #ebeef5;
padding: 0 16px 16px;
}
.content-card ::v-deep .el-tabs__header {
margin-bottom: 16px;
}
.search-bar {
margin-bottom: 16px;
}
.search-input {
width: 320px;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: #c0c4cc;
}
.empty-state i {
font-size: 48px;
margin-bottom: 12px;
}
.empty-state p {
margin: 0 0 8px;
font-size: 14px;
color: #909399;
}
.empty-state span {
font-size: 12px;
color: #c0c4cc;
}
.text-muted {
color: #c0c4cc;
}
.table-footer {
text-align: center;
padding: 12px;
font-size: 13px;
color: #909399;
}
.pagination-wrapper {
display: flex;
justify-content: flex-end;
padding: 12px 0;
}
</style>