0407-北仑客群+客群业绩统计+网格整体业绩修改+青田贷款客户经理

This commit is contained in:
2026-04-07 18:58:11 +08:00
parent 3938004783
commit 803dbf2aa5
45 changed files with 3457 additions and 1119 deletions

View File

@@ -17,15 +17,6 @@ export function getCustGroup(id) {
})
}
// 异步创建客群(网格导入)
export function createCustGroupByGrid(data) {
return request({
url: '/group/cust/createByGrid',
method: 'post',
data: data
})
}
// 异步创建客群(模板导入)
export function createCustGroupByTemplate(data) {
return request({
@@ -36,24 +27,6 @@ export function createCustGroupByTemplate(data) {
})
}
// 更新客群
export function updateCustGroup(data) {
return request({
url: '/group/cust/update',
method: 'post',
data: data
})
}
// 更新客群(网格导入)
export function updateCustGroupByGrid(data) {
return request({
url: '/group/cust/updateByGrid',
method: 'post',
data: data
})
}
// 更新客群(模板导入)
export function updateCustGroupByTemplate(data) {
return request({
@@ -100,24 +73,6 @@ export function removeMembers(groupId, memberIds) {
})
}
// 检查客群名称是否存在
export function checkGroupNameExist(groupName) {
return request({
url: '/group/cust/checkName',
method: 'get',
params: { groupName: groupName }
})
}
// 根据网格类型获取客户经理列表
export function getManagerList(gridType) {
return request({
url: '/grid/cmpm/managerList',
method: 'get',
params: { gridType: gridType }
})
}
// 分页查询客群成员列表
export function listCustGroupMembers(groupId, query) {
return request({
@@ -135,3 +90,11 @@ export function getRegionGridListForGroup(query) {
params: query
})
}
// 获取所有客群标签列表
export function getAllGroupTags() {
return request({
url: '/group/cust/tags',
method: 'get'
})
}

View File

@@ -0,0 +1,69 @@
import request from '@/utils/request';
export function getGroupPerformanceLsList(params) {
return request({
url: '/group/performance/ls/list',
method: 'get',
params
});
}
export function getGroupPerformanceGsList(params) {
return request({
url: '/group/performance/gs/list',
method: 'get',
params
});
}
export function getGroupPerformanceLsCustList(params) {
return request({
url: '/group/performance/ls/custList',
method: 'get',
params
});
}
export function getGroupPerformanceGsCustList(params) {
return request({
url: '/group/performance/gs/custList',
method: 'get',
params
});
}
export function exportGroupPerformanceLs(params) {
return request({
url: '/group/performance/exportLs',
method: 'get',
params,
responseType: 'blob'
});
}
export function exportGroupPerformanceGs(params) {
return request({
url: '/group/performance/exportGs',
method: 'get',
params,
responseType: 'blob'
});
}
export function exportGroupPerformanceLsCust(params) {
return request({
url: '/group/performance/exportLsCust',
method: 'get',
params,
responseType: 'blob'
});
}
export function exportGroupPerformanceGsCust(params) {
return request({
url: '/group/performance/exportGsCust',
method: 'get',
params,
responseType: 'blob'
});
}

View File

@@ -238,6 +238,28 @@ export const constantRoutes = [
component: () => import('@/views/group/custGroup/detail')
}]
},
{
path: '/center/groupPerformance/list',
component: Layout,
hidden: true,
children: [{
path: '/center/groupPerformance/list',
name: 'GroupPerformanceList',
meta: { title: '客群业绩统计', activeMenu: '/center/groupPerformance/list' },
component: () => import('@/views/group/performance/list')
}]
},
{
path: '/center/groupPerformance/custom',
component: Layout,
hidden: true,
children: [{
path: '/center/groupPerformance/custom',
name: 'GroupPerformanceCustom',
meta: { title: '客群客户明细', activeMenu: '/center/groupPerformance/list' },
component: () => import('@/views/group/performance/custom')
}]
},
{
path: '/checklist/customerlist',
component: Layout,
@@ -353,8 +375,8 @@ export const constantRoutes = [
meta: { title: '个人中心', icon: 'user' }
}
]
},
];
}
];
// 动态路由,基于用户权限动态去加载
export const dynamicRoutes = [

View File

@@ -432,7 +432,7 @@
v-if="item.cmpmType == '贷款客户经理'"
:label="`${item.cmpmType}`"
>
<span>{{ item.cmpmUserList }}</span>
<span>{{ is932Dept ? `${baseForm.belongUserName}-${baseForm.belongUserId}` : item.cmpmUserList }}</span>
</el-form-item>
</el-col>
</el-row>
@@ -1242,6 +1242,7 @@ export default {
businessScope: '',
gridUserName: '',
belongUserName: '',
belongUserId: '',
updateTime: ''
},
registerLocationNum: '',
@@ -1374,7 +1375,7 @@ export default {
Custom
},
computed: {
...mapGetters(['roles', 'userName']),
...mapGetters(['roles', 'userName', 'deptId']),
isHeadAdmin() {
return this.roles.includes('headAdmin')
},
@@ -1386,6 +1387,10 @@ export default {
isPrivate() {
return this.roles.includes('headPrivate')
},
// 是否932开头部门
is932Dept() {
return String(this.deptId || '').substring(0, 3) === '932'
},
// 运管部
isHeadOps() {
return this.roles.includes('headOps')

View File

@@ -437,7 +437,7 @@
v-if="item.cmpmType == '贷款客户经理'"
:label="`${item.cmpmType}`"
>
<span>{{ item.cmpmUserList }}</span>
<span>{{ is932Dept ? `${baseForm.belongUserName}-${baseForm.belongUserId}` : item.cmpmUserList }}</span>
</el-form-item>
</el-col>
</el-row>
@@ -1460,6 +1460,7 @@ export default {
tel: '',
gridUserName: '',
belongUserName: '',
belongUserId: '',
updateTime: ''
},
registerLocationNum: '',
@@ -1663,6 +1664,9 @@ export default {
},
is825() {
return String(this.deptId || '').substring(0, 3) === '825'
},
is932Dept() {
return String(this.deptId || '').substring(0, 3) === '932'
}
},
created() {

View File

@@ -364,7 +364,7 @@
v-if="item.cmpmType == '贷款客户经理'"
:label="`${item.cmpmType}`"
>
<span>{{ item.cmpmUserList }}</span>
<span>{{ is932Dept ? `${profile.belongUserName}-${profile.belongUserId}` : item.cmpmUserList }}</span>
</el-form-item>
</el-col>
</el-row>
@@ -1663,7 +1663,7 @@ export default {
CustContact
},
computed: {
...mapGetters(['roles', 'userName']),
...mapGetters(['roles', 'userName', 'deptId']),
isHeadAdmin() {
return this.roles.includes('headAdmin')
},
@@ -1675,6 +1675,10 @@ export default {
isPrivate() {
return this.roles.includes('headPrivate')
},
// 是否932开头部门
is932Dept() {
return String(this.deptId || '').substring(0, 3) === '932'
},
// 运管部
isHeadOps() {
return this.roles.includes('headOps')

View File

@@ -235,10 +235,11 @@ export const privateColumnsNew825 = [
{ prop: 'cfyxNum', label: '财富有效客户数', width: 150 },
{ prop: 'yxxykNum', label: '有效信用卡数', width: 140 },
{ prop: 'yxsbkNum', label: '有效社保卡户数', width: 150 },
{ prop: 'twoTo3SbkNum', label: '二换三社保卡户数', width: 160 },
{ prop: 'twoTo3SbkNum', label: '有效社保卡新增', width: 160 },
{ prop: 'yljToSbkNum', label: '养老金迁移至社保卡户数', width: 180 },
{ prop: 'fshlNum', label: '丰收互联客户数', width: 150 },
{ prop: 'yxsdNum', label: '有效收单商户', width: 140 },
{ prop: 'hxsdNum', label: '核心收单户数', width: 140 },
{ prop: 'zf365rt', label: '近365天走访率', width: 140 },
{ prop: 'zf180rt', label: '近180天走访率', width: 140 },
{ prop: 'zf90rt', label: '近90天走访率', width: 140 },
@@ -638,8 +639,9 @@ export const privateTotalColumnsNew825 = [
{ prop: 'isGrhx', label: '是否个人核心客户', width: 140 },
{ prop: 'isCfyx', label: '是否财富有效客户', width: 140 },
{ prop: 'isYxsbk', label: '是否有效社保卡客户', width: 150 },
{ prop: 'is2to3Sbk', label: '是否二换三社保卡', width: 150 },
{ prop: 'is2to3Sbk', label: '是否有效社保卡新增', width: 150 },
{ prop: 'isYljToSbk', label: '是否养老金迁移至社保卡', width: 180 },
{ prop: 'isHxsd', label: '是否核心收单', width: 120 },
{ prop: 'is365zf', label: '近365天有无走访', width: 140 },
{ prop: 'is180zf', label: '近180天有无走访', width: 140 },
{ prop: 'is90zf', label: '近90天有无走访', width: 140 },

View File

@@ -22,11 +22,22 @@
<el-radio label="0">静态客群</el-radio>
<el-radio label="1">动态客群</el-radio>
</el-radio-group>
<div class="form-tip">
静态客群创建后客户列表固定除非手动更新
<!-- <div class="form-tip">
静态客群创建后客户列表固定
<br />
动态客群系统定期根据原始条件自动刷新客户列表
</div>
</div> -->
</el-form-item>
<el-form-item label="客群类型" prop="groupType">
<el-radio-group v-model="form.groupType" :disabled="isEdit">
<el-radio label="0">零售类</el-radio>
<el-radio label="1">公司类</el-radio>
</el-radio-group>
<!-- <div class="form-tip">
零售类只能导入个人和商户客户
<br />
公司类只能导入企业客户
</div> -->
</el-form-item>
<el-form-item label="有效期" prop="validTime">
<el-date-picker
@@ -42,89 +53,103 @@
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" :rows="2" />
</el-form-item>
<el-form-item label="客群标签">
<div class="tag-input-wrapper">
<el-input
v-model="tagInput"
placeholder="输入标签后点击加号添加"
style="width: calc(100% - 40px)"
@keyup.enter.native="addTag"
/>
<el-button type="primary" icon="el-icon-plus" @click="addTag" style="margin-left: 8px" />
</div>
<div class="tag-list" v-if="allTags.length > 0">
<span class="tag-label">已有标签</span>
<el-tag
v-for="tag in allTags"
:key="tag"
:type="selectedTags.includes(tag) ? 'primary' : 'info'"
:effect="selectedTags.includes(tag) ? 'dark' : 'plain'"
@click="toggleTag(tag)"
style="cursor: pointer; margin-right: 8px; margin-bottom: 8px"
>
{{ tag }}
</el-tag>
</div>
<div class="selected-tags" v-if="selectedTags.length > 0">
<span class="tag-label">已选标签</span>
<el-tag
v-for="tag in selectedTags"
:key="tag"
closable
@close="removeTag(tag)"
style="margin-right: 8px; margin-bottom: 8px"
>
{{ tag }}
</el-tag>
</div>
</el-form-item>
<el-form-item label="开启共享">
<el-switch v-model="shareEnabled" />
</el-form-item>
<el-form-item label="可见部门" v-if="shareEnabled">
<el-select
v-model="form.shareDeptIdList"
v-model="shareDeptList"
multiple
filterable
placeholder="请选择可见部门"
style="width: 100%"
popper-class="dept-select-popper"
>
<el-option
v-for="dept in deptOptions"
:key="dept.deptId"
:label="dept.deptName"
:value="dept.deptId"
:value="String(dept.deptId)"
/>
</el-select>
</el-form-item>
<el-form-item label="创建方式" prop="createMode">
<el-radio-group v-model="form.createMode" :disabled="isEdit">
<el-radio label="1">模板导入</el-radio>
<el-radio label="2">绩效网格</el-radio>
<el-radio label="3">地理网格</el-radio>
<el-radio label="4">绘制网格</el-radio>
</el-radio-group>
<!-- 模板导入 -->
<el-form-item label="客户文件" prop="file">
<el-upload
ref="upload"
:action="uploadUrl"
:headers="uploadHeaders"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
:on-change="handleFileChange"
:auto-upload="false"
:show-file-list="true"
:limit="1"
accept=".xlsx,.xls"
>
<el-button slot="trigger" size="small" icon="el-icon-upload">选择文件</el-button>
<el-button size="small" type="text" @click.stop="downloadTemplate" style="margin-left: 15px">
下载模板
</el-button>
<div slot="tip" class="el-upload__tip">
仅支持Excel文件文件大小不超过10MB
</div>
</el-upload>
</el-form-item>
<!-- 模板导入 -->
<template v-if="form.createMode === '1'">
<el-form-item label="客户文件" prop="file">
<el-upload
ref="upload"
:action="uploadUrl"
:headers="uploadHeaders"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
:on-change="handleFileChange"
:auto-upload="false"
:show-file-list="true"
:limit="1"
accept=".xlsx,.xls"
>
<el-button slot="trigger" size="small" icon="el-icon-upload">选择文件</el-button>
<el-button size="small" type="text" @click.stop="downloadTemplate" style="margin-left: 15px">
下载模板
</el-button>
<div slot="tip" class="el-upload__tip">
仅支持Excel文件文件大小不超过10MB
</div>
</el-upload>
</el-form-item>
</template>
<el-form-item label="匹配网格类型" prop="gridType">
<el-radio-group v-model="form.gridType" @change="handleGridTypeChange">
<el-radio label="0">绩效网格</el-radio>
<!-- 暂时隐藏后续版本开放
<el-radio label="1">地理网格</el-radio>
<el-radio label="2">绘制网格</el-radio>
-->
</el-radio-group>
<div class="form-tip">
系统将根据客户信息从所选网格匹配管户关系匹配不上则保存空的管户关系
</div>
</el-form-item>
<!-- 绩效网格 -->
<template v-if="form.createMode === '2'">
<el-form-item label="业务类型" prop="cmpmBizType">
<el-select v-model="gridForm.cmpmBizType" placeholder="请选择业务类型" style="width: 100%" @change="handleCmpmBizTypeChange">
<el-option label="零售" value="retail" />
<el-option label="对公" value="corporate" />
<el-option label="对公账户" value="corporate_account" />
</el-select>
</el-form-item>
<el-form-item label="客户经理" prop="userNames">
<el-select
v-model="gridForm.userNames"
multiple
filterable
placeholder="请选择客户经理"
style="width: 100%"
>
<el-option
v-for="user in userOptions"
:key="user.userName"
:label="user.nickName"
:value="user.userName"
/>
</el-select>
</el-form-item>
</template>
<!-- 地理网格 -->
<template v-if="form.createMode === '3'">
<!-- 地理网格筛选 - 暂时隐藏后续版本开放
<template v-if="form.gridType === '1'">
<el-form-item label="网格级别">
<el-radio-group v-model="regionQuery.gridLevel">
<el-radio label="1">总行行政网格</el-radio>
@@ -146,11 +171,11 @@
</el-form-item>
<el-form-item label="选择网格">
<el-select
v-model="gridForm.regionGridIds"
v-model="form.regionGridIds"
multiple
filterable
collapse-tags
placeholder="请先查询网格,然后选择"
placeholder="请选择网格"
style="width: 100%"
>
<el-option
@@ -162,15 +187,16 @@
</el-select>
</el-form-item>
</template>
-->
<!-- 绘制网格 -->
<template v-if="form.createMode === '4'">
<el-form-item label="绘制网格" prop="drawGridIds">
<!-- 绘制网格筛选 - 暂时隐藏后续版本开放
<template v-if="form.gridType === '2'">
<el-form-item label="绘制网格">
<el-select
v-model="gridForm.drawGridIds"
v-model="form.drawGridIds"
multiple
filterable
placeholder="请选择绘制网格"
placeholder="请选择网格"
style="width: 100%"
>
<el-option
@@ -182,6 +208,7 @@
</el-select>
</el-form-item>
</template>
-->
</el-form>
<div slot="footer" class="dialog-footer">
@@ -195,16 +222,14 @@
<script>
import {
createCustGroupByGrid,
createCustGroupByTemplate,
updateCustGroupByGrid,
updateCustGroupByTemplate,
downloadTemplate,
getManagerList,
getRegionGridListForGroup
getRegionGridListForGroup,
getCreateStatus,
getAllGroupTags
} from '@/api/group/custGroup'
import { getToken } from '@/utils/auth'
import { listUser } from '@/api/system/user'
import { listDept } from '@/api/system/dept'
import { getSimpleGridList } from '@/api/grid/list/gridlist'
@@ -231,8 +256,61 @@ export default {
// 上传地址
uploadUrl: process.env.VUE_APP_BASE_API + '/group/cust/createByTemplate',
uploadHeaders: { Authorization: 'Bearer ' + getToken() },
// 处理中提示
processTipVisible: false
// 表单数据
form: {
id: null,
groupName: null,
groupMode: '0',
groupType: '0',
groupStatus: '0',
gridType: '0',
regionGridIds: [],
drawGridIds: [],
shareEnabled: 0,
shareDeptIdList: [],
remark: null,
validTime: null
},
// 共享开关
shareEnabled: false,
// 已选部门列表字符串数组用于el-select绑定
shareDeptList: [],
// 上传文件
uploadFile: null,
// 表单验证规则
rules: {
groupName: [
{ required: true, message: '请输入客群名称', trigger: 'blur' },
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
],
groupMode: [{ required: true, message: '请选择客群模式', trigger: 'change' }],
groupType: [{ required: true, message: '请选择客群类型', trigger: 'change' }],
gridType: [{ required: true, message: '请选择匹配网格类型', trigger: 'change' }]
},
// 部门选项
deptOptions: [],
// 地理网格选项
regionGridOptions: [],
// 地理网格查询条件
regionQuery: {
gridLevel: '1',
gridDutyType: null,
gridName: null
},
// 地理网格加载状态
regionLoading: false,
// 绘制网格选项
drawGridOptions: [],
// 轮询定时器
pollTimer: null,
// 标签输入框内容
tagInput: '',
// 所有已有标签列表
allTags: [],
// 已选标签列表
selectedTags: [],
// 关闭弹窗时是否保留状态轮询
keepPollingOnClose: false
}
},
computed: {
@@ -247,66 +325,6 @@ export default {
return '确 定'
}
},
data() {
return {
// 提交中状态
submitting: false,
// 上传地址
uploadUrl: process.env.VUE_APP_BASE_API + '/group/cust/createByTemplate',
uploadHeaders: { Authorization: 'Bearer ' + getToken() },
// 处理中提示
processTipVisible: false,
// 表单数据
form: {
id: null,
groupName: null,
groupMode: '0',
createMode: '1',
groupStatus: '0',
shareEnabled: 0,
shareDeptIdList: [],
remark: null,
validTime: null
},
// 共享开关
shareEnabled: false,
// 网格表单数据
gridForm: {
gridType: '0',
cmpmBizType: null,
userNames: [],
regionGridIds: [],
drawGridIds: []
},
// 上传文件
uploadFile: null,
// 表单验证规则
rules: {
groupName: [
{ required: true, message: '请输入客群名称', trigger: 'blur' },
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
],
groupMode: [{ required: true, message: '请选择客群模式', trigger: 'change' }],
createMode: [{ required: true, message: '请选择创建方式', trigger: 'change' }]
},
// 部门选项
deptOptions: [],
// 用户选项
userOptions: [],
// 地理网格选项
regionGridOptions: [],
// 地理网格查询条件
regionQuery: {
gridLevel: '1',
gridDutyType: null,
gridName: null
},
// 地理网格加载状态
regionLoading: false,
// 绘制网格选项
drawGridOptions: []
}
},
watch: {
visible(val) {
if (val) {
@@ -318,143 +336,142 @@ export default {
if (val && Object.keys(val).length > 0) {
this.form = { ...val }
this.shareEnabled = val.shareEnabled === 1
// 解析 shareDeptIds 字符串为 shareDeptIdList 数组
// 解析 shareDeptIds 字符串为 shareDeptList 数组保持字符串类型以匹配el-option的value
if (val.shareDeptIds) {
this.form.shareDeptIdList = val.shareDeptIds.split(',').filter(id => id)
this.shareDeptList = val.shareDeptIds.split(',').filter(id => id)
} else {
this.form.shareDeptIdList = []
this.shareDeptList = []
}
// 解析网格ID
if (val.regionGridIds) {
this.form.regionGridIds = val.regionGridIds.split(',').map(id => parseInt(id)).filter(id => id)
} else {
this.form.regionGridIds = []
}
if (val.drawGridIds) {
this.form.drawGridIds = val.drawGridIds.split(',').map(id => parseInt(id)).filter(id => id)
} else {
this.form.drawGridIds = []
}
// 解析标签
if (val.groupTags) {
this.selectedTags = val.groupTags.split(',').map(tag => tag.trim()).filter(tag => tag)
} else {
this.selectedTags = []
}
// 反显网格数据(需要在 form.createMode watch 之后执行)
// 反显网格数据
this.$nextTick(() => {
this.restoreGridData(val)
})
}
},
immediate: true
},
'form.createMode'(val) {
// 重置网格表单
this.gridForm = {
gridType: val === '2' ? '0' : val === '3' ? '1' : '2',
regionGridIds: [],
drawGridIds: []
}
// 切换到地理网格模式时,重置查询条件
if (val === '3') {
this.resetRegionQuery()
}
// 切换到绘制网格模式时,加载绘制网格列表
if (val === '4') {
this.loadDrawGridOptions()
}
},
'gridForm.cmpmBizType'(val) {
// 当业务类型改变时,清空已选择的客户经理
if (val) {
this.gridForm.userNames = []
this.loadManagerOptions()
}
},
'form.createStatus'(val) {
// 监听创建状态变化,显示提示
if (this.isEdit && val === '0') {
this.processTipVisible = true
} else if (this.processTipVisible) {
this.processTipVisible = false
}
}
},
created() {
this.loadDeptOptions()
this.loadAllTags()
},
beforeDestroy() {
this.clearPollTimer()
},
methods: {
/** 初始化 */
init() {
this.$nextTick(() => {
this.$refs.form && this.$refs.form.clearValidate()
// 编辑模式下,恢复网格数据
if (this.isEdit && this.groupData && Object.keys(this.groupData).length > 0) {
this.restoreGridData(this.groupData)
}
})
},
/** 反显网格数据 */
restoreGridData(data) {
if (!this.isEdit) return
if (!this.isEdit || !data.gridType) return
const createMode = String(data.createMode)
// 根据创建方式反显网格数据
if (createMode === '2' && data.gridType === '0') {
// 绩效网格
this.gridForm.gridType = '0'
this.gridForm.cmpmBizType = data.cmpmBizType
if (data.gridUserNames) {
this.gridForm.userNames = data.gridUserNames.split(',').filter(n => n)
// 加载客户经理选项
this.loadManagerOptions()
}
} else if (createMode === '3' && data.gridType === '1') {
// 地理网格 - 需要查询所有网格以便正确反显
this.gridForm.gridType = '1'
// 设置默认查询条件(获取所有网格)
this.regionQuery = {
gridLevel: '1',
gridDutyType: null,
gridName: null
}
if (data.gridType === '1') {
this.resetRegionQuery()
if (data.regionGridIds) {
this.gridForm.regionGridIds = data.regionGridIds.split(',').map(id => parseInt(id)).filter(id => id)
// 加载地理网格选项(无查询条件,获取全部)
this.loadRegionGridOptions()
}
} else if (createMode === '4' && data.gridType === '2') {
// 绘制网格
this.gridForm.gridType = '2'
if (data.drawGridIds) {
this.gridForm.drawGridIds = data.drawGridIds.split(',').map(id => parseInt(id)).filter(id => id)
// 加载绘制网格选项
this.loadDrawGridOptions()
}
} else if (data.gridType === '2') {
this.loadDrawGridOptions()
}
},
/** 加载部门选项 */
loadDeptOptions() {
listDept().then(response => {
this.deptOptions = response.data || []
// 将树形结构扁平化以便el-select遍历
this.deptOptions = this.flattenDeptList(response.data || [])
}).catch(() => {
this.deptOptions = []
})
},
/** 加载用户选项 */
loadUserOptions() {
listUser().then(response => {
this.userOptions = response.rows || []
/** 扁平化部门树 */
flattenDeptList(list, result = []) {
list.forEach(item => {
result.push({ deptId: item.deptId, deptName: item.deptName })
if (item.children && item.children.length > 0) {
this.flattenDeptList(item.children, result)
}
})
return result
},
/** 加载所有已有标签 */
loadAllTags() {
getAllGroupTags().then(response => {
this.allTags = response.data || []
}).catch(() => {
this.userOptions = []
this.allTags = []
})
},
/** 根据业务类型加载客户经理选项 */
loadManagerOptions() {
if (!this.gridForm.cmpmBizType) {
this.userOptions = []
/** 添加标签 */
addTag() {
const tag = this.tagInput.trim()
if (!tag) return
if (this.selectedTags.includes(tag)) {
this.$message.warning('该标签已添加')
return
}
getManagerList(this.gridForm.cmpmBizType).then(response => {
this.userOptions = response.data || []
}).catch(() => {
this.userOptions = []
})
this.selectedTags.push(tag)
// 如果是新标签添加到allTags中
if (!this.allTags.includes(tag)) {
this.allTags.push(tag)
}
this.tagInput = ''
},
/** 业务类型改变处理 */
handleCmpmBizTypeChange(val) {
// 清空已选择的客户经理
this.gridForm.userNames = []
/** 切换标签选中状态 */
toggleTag(tag) {
const index = this.selectedTags.indexOf(tag)
if (index > -1) {
this.selectedTags.splice(index, 1)
} else {
this.selectedTags.push(tag)
}
},
/** 移除已选标签 */
removeTag(tag) {
const index = this.selectedTags.indexOf(tag)
if (index > -1) {
this.selectedTags.splice(index, 1)
}
},
/** 网格类型改变处理 */
handleGridTypeChange(val) {
if (val === '1') {
this.resetRegionQuery()
} else if (val === '2') {
this.loadDrawGridOptions()
}
// 清空网格选择
this.form.regionGridIds = []
this.form.drawGridIds = []
},
/** 加载地理网格选项 */
@@ -486,7 +503,7 @@ export default {
gridDutyType: null,
gridName: null
}
this.gridForm.regionGridIds = []
this.form.regionGridIds = []
},
/** 加载绘制网格选项 */
@@ -534,130 +551,138 @@ export default {
handleSubmit() {
this.$refs.form.validate(valid => {
if (valid) {
if (!this.uploadFile && !this.isEdit) {
this.$modal.msgWarning('请选择要上传的文件')
return
}
// 处理共享设置
this.form.shareEnabled = this.shareEnabled ? 1 : 0
// 转换 shareDeptIdList 数组为 shareDeptIds 逗号分隔字符串
this.form.shareDeptIds = this.form.shareDeptIdList.join(',')
this.form.shareDeptIds = this.shareDeptList.join(',')
// 转换选中的标签为逗号分隔字符串
this.form.groupTags = this.selectedTags.join(',')
// 转换网格ID数组为逗号分隔字符串
this.form.regionGridIds = this.form.regionGridIds.join(',')
this.form.drawGridIds = this.form.drawGridIds.join(',')
// 根据创建方式处理提交数据
if (this.form.createMode === '1') {
this.handleTemplateSubmit()
this.submitting = true
// 构建表单数据
const formData = new FormData()
formData.append('dto', JSON.stringify(this.form))
if (this.uploadFile) {
formData.append('file', this.uploadFile)
}
if (this.isEdit) {
// 编辑模式
updateCustGroupByTemplate(formData).then(response => {
this.submitting = false
if (response.msg === '客群更新成功') {
// 没有上传文件,直接更新成功
this.$modal.msgSuccess('客群更新成功')
this.$emit('submit', { id: this.form.id, refresh: true })
} else {
// 有上传文件,后台正在处理
this.$modal.msgSuccess('客群更新任务已提交,后台正在处理')
this.keepPollingOnClose = true
this.startPollingStatus(this.form.id)
}
this.handleClose()
}).catch(() => {
this.submitting = false
})
} else {
this.handleGridSubmit()
// 新增模式
createCustGroupByTemplate(formData).then(response => {
const groupId = response && response.data ? response.data : null
if (!groupId) {
this.$modal.msgError('客群创建成功但未获取到任务ID请稍后在列表中查看状态')
this.submitting = false
this.handleClose()
return
}
this.$modal.msgSuccess('客群创建任务已提交,后台正在处理')
this.submitting = false
this.keepPollingOnClose = true
this.startPollingStatus(groupId)
this.handleClose()
}).catch(() => {
this.submitting = false
})
}
}
})
},
/** 模板导入提交 */
handleTemplateSubmit() {
if (!this.uploadFile && !this.isEdit) {
this.$modal.msgWarning('请选择要上传的文件')
return
}
this.submitting = true
// 构建表单数据
const formData = new FormData()
formData.append('dto', JSON.stringify(this.form))
if (this.uploadFile) {
formData.append('file', this.uploadFile)
}
if (this.isEdit) {
// 编辑模式:重新导入模板文件
updateCustGroupByTemplate(formData).then(response => {
this.$modal.msgSuccess('客群更新中,请稍后刷新查看')
this.submitting = false
this.$emit('submit', { id: this.form.id })
this.handleClose()
}).catch(() => {
this.submitting = false
})
} else {
// 新增模式
createCustGroupByTemplate(formData).then(response => {
this.$modal.msgSuccess('客群创建中,请稍后刷新查看')
this.submitting = false
this.$emit('submit', { id: response.data })
this.handleClose()
}).catch(() => {
this.submitting = false
})
}
},
/** 网格导入提交 */
handleGridSubmit() {
this.submitting = true
// 构建提交数据
const submitData = {
custGroup: { ...this.form },
gridType: this.form.createMode === '2' ? '0' : this.form.createMode === '3' ? '1' : '2',
cmpmBizType: this.gridForm.cmpmBizType,
userNames: this.gridForm.userNames,
regionGridIds: this.gridForm.regionGridIds,
drawGridIds: this.gridForm.drawGridIds
}
if (this.isEdit) {
// 编辑模式:重新导入网格
updateCustGroupByGrid(submitData).then(response => {
// 使用后端返回的消息
const msg = response.msg || '客群更新成功'
if (msg.includes('更新中')) {
this.$modal.msgSuccess('客群更新中,请稍后刷新查看')
} else {
this.$modal.msgSuccess(msg)
}
this.submitting = false
this.$emit('submit', { id: this.form.id })
this.handleClose()
}).catch(() => {
this.submitting = false
})
} else {
// 新增模式
createCustGroupByGrid(submitData).then(response => {
this.$modal.msgSuccess('客群创建中,请稍后刷新查看')
this.submitting = false
this.$emit('submit', { id: response.data })
this.handleClose()
}).catch(() => {
this.submitting = false
})
}
},
/** 关闭弹窗 */
handleClose() {
this.$emit('update:visible', false)
this.resetForm()
},
/** 开始轮询创建状态 */
startPollingStatus(groupId) {
if (!groupId || groupId === 'undefined' || groupId === 'null') {
return
}
this.clearPollTimer()
// 每30秒轮询一次
this.pollTimer = setInterval(() => {
getCreateStatus(groupId).then(res => {
const status = res.data
if (status === '1') {
// 创建成功
this.clearPollTimer()
this.keepPollingOnClose = false
window.dispatchEvent(new Event('notice-center-refresh'))
this.$modal.msgSuccess('客群导入完成')
this.$emit('submit', { id: groupId, refresh: true })
} else if (status === '2') {
// 创建失败
this.clearPollTimer()
this.keepPollingOnClose = false
window.dispatchEvent(new Event('notice-center-refresh'))
this.$modal.msgError('客群导入失败,请查看消息中心了解详情')
}
}).catch(() => {
// 查询失败,继续轮询
})
}, 30000)
},
/** 清除轮询定时器 */
clearPollTimer() {
if (this.pollTimer) {
clearInterval(this.pollTimer)
this.pollTimer = null
}
},
/** 重置表单 */
resetForm() {
if (!this.keepPollingOnClose) {
this.clearPollTimer()
}
this.form = {
id: null,
groupName: null,
groupMode: '0',
createMode: '1',
groupType: '0',
groupStatus: '0',
gridType: '0',
regionGridIds: [],
drawGridIds: [],
shareEnabled: 0,
shareDeptIdList: [],
remark: null,
validTime: null
}
this.shareEnabled = false
this.gridForm = {
gridType: '0',
cmpmBizType: null,
userNames: [],
regionGridIds: [],
drawGridIds: []
}
this.shareDeptList = []
this.tagInput = ''
this.selectedTags = []
this.uploadFile = null
if (this.$refs.upload) {
this.$refs.upload.clearFiles()
@@ -677,4 +702,31 @@ export default {
line-height: 1.5;
margin-top: 5px;
}
.tag-input-wrapper {
display: flex;
align-items: center;
}
.tag-list,
.selected-tags {
margin-top: 10px;
padding: 10px;
background-color: #f5f7fa;
border-radius: 4px;
}
.tag-label {
display: block;
font-size: 12px;
color: #909399;
margin-bottom: 8px;
}
</style>
<style>
/* 解决弹窗内下拉框被遮挡的问题 */
.dept-select-popper {
z-index: 9999 !important;
}
</style>

View File

@@ -36,19 +36,51 @@
<div class="main_table">
<el-table v-loading="loading" :data="memberList">
<el-table-column label="序号" type="index" width="60" align="center" :index="indexMethod" />
<el-table-column label="客户类型" align="center" width="100">
<el-table-column label="存量状态" align="center" width="90">
<template slot-scope="scope">
<el-tag v-if="scope.row.isExisting" size="small" type="success">存量</el-tag>
<el-tag v-else size="small" type="danger">非存量</el-tag>
</template>
</el-table-column>
<el-table-column label="客户类型" align="center" width="80">
<template slot-scope="scope">
<el-tag v-if="scope.row.custType === '0'" size="small">个人</el-tag>
<el-tag v-else-if="scope.row.custType === '1'" size="small" type="warning">商户</el-tag>
<el-tag v-else-if="scope.row.custType === '2'" size="small" type="success">企业</el-tag>
</template>
</el-table-column>
<el-table-column label="客户号" prop="custId" width="150" />
<el-table-column label="客户姓名" prop="custName" width="120" />
<el-table-column label="客户号" prop="custId" width="150">
<template slot-scope="scope">
<el-button type="text" @click="handleCustClick(scope.row)">{{ scope.row.custId }}</el-button>
</template>
</el-table-column>
<el-table-column label="客户姓名" prop="custName" width="120">
<template slot-scope="scope">
<el-button type="text" @click="handleCustClick(scope.row)">{{ scope.row.custName || '-' }}</el-button>
</template>
</el-table-column>
<el-table-column label="身份证号" prop="custIdc" show-overflow-tooltip />
<el-table-column label="统信码" prop="socialCreditCode" show-overflow-tooltip />
<el-table-column label="客户经理" prop="nickName" width="100">
<template slot-scope="scope">
<span v-if="scope.row.nickName">{{ scope.row.nickName }}</span>
<span v-else class="text-muted">-</span>
</template>
</el-table-column>
<el-table-column label="所属网点" prop="outletName" width="150">
<template slot-scope="scope">
<span v-if="scope.row.outletName">{{ scope.row.outletName }}</span>
<span v-else class="text-muted">-</span>
</template>
</el-table-column>
<el-table-column label="所属支行" prop="branchName" width="150">
<template slot-scope="scope">
<span v-if="scope.row.branchName">{{ scope.row.branchName }}</span>
<span v-else class="text-muted">-</span>
</template>
</el-table-column>
<el-table-column label="添加时间" prop="createTime" width="180" />
<el-table-column v-if="isMineView" label="操作" align="center" width="100" fixed="right">
<el-table-column v-if="isMineView" label="操作" align="center" width="80" fixed="right">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="handleRemove(scope.row)">移除</el-button>
</template>
@@ -155,11 +187,45 @@ export default {
/** 返回 */
goBack() {
const fromPerformance = this.$route.query.fromPerformance
if (fromPerformance === '1') {
// 从网格业绩统计页进入,返回网格业绩统计页的客群报表
this.$router.push({
path: '/center/performance/list',
query: { isActive: '8' }
})
} else {
// 从客群列表进入,返回客群列表
this.$router.push({
path: '/group/custGroup',
query: {
tab: this.viewType,
refresh: Date.now()
}
})
}
},
/** 点击客户号/姓名跳转360视图 */
handleCustClick(row) {
const custType = row.custType
let path = ''
if (custType === '0') {
// 个人客户
path = '/360charts/indexcharts'
} else if (custType === '1') {
// 商户
path = '/360charts/commercial/'
} else if (custType === '2') {
// 企业
path = '/360charts/firm/'
}
this.$router.push({
path: '/group/custGroup',
path: path,
query: {
tab: this.viewType,
refresh: Date.now()
custId: row.custId,
selectedTab: custType,
backUrl: '/group/custGroup'
}
})
},
@@ -214,5 +280,9 @@ export default {
margin-top: 15px;
}
}
.text-muted {
color: #909399;
}
}
</style>

View File

@@ -35,19 +35,6 @@
<el-option label="动态" value="1" />
</el-select>
</el-form-item>
<el-form-item label="创建方式" prop="createMode">
<el-select
v-model="queryParams.createMode"
placeholder="请选择"
clearable
style="width: 150px"
>
<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 label="客群状态" prop="groupStatus">
<el-select
v-model="queryParams.groupStatus"
@@ -59,6 +46,22 @@
<el-option label="已禁用" value="1" />
</el-select>
</el-form-item>
<el-form-item label="客群标签" prop="groupTags">
<el-select
v-model="queryParams.groupTags"
placeholder="请选择"
clearable
filterable
style="width: 150px"
>
<el-option
v-for="tag in allTags"
:key="tag"
:label="tag"
:value="tag"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery">
搜索
@@ -104,31 +107,38 @@
width="55"
align="center"
/>
<el-table-column label="客群名称" prop="groupName" min-width="180" show-overflow-tooltip />
<el-table-column label="客群模式" align="center" width="100">
<el-table-column label="客群名称" prop="groupName" width="120" show-overflow-tooltip />
<el-table-column label="客群模式" align="center" width="120">
<template slot-scope="scope">
<el-tag v-if="scope.row.groupMode === '0'" type="info" size="small">静态</el-tag>
<el-tag v-else-if="scope.row.groupMode === '1'" type="success" size="small">动态</el-tag>
</template>
</el-table-column>
<el-table-column label="创建方式" align="center" width="120">
<template slot-scope="scope">
<span v-if="scope.row.createMode === '1'">模板导入</span>
<span v-else-if="scope.row.createMode === '2'">绩效网格</span>
<span v-else-if="scope.row.createMode === '3'">地理网格</span>
<span v-else-if="scope.row.createMode === '4'">绘制网格</span>
</template>
</el-table-column>
<el-table-column label="客户数量" align="center" prop="custCount" width="100" />
<el-table-column label="客群状态" align="center" width="100">
<el-table-column label="客户数量" align="center" prop="custCount" width="120" />
<el-table-column label="客群状态" align="center" width="120">
<template slot-scope="scope">
<el-tag v-if="scope.row.groupStatus === '0'" type="success" size="small">正常</el-tag>
<el-tag v-else-if="scope.row.groupStatus === '1'" type="danger" size="small">已禁用</el-tag>
</template>
</el-table-column>
<el-table-column label="客群标签" min-width="150">
<template slot-scope="scope">
<template v-if="scope.row.groupTags">
<el-tag
v-for="tag in scope.row.groupTags.split(',')"
:key="tag"
size="small"
style="margin-right: 5px; margin-bottom: 3px"
>
{{ tag.trim() }}
</el-tag>
</template>
</template>
</el-table-column>
<el-table-column label="创建者" prop="nickName" width="120" show-overflow-tooltip />
<el-table-column label="创建时间" prop="createTime" width="180" />
<el-table-column label="操作" align="center" width="180" fixed="right">
<el-table-column label="备注" prop="remark" min-width="150" show-overflow-tooltip />
<el-table-column label="操作" align="center" width="200" fixed="right">
<template slot-scope="scope">
<el-button
size="mini"
@@ -179,7 +189,7 @@
</template>
<script>
import { listCustGroup, getCustGroup, deleteCustGroup } from '@/api/group/custGroup'
import { listCustGroup, getCustGroup, deleteCustGroup, getAllGroupTags } from '@/api/group/custGroup'
import CreateDialog from './components/create-dialog'
export default {
@@ -204,10 +214,12 @@ export default {
pageSize: 10,
groupName: null,
groupMode: null,
createMode: null,
groupStatus: null,
groupTags: null,
viewType: 'mine'
}
},
// 所有标签列表
allTags: []
}
},
computed: {
@@ -218,6 +230,7 @@ export default {
created() {
this.activeTab = this.$route.query.tab || 'mine'
this.getList()
this.loadAllTags()
},
watch: {
'$route.query.refresh'() {
@@ -261,6 +274,15 @@ export default {
})
},
/** 加载所有标签列表 */
loadAllTags() {
getAllGroupTags().then(response => {
this.allTags = response.data || []
}).catch(() => {
this.allTags = []
})
},
handleTabChange() {
this.ids = []
this.single = true
@@ -323,6 +345,12 @@ export default {
this.$modal.confirm('是否确认删除选中的客群?').then(() => {
return deleteCustGroup(ids)
}).then(() => {
if (this.groupList.length <= ids.length && this.queryParams.pageNum > 1) {
this.queryParams.pageNum -= 1
}
this.ids = []
this.single = true
this.multiple = true
this.refreshList()
this.$modal.msgSuccess('删除成功')
}).catch(() => {})
@@ -339,7 +367,6 @@ export default {
id: null,
groupName: null,
groupMode: '0',
createMode: null,
groupStatus: '0',
shareEnabled: 0,
shareDeptIdList: [],

View File

@@ -0,0 +1,275 @@
<template>
<div class="customer-wrap">
<div class="content-box">
<div class="form-box">
<el-form ref="searchForm" :model="searchForm">
<el-row>
<el-col :span="6">
<el-form-item label="客户名称" prop="custName" label-width="80px">
<el-input
v-model="searchForm.custName"
clearable
@keyup.enter.native="handleSearch"
@clear="handleSearch"
/>
</el-form-item>
</el-col>
<el-col v-if="selectedTab === '1'" :span="6">
<el-form-item label="证件号" prop="custIdc" label-width="80px">
<el-input
v-model="searchForm.custIdc"
clearable
@keyup.enter.native="handleSearch"
@clear="handleSearch"
/>
</el-form-item>
</el-col>
<el-col v-else :span="6">
<el-form-item label="统一信用码" prop="socialCreditCode" label-width="100px">
<el-input
v-model="searchForm.socialCreditCode"
clearable
@keyup.enter.native="handleSearch"
@clear="handleSearch"
/>
</el-form-item>
</el-col>
<el-col :span="5" :offset="1" style="text-align: left">
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</el-col>
</el-row>
</el-form>
</div>
<div class="operate-btn">
<el-button @click="returnLastPage">返回上一级</el-button>
<el-button type="primary" @click="handleDownload">导出</el-button>
</div>
<div class="remark">
<span>统计日期<span class="span-value">{{ dt }}</span></span>
<span>客群名称<span class="span-value">{{ groupName }}</span></span>
<span>客群模式<span class="span-value">{{ groupModeText }}</span></span>
</div>
<div class="main_table">
<el-table v-loading="loading" :data="tableData" style="width: 100%">
<el-table-column
v-for="(item, index) in tableColumns"
:key="index"
align="center"
:prop="item.prop"
:label="item.label"
show-overflow-tooltip
:width="item.width || 120"
>
<template slot-scope="scope">
<el-button
v-if="item.prop === 'custName'"
type="text"
class="special-btn"
@click="openCustomer(scope.row)"
>
{{ scope.row[item.prop] || '-' }}
</el-button>
<span v-else>{{ scope.row[item.prop] || '-' }}</span>
</template>
</el-table-column>
</el-table>
<el-pagination
:page-sizes="[10, 20, 30, 40]"
:page-size="pageSize"
layout="->,total,prev,pager,next,sizes"
:total="total"
:current-page="pageNum"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</div>
</template>
<script>
import {
getGroupPerformanceLsCustList,
getGroupPerformanceGsCustList,
exportGroupPerformanceLsCust,
exportGroupPerformanceGsCust
} from '@/api/group/performance';
import { retailDetailColumns, publicDetailColumns, groupModeDict } from '../list/list';
import { downloadFiles } from '@/utils';
export default {
name: 'GroupPerformanceCustom',
data() {
return {
loading: false,
tableData: [],
total: 0,
pageSize: 10,
pageNum: 1,
tableColumns: [],
selectedTab: '1',
dt: '',
groupId: '',
groupName: '',
groupMode: '',
searchForm: {
custName: '',
custIdc: '',
socialCreditCode: ''
}
};
},
computed: {
groupModeText() {
return groupModeDict[this.groupMode] || '-';
}
},
created() {
const { type, dt, groupId, groupName, groupMode } = this.$route.query;
this.selectedTab = type || '1';
this.dt = dt || '';
this.groupId = groupId || '';
this.groupName = groupName || '';
this.groupMode = groupMode || '';
this.tableColumns = this.selectedTab === '1' ? retailDetailColumns : publicDetailColumns;
this.getList();
},
methods: {
getList() {
this.loading = true;
const api = this.selectedTab === '1' ? getGroupPerformanceLsCustList : getGroupPerformanceGsCustList;
const params = {
groupId: this.groupId,
dt: this.dt,
custName: this.searchForm.custName,
custIdc: this.searchForm.custIdc,
socialCreditCode: this.searchForm.socialCreditCode,
pageNum: this.pageNum,
pageSize: this.pageSize
};
api(params).then(res => {
this.tableData = res.rows;
this.total = res.total;
this.loading = false;
}).catch(() => {
this.loading = false;
});
},
handleSearch() {
this.pageNum = 1;
this.getList();
},
handleReset() {
this.searchForm = {
custName: '',
custIdc: '',
socialCreditCode: ''
};
this.pageNum = 1;
this.getList();
},
handleSizeChange(size) {
this.pageSize = size;
this.getList();
},
handleCurrentChange(page) {
this.pageNum = page;
this.getList();
},
handleDownload() {
const api = this.selectedTab === '1' ? exportGroupPerformanceLsCust : exportGroupPerformanceGsCust;
const fileName = this.selectedTab === '1' ? '零售客群客户明细' : '公司客群客户明细';
api({
groupId: this.groupId,
dt: this.dt,
custName: this.searchForm.custName,
custIdc: this.searchForm.custIdc,
socialCreditCode: this.searchForm.socialCreditCode
}).then(res => {
downloadFiles(res, `${fileName}_${new Date().getTime()}.xlsx`);
});
},
returnLastPage() {
this.$router.replace({
path: '/center/groupPerformance/list'
});
},
openCustomer(row) {
const custType = row.custType;
let path = '';
const rawId = this.selectedTab === '1' ? row.custIdc : row.socialCreditCode;
const custId = rawId ? (custType === '0' ? `101${rawId}` : `202${rawId}`) : '';
if (custType === '0') {
path = '/360charts/indexcharts';
} else if (custType === '1') {
path = '/360charts/commercial/';
} else if (custType === '2') {
path = '/360charts/firm/';
}
if (!path || !custId) {
return;
}
this.$router.push({
path: path,
query: {
custId,
selectedTab: custType,
backUrl: '/center/groupPerformance/list'
}
});
}
}
};
</script>
<style lang="scss" scoped>
.customer-wrap {
background-color: #ffffff;
overflow: hidden;
box-shadow: 0 3px 8px 0 #00000017;
}
.content-box {
padding: 20px 24px 24px;
}
.operate-btn {
margin-bottom: 12px;
.el-button + .el-button {
margin-left: 12px;
}
}
.remark {
margin-bottom: 12px;
color: #666666;
span {
display: inline-block;
margin-right: 20px;
margin-bottom: 8px;
}
.span-value {
color: #333333;
font-weight: 500;
}
}
.main_table {
margin-top: 15px;
}
::v-deep .el-input {
width: 100%;
}
::v-deep .el-pagination {
margin-top: 15px;
}
</style>

View File

@@ -0,0 +1,294 @@
<template>
<div class="customer-wrap">
<el-radio-group
v-model="selectedTab"
class="header-radio"
@input="handleTabChange"
>
<el-radio-button
label="1"
:disabled="isPublic"
:class="{ 'btn-disabled': isPublic }"
>零售部</el-radio-button>
<el-radio-button
label="2"
:disabled="isPrivate"
:class="{ 'btn-disabled': isPrivate }"
>公司部</el-radio-button>
</el-radio-group>
<div class="content-box">
<div class="form-box">
<el-form ref="searchForm" :model="searchForm">
<el-row>
<el-col :span="6">
<el-form-item label="客群名称" prop="groupName" label-width="80px">
<el-input
v-model="searchForm.groupName"
clearable
@keyup.enter.native="handleSearch"
@clear="handleSearch"
/>
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="统计日期" prop="dt" label-width="80px">
<el-date-picker
v-model="searchForm.dt"
type="date"
value-format="yyyy-MM-dd"
:clearable="false"
:picker-options="pickerOptions"
@change="handleSearch"
/>
</el-form-item>
</el-col>
<el-col :span="4" style="text-align: right">
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</el-col>
</el-row>
</el-form>
</div>
<div>
<el-button type="primary" @click="handleDownload">导出</el-button>
</div>
<div class="main_table">
<el-table
:key="tableKey"
v-loading="loading"
:data="tableData"
style="width: 100%"
max-height="650"
>
<el-table-column
v-for="(item, index) in tableColumns"
:key="index"
align="center"
:prop="item.prop"
:label="item.label"
show-overflow-tooltip
:width="item.width || 120"
>
<template slot-scope="scope">
<span v-if="item.dict">{{ item.dict[scope.row[item.prop]] || '-' }}</span>
<span v-else>{{ scope.row[item.prop] || '-' }}</span>
</template>
</el-table-column>
<el-table-column align="center" label="操作" width="100" fixed="right">
<template slot-scope="scope">
<el-button type="text" @click="handleView(scope.row)">客户明细</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
:page-sizes="[10, 20, 30, 40]"
:page-size="pageSize"
layout="->,total,prev,pager,next,sizes"
:total="total"
:current-page="pageNum"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</div>
</template>
<script>
import {
getGroupPerformanceLsList,
getGroupPerformanceGsList,
exportGroupPerformanceLs,
exportGroupPerformanceGs
} from '@/api/group/performance';
import { retailColumns, publicColumns } from './list';
import { mapGetters } from 'vuex';
import { downloadFiles } from '@/utils';
export default {
name: 'GroupPerformanceList',
data() {
return {
pickerOptions: {
disabledDate(time) {
const today = new Date();
today.setHours(0, 0, 0, 0);
return time.getTime() >= today.getTime();
}
},
selectedTab: '1',
loading: false,
tableData: [],
total: 0,
pageSize: 10,
pageNum: 1,
tableColumns: [],
tableKey: 'group-performance-1',
searchForm: {
groupName: '',
dt: ''
}
};
},
computed: {
...mapGetters(['roles']),
isPublic() {
return this.roles.includes('headPublic');
},
isPrivate() {
return this.roles.includes('headPrivate');
}
},
created() {
const date = new Date();
date.setDate(date.getDate() - 1);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
this.searchForm.dt = `${year}-${month}-${day}`;
if (this.isPublic) {
this.selectedTab = '2';
} else if (this.isPrivate) {
this.selectedTab = '1';
}
this.updateColumns();
this.getList();
},
methods: {
updateColumns() {
this.tableColumns = this.selectedTab === '1' ? retailColumns : publicColumns;
this.tableKey = `group-performance-${this.selectedTab}`;
},
getList() {
this.loading = true;
const api = this.selectedTab === '1' ? getGroupPerformanceLsList : getGroupPerformanceGsList;
api({
...this.searchForm,
pageNum: this.pageNum,
pageSize: this.pageSize
}).then(res => {
this.tableData = res.rows;
this.total = res.total;
this.loading = false;
}).catch(() => {
this.loading = false;
});
},
handleTabChange() {
this.updateColumns();
this.pageNum = 1;
this.getList();
},
handleSearch() {
this.pageNum = 1;
this.getList();
},
handleReset() {
const date = new Date();
date.setDate(date.getDate() - 1);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
this.searchForm = {
groupName: '',
dt: `${year}-${month}-${day}`
};
this.pageNum = 1;
this.getList();
},
handleSizeChange(size) {
this.pageSize = size;
this.getList();
},
handleCurrentChange(page) {
this.pageNum = page;
this.getList();
},
handleView(row) {
this.$router.push({
path: '/center/groupPerformance/custom',
query: {
type: this.selectedTab,
dt: row.dt,
groupId: row.groupId,
groupName: row.groupName,
groupMode: row.groupMode
}
});
},
handleDownload() {
const api = this.selectedTab === '1' ? exportGroupPerformanceLs : exportGroupPerformanceGs;
const fileName = this.selectedTab === '1' ? '零售客群业绩汇总' : '公司客群业绩汇总';
api(this.searchForm).then(res => {
downloadFiles(res, `${fileName}_${new Date().getTime()}.xlsx`);
});
}
}
};
</script>
<style lang="scss" scoped>
.customer-wrap {
background-color: #ffffff;
overflow: hidden;
border-radius: 16px 16px 0 0;
box-shadow: 0 3px 8px 0 #00000017;
.header-radio {
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #ebebeb;
.btn-disabled {
::v-deep .el-radio-button__inner {
background-color: #e7e7e7;
}
}
.el-radio-button {
flex: 1;
::v-deep .el-radio-button__inner {
width: 100%;
border: none;
font-weight: 400;
letter-spacing: 0.44px;
line-height: 25px;
font-size: 16px;
color: #666666;
padding: 11px 0 12px 0;
}
::v-deep .el-radio-button__orig-radio:checked + .el-radio-button__inner {
background-color: #4886f8;
font-weight: 400;
color: #ffffff;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
}
}
}
.content-box {
padding: 20px 24px 24px;
}
.main_table {
margin-top: 15px;
}
::v-deep .el-date-editor,
::v-deep .el-input,
::v-deep .el-select {
width: 100%;
}
::v-deep .el-pagination {
margin-top: 15px;
}
</style>

View File

@@ -0,0 +1,194 @@
export const groupModeDict = {
'0': '静态',
'1': '动态'
};
export const retailColumns = [
{ prop: 'dt', label: '统计日期', width: 140 },
{ prop: 'groupName', label: '客群名称', width: 180 },
{ prop: 'groupMode', label: '客群模式', width: 100, dict: groupModeDict },
{ prop: 'deptId', label: '归属支行机构号', width: 140 },
{ prop: 'deptName', label: '归属支行名称', width: 160 },
{ prop: 'outletsId', label: '归属网点机构号', width: 140 },
{ prop: 'outletsName', label: '归属网点名称', width: 160 },
{ prop: 'userName', label: '归属客户经理', width: 140 },
{ prop: 'custNum', label: '入格客户数', width: 120 },
{ prop: 'zf365cnt', label: '近365天已走访人数', width: 150 },
{ prop: 'zf180cnt', label: '近180天已走访人数', width: 150 },
{ prop: 'zf90cnt', label: '近90天已走访人数', width: 150 },
{ prop: 'zf30cnt', label: '近30天已走访人数', width: 150 },
{ prop: 'zf365rt', label: '近365天走访率', width: 140 },
{ prop: 'zf180rt', label: '近180天走访率', width: 140 },
{ prop: 'zf90rt', label: '近90天走访率', width: 140 },
{ prop: 'zf30rt', label: '近30天走访率', width: 140 },
{ prop: 'curBalD', label: '活期存款余额(元)', width: 160 },
{ prop: 'sxRat', label: '授信率(%', width: 120 },
{ prop: 'yxRat', label: '用信覆盖率', width: 120 },
{ prop: 'sxNum', label: '授信户数', width: 120 },
{ prop: 'yxNum', label: '用信户数', width: 120 },
{ prop: 'sxBal', label: '授信金额(合同)', width: 160 },
{ prop: 'balLoan', label: '贷款余额(元)', width: 150 },
{ prop: 'loanAve', label: '贷款年日均(元)', width: 150 },
{ prop: 'yxhtRat', label: '合同签约率(%', width: 140 },
{ prop: 'dianRat', label: '代扣电费覆盖率(%', width: 150 },
{ prop: 'shuiRat', label: '代扣水费率(%', width: 140 },
{ prop: 'taxRat', label: '代扣税费率(%', width: 140 },
{ prop: 'openRat', label: '开户率(%', width: 120 },
{ prop: 'yxhtNum', label: '合同签约客户数', width: 150 },
{ prop: 'dianNum', label: '代扣电费客户数', width: 150 },
{ prop: 'shuiNum', label: '代扣水费客户数', width: 150 },
{ prop: 'taxNum', label: '代扣税费数', width: 140 },
{ prop: 'openNum', label: '开户数', width: 120 },
{ prop: 'depBal', label: '存款余额(元)', width: 150 },
{ prop: 'finBal', label: '财富余额(元)', width: 150 },
{ prop: 'grhxNum', label: '个人核心客户数', width: 150 },
{ prop: 'cfyxNum', label: '财富有效客户数', width: 150 },
{ prop: 'yxxykNum', label: '有效信用卡数', width: 140 },
{ prop: 'yxsbkNum', label: '有效社保卡户数', width: 150 },
{ prop: 'twoTo3SbkNum', label: '二换三社保卡户数', width: 160 },
{ prop: 'yljToSbkNum', label: '养老金迁移至社保卡户数', width: 180 },
{ prop: 'fshlNum', label: '丰收互联客户数', width: 150 },
{ prop: 'yxsdNum', label: '有效收单商户', width: 140 },
{ prop: 'hxsdNum', label: '核心收单户数', width: 140 },
{ prop: 'regionCode', label: '归属行政区划编码', width: 160 }
];
export const publicColumns = [
{ prop: 'dt', label: '统计日期', width: 140 },
{ prop: 'groupName', label: '客群名称', width: 180 },
{ prop: 'groupMode', label: '客群模式', width: 100, dict: groupModeDict },
{ prop: 'deptId', label: '归属支行', width: 120 },
{ prop: 'deptName', label: '归属支行名称', width: 140 },
{ prop: 'outletsId', label: '归属网点', width: 120 },
{ prop: 'outletsName', label: '归属网点名称', width: 140 },
{ prop: 'userName', label: '归属客户经理', width: 140 },
{ prop: 'custNum', label: '入格客户数', width: 120 },
{ prop: 'hqCurBalance', label: '活期存款余额', width: 140 },
{ prop: 'bzCurBalance', label: '保证金存款余额', width: 150 },
{ prop: 'loanBalanceCny', label: '贷款余额', width: 140 },
{ prop: 'financeProd711Balance', label: '贴现余额', width: 140 },
{ prop: 'financeProd716Balance', label: '承兑汇票余额', width: 150 },
{ prop: 'loanYearDailyaverage', label: '贷款年日均', width: 140 },
{ prop: 'qfcdRat', label: '签发承兑汇票率', width: 140 },
{ prop: 'txRat', label: '贴现业务率', width: 120 },
{ prop: 'bhRat', label: '保函业务率', width: 120 },
{ prop: 'yxdfgzRat', label: '有效代发工资率', width: 150 },
{ prop: 'dkdfRat', label: '代扣电费率', width: 120 },
{ prop: 'dksfRat', label: '代扣水费率', width: 120 },
{ prop: 'dkshfRat', label: '代扣税费率', width: 120 },
{ prop: 'pjbRat', label: '票据宝签约率', width: 130 },
{ prop: 'czbRat', label: '财资宝签约率', width: 130 },
{ prop: 'sfbRat', label: '收付宝签约率', width: 130 },
{ prop: 'mrbRat', label: '贸融宝签约率', width: 130 },
{ prop: 'szstRat', label: '数字生态产品签约率', width: 170 },
{ prop: 'khRat', label: '开户率', width: 120 },
{ prop: 'gjjsywRat', label: '国际结算业务率', width: 150 },
{ prop: 'yqjshRat', label: '远期结算汇业务率', width: 160 },
{ prop: 'qfcdNum', label: '签发承兑汇票数', width: 150 },
{ prop: 'txNum', label: '贴现业务数', width: 120 },
{ prop: 'bhNum', label: '保函业务数', width: 120 },
{ prop: 'yxdfgzNum', label: '有效代发工资数', width: 150 },
{ prop: 'ustrCountPerM', label: '月均代发工资笔数', width: 150 },
{ prop: 'ustrBalM', label: '月均代发工资金额(元)', width: 170 },
{ prop: 'dkdfNum', label: '代扣电费数', width: 120 },
{ prop: 'dksfNum', label: '代扣水费数', width: 120 },
{ prop: 'dkshfNum', label: '代扣税费数', width: 120 },
{ prop: 'pjbNum', label: '票据宝签约数', width: 130 },
{ prop: 'czbNum', label: '财资宝签约数', width: 130 },
{ prop: 'sfbNum', label: '收付宝签约数', width: 130 },
{ prop: 'mrbNum', label: '贸融宝签约数', width: 130 },
{ prop: 'szstNum', label: '数字生态产品签约数', width: 170 },
{ prop: 'khNum', label: '开户数', width: 120 },
{ prop: 'gjjsywNum', label: '国际结算业务数', width: 150 },
{ prop: 'yqjshNum', label: '远期结算汇业务数', width: 160 },
{ prop: 'htqyRat', label: '合同签约率', width: 130 },
{ prop: 'htqyNum', label: '合同签约数', width: 130 },
{ prop: 'zf365cnt', label: '近365天已走访人数', width: 150 },
{ prop: 'zf180cnt', label: '近180天已走访人数', width: 150 },
{ prop: 'zf90cnt', label: '近90天已走访人数', width: 150 },
{ prop: 'zf30cnt', label: '近30天已走访人数', width: 150 },
{ prop: 'zf365rt', label: '近365天走访率', width: 140 },
{ prop: 'zf180rt', label: '近180天走访率', width: 140 },
{ prop: 'zf90rt', label: '近90天走访率', width: 140 },
{ prop: 'zf30rt', label: '近30天走访率', width: 140 },
{ prop: 'phRat', label: '建档率', width: 120 },
{ prop: 'phNum', label: '建档数', width: 120 },
{ prop: 'regionCode', label: '归属行政区划编码', width: 160 }
];
export const retailDetailColumns = [
{ prop: 'custName', label: '客户名称', width: 160 },
{ prop: 'custIdc', label: '客户证件号', width: 180 },
{ prop: 'custIsn', label: '客户内码', width: 140 },
{ prop: 'curBalD', label: '活期存款余额', width: 140 },
{ prop: 'balLoan', label: '贷款余额', width: 140 },
{ prop: 'loanAve', label: '贷款年日均', width: 140 },
{ prop: 'isSx', label: '是否授信', width: 100 },
{ prop: 'isYx', label: '是否用信', width: 100 },
{ prop: 'sxBal', label: '授信金额', width: 120 },
{ prop: 'isYxht', label: '是否合同签约', width: 120 },
{ prop: 'isXyk', label: '是否持有信用卡', width: 130 },
{ prop: 'fshl', label: '是否开通丰收互联', width: 140 },
{ prop: 'isSd', label: '是否办理收单', width: 120 },
{ prop: 'dian', label: '是否代扣电费', width: 120 },
{ prop: 'shui', label: '是否代扣水费', width: 120 },
{ prop: 'tax', label: '是否代扣税费', width: 120 },
{ prop: 'openNum', label: '开户数', width: 100 },
{ prop: 'depBal', label: '存款余额', width: 120 },
{ prop: 'finBal', label: '财富余额', width: 120 },
{ prop: 'isGrhx', label: '是否个人核心客户', width: 140 },
{ prop: 'isCfyx', label: '是否财富有效客户', width: 140 },
{ prop: 'isYxsbk', label: '是否有效社保卡客户', width: 150 },
{ prop: 'is2to3Sbk', label: '是否二换三社保卡', width: 140 },
{ prop: 'isYljToSbk', label: '是否养老金迁移至社保卡', width: 180 },
{ prop: 'is365zf', label: '近365天有无走访', width: 140 },
{ prop: 'is180zf', label: '近180天有无走访', width: 140 },
{ prop: 'is90zf', label: '近90天有无走访', width: 140 },
{ prop: 'is30zf', label: '近30天有无走访', width: 140 },
{ prop: 'isHxsd', label: '是否核心收单', width: 120 },
{ prop: 'custType', label: '客户类型', width: 100 },
{ prop: 'regionCode', label: '归属行政区划编码', width: 160 }
];
export const publicDetailColumns = [
{ prop: 'custName', label: '客户名称', width: 160 },
{ prop: 'socialCreditCode', label: '客户证件号', width: 200 },
{ prop: 'custIsn', label: '客户内码', width: 140 },
{ prop: 'hqCurBalance', label: '活期存款余额', width: 140 },
{ prop: 'bzCurBalance', label: '保证金存款余额', width: 150 },
{ prop: 'isCredit', label: '是否授信', width: 100 },
{ prop: 'loanBalanceCny', label: '贷款余额', width: 140 },
{ prop: 'loanYearDailyaverage', label: '贷款年日均', width: 140 },
{ prop: 'financeProd716OpenFlag', label: '是否有签发承兑汇票', width: 160 },
{ prop: 'financeProd716Balance', label: '承兑汇票余额', width: 150 },
{ prop: 'financeProd711OpenFlag', label: '是否有贴现业务', width: 140 },
{ prop: 'financeProd711Balance', label: '贴现金额', width: 120 },
{ prop: 'intlBussinessJcbhOpenFlag', label: '是否有保函业务', width: 140 },
{ prop: 'isUstr', label: '是否为有效代发工资客户', width: 170 },
{ prop: 'ustrCountPerM', label: '月均代发工资笔数', width: 150 },
{ prop: 'ustrBalM', label: '月均代发工资金额(元)', width: 170 },
{ prop: 'elecchargeSignFlag', label: '是否代扣电费', width: 120 },
{ prop: 'waterchargeSignFlag', label: '是否代扣水费', width: 120 },
{ prop: 'taxdeductionSignFlag', label: '是否代扣税费', width: 120 },
{ prop: 'pjb', label: '是否票据宝签约', width: 130 },
{ prop: 'czb', label: '是否财资宝签约', width: 130 },
{ prop: 'sfb', label: '是否收付宝签约', width: 130 },
{ prop: 'mrb', label: '是否贸融宝签约', width: 130 },
{ prop: 'szst', label: '是否数字生态产品签约', width: 170 },
{ prop: 'isOpenSts', label: '是否开户', width: 100 },
{ prop: 'intlBussinessOpenFlag', label: '是否国际结算业务', width: 150 },
{ prop: 'intlBussiness325OpenFlag', label: '是否有远期结算汇业务', width: 170 },
{ prop: 'isHtqy', label: '是否合同签约', width: 120 },
{ prop: 'deptName', label: '归属支行名称', width: 140 },
{ prop: 'outletsId', label: '归属网点id', width: 120 },
{ prop: 'outletsName', label: '归属网点名称', width: 140 },
{ prop: 'userName', label: '归属客户经理', width: 140 },
{ prop: 'deptId', label: '归属支行id', width: 120 },
{ prop: 'is365zf', label: '近365天有无走访', width: 140 },
{ prop: 'is180zf', label: '近180天有无走访', width: 140 },
{ prop: 'is90zf', label: '近90天有无走访', width: 140 },
{ prop: 'is30zf', label: '近30天有无走访', width: 140 },
{ prop: 'isPh', label: '是否建档', width: 100 },
{ prop: 'custType', label: '客户类型', width: 100 },
{ prop: 'regionCode', label: '归属行政区划编码', width: 160 }
];