预期功能界面如下:
客户管理系统主要是对客户信息进行管理及分析,该系统应支持以下核心功能:
- 客户信息管理:添加、编辑、删除和查询客户信息
- 客户状态跟踪:记录与客户的所有交互,包括电话、邮件、会议等
- 客户统计与分析:图表展示客户信息
- 批量客户转移:批量转移客户到其他员工归属下
- 动态添加标签:快速添加标签进行分类
- 黑名单:添加到黑名单
- 公海客户展示:展示暂未分配的公共客户
- 权限管理:不同级别的用户访问不同级别的数据和功能
结合上述需求调研,我们进行架构设计以及技术选型
需要如下两张数据表,分别存储客户信息以及状态更新记录数据
| 字段 | 编码 | 类型 | 备注 |
|---|---|---|---|
| 名称 | mc | 文本 | - |
| 性别 | xb | 枚举 | 男/女 |
| 电话 | dh | 文本(电话) | - |
| 邮箱 | yx | 文本(邮箱) | - |
| 客户阶段 | khjd | 枚举 | 新用户/初步沟通/意向客户/无意向客户 |
| 添加渠道 | tjqd | 枚举 | app/微信小程序 |
| 所属员工 | ssyg | 文本 | - |
| 标签 | bq | 文本 | - |
| 状态 | zt | 枚举 | 正常(近期联系)/灰度(长久没联系)/流失(已删除) |
| 客户类型 | khlx | 枚举 | 个人/公司 |
| 备注 | bz | 文本 | - |
| 企业名称 | qymc | 文本 | - |
| 企业地址 | qydz | 文本 | - |
| 是否拉黑 | sflh | 布尔 | 是/否 |
| 拉黑时间 | lhsj | 日期时间 | - |
| 拉黑原因 | lhyy | 文本 | - |
| 拉黑操作人 | lhczr | 文本 | - |
| 字段 | 编码 | 类型 | 备注 |
|---|---|---|---|
| 客户 ID | khId | 文本 | - |
| 行为 | xw | 文本 | 添加黑名单/移出黑名单等 |
| 具体描述 | jtms | 文本 | - |
可以将客户信息管理、客户状态跟踪、批量客户转移、客户添加标签等功能应放在同一个模块进行操作
因此前端分为如下 4 个模块:
-
客户列表
列表展示客户信息,过滤条件为是否拉黑: false,所属员工: 不为空.
支持对客户 新增、编辑、查看、删除、转移、添加标签、拉黑、导入、导出 -
客户统计
图表展示 客户总数、今日新增客户数、公海客户数、黑名单数、客户总数趋势、新增客户数趋势、客户状态分布、客户阶段分布等信息 -
黑名单
列表展示已拉黑客户信息,过滤条件为是否拉黑: true.
支持对客户 查看、编辑、移出、删除、导入、导出 -
公海池
列表展示未分配员工的客户信息,过滤条件为是否拉黑: false,所属员工: 空.
支持对客户 查看、领取、转移、删除、导入、导出
客户管理场景比较多变,没有办法进行统一,针对每位使用方均会有对应的定制开发场景
因此这里选择使用 微搭低代码平台 快速搭建该系统
微搭是一个云开发的高性能低代码开发平台,支持快速创建数据源以及应用场景,而且内置链接腾讯 SaaS 生态,能快速对接微信生态、企业微信能力,并支持接入 AI 大模型,将产品与 AI 完美结合
下方介绍使用微搭进行开发流程
微搭分为公有云和私有云版本
公有云: 通过微搭官方平台进行搭建页面, 打包后在本地部署
私有云: 在本地服务器搭建微搭平台进行开发并发布(功能相比公有云有缺失)
下方介绍两个版本进入方式
进入 微搭官网, 点击免费试用
登录后进入微搭控制台,在主页点击 从空白创建 即可进入编辑器开发页面,这里可以查看 编辑器介绍
安装部署文档: 本地部署管理系统开发工具
微搭中将所有数据集合命名为数据源,其中分为了数据模型、APIs(私有云暂只支持http、postman、openapi)、数据连接器,具体使用参考这里 数据源概述
这里我们使用微搭的数据模型进行开发 数据模型概述
公有云
点击编辑器左侧数据源标签,即可进入数据源编辑页面
私有云
点击云数据库进入数据模型新增页面
点击新建数据模型,名称填写为客户信息,标识为数据模型唯一标识,会自动生成,这里不做调整
点击 创建 按钮,成功后进入模型配置页面,点击编辑,添加字段,按照上述数据架构录入所有字段信息
其中枚举类型字段需要关联数据集,这里点击立即创建
然后录入对应字典数据,这里以客户类型举例,点击立即创建,输入名称为 scrm-客户类型,我们默认使用1/2 选项标识,分别对应个人/企业。点击确定后即可绑定该选项集到客户类型字段上
同理创建 客户跟踪记录表
回到页面设计页签,这里我们确认有4个页面模块的开发,因此我们先新建四个空页面
- 点击新建页面按钮
- 选择表格与表单页模板,数据模型选择刚创建的客户管理,页面布局暂时选择左侧布局
- 点击新建后
微搭会自动帮我们创建三个页面,其中页面布局和页面主体是分开配置的,这里我们先配置出所有页面入口
- 这里我们克隆客户列表两次,将克隆出的页面分别命名为黑名单、公海池
并将编辑客户信息、客户信息详情等二级页面分到一个组里,方便后续维护
- 所有页面入口创建完毕后,我们进行页面布局的配置,点击头部切换到布局设计,选择左侧布局,点击根据页面一键生成按钮,则帮我们生成好了和上述页面管理目录结构一致的目录树
- 因为详情页面不能直接展示在目录上,因此删除客户详情管理目录,ctrl+s保存,点击头部切换到页面设计,即可看到左侧目录数也更新了
下面进行单个模块开发
效果如图:
点击编辑器中的列表,在右侧配置中调整如下
- 过滤条件为
是否拉黑: false,所属员工: 不为空
- 排序字段选择
更新时间、倒序
- 筛选器选择名称、电话、状态、标签等字段
- 列排序为如图顺序
- 全局按钮包含新增、批量转移客户、批量拉黑、批量删除、导出、导入
其中新增、删除、导出、导入为默认内置的功能按钮,这里我们修改下名称即可
选择左侧大纲树中的全局按钮,点击右侧添加/组件中的按钮即可添加,添加批量转移客户、批量拉黑按钮
- 操作按钮包含 查看、编辑、拉黑、删除
其中查看、编辑、删除按钮为默认内置的操作按钮
同添加全局按钮一样,选择左侧大纲树中的操作按钮,添加拉黑按钮,并调整位置到第三位
- 客户管理页面初步搭建完成,现在可以点击右上角播放按钮进行预览
点击新建,我们填写好信息点击保存
返回到列表中可以看到已经新增了一条数据
点击编辑也可正常回显,编辑数据后保存也可正常更新数据
下面我们开始完善转移客户、拉黑、自定义列展示功能
点击批量转移时需要选择新的所属员工进行更新当前条数据
因此用弹窗进行所属员工选择,确认后修改当前项数据所属员工信息,我们的逻辑线为:
操作步骤:
- 新增弹窗组件,用来存放选择所属员工
- 点击批量转移按钮事件中的点击事件,选择逻辑分支
- 判断条件选择表达式,填入下方表达式
If(!!$w.table1.selectedRecords.length, true, false)- 满足条件时,选择打开弹窗组件
- 选择刚新增的弹窗,执行方法选择打开弹窗,modal3为新增的弹窗id
- 不满足条件时,选择打开提示弹窗
- 标题为 系统提示,内容为 请选择需要转移的客户
If(!!$w.member2.value.length, true, false)- 满足条件时,调用数据源方法
- 数据源选择客户管理,方法修改多条
我们只需要更新所属员工字段,所以data参数结构如下:
({
"ssyg": $w.app.common.transSSYG($w.member2.value),
"params": {
"filter": {
"where": {
"$and": [{
"$and": [{
"_id": {
"$in": $w.table1.selectedRecords.map(it => it._id)
}
}]
}]
},
"relateWhere": {}
}
}
})因为人员字段存储为数组格式,而表中的所属员工字段是字符串格式,因此这里在全局新建一个transSSYG方法用来对数组进行转换
具体transSSYG代码如下:
export default function(arr) {
return arr ? typeof arr === 'string' ? arr : arr.join(',') : ''
}- 查询条件为数据标识等于任意一个所选行的id集合
- 成功时关闭弹窗
- 继续提示成功消息
- 刷新列表当前页
- 点击保存,此时完成了整个转移功能开发
点击 拉黑、批量拉黑 时需要填写拉黑原因
因此用弹窗进行拉黑原因填写,确认后修改当前项数据 是否拉黑:true 、 拉黑原因: 所填写文本内容 、 拉黑时间: 当前时间 、 拉黑操作人: 当前登录人userId 等信息,我们的逻辑线为:
点击拉黑时弹窗后无法获取到点击行数据,因此需要创建一个全局变量用来存储当前点击行数据,在当前页添加一个对象变量
大体流程和上方批量转移功能相似,这里稍简表示开发流程
- 新建弹窗
弹窗中添加一个多行文本字段用来存放拉黑原因
- 点击批量拉黑、拉黑时,打开该弹窗
点击批量拉黑时先判断列表是否有选中项,若没有则提示用户需要选择拉黑客户
若点击的是操作列的拉黑按钮,则需要设置全局变量
- 编辑弹窗中确认按钮的点击事件
先添加判断条件判断拉黑原因是否为空, 表达式如下:
textarea1 为拉黑原因文本框id
If(!!$w.textarea1.value, true, false)- 若满足条件则判断全局变量
cur_kh是否有值
If(!!$w.page.dataset.state.cur_kh._id, true, false)- 若有值则更新单条数据,没有值则为批量拉黑,则更新多条数据
单条更新查询条件如下:
单条更新入参如下:
({
"lhczr": $w.auth.currentUser.name,
"lhsj": $w.Now(),
"lhyy": $w.textarea1.value,
"sflh": true,
"params": {
"filter": {
"where": {
"$and": [{
"$and": [{
"_id": {
"$eq": $w.page.dataset.state.cur_kh._id
}
}]
}]
},
"relateWhere": {}
}
}
})多条更新查询条件如下:
多条更新入参如下:
({
"sflh": true,
"lhyy": $w.textarea1.value,
"lhczr": $w.auth.currentUser.nickName,
"lhsj": $w.Now(),
"params": {
"filter": {
"where": {
"$and": [{
"$and": [{
"_id": {
"$in": $w.table1.selectedRecords.map(it => it._id)
}
}]
}]
},
"relateWhere": {}
}
}
})- 更新成功后调用客户跟踪记录表新增记录,单条更新成功后创建单条记录,多条更新成功后创建多条记录
创建单条参数结构如下:
({
"xw": "添加黑名单",
"czr": {
_id: $w.auth.currentUser.userId
},
"jtms": $w.textarea1.value,
"khId": $w.page.dataset.state.cur_kh._id
})创建多条参数结构如下:
$w.table1.selectedRecords.map(it => ({
"xw": "添加黑名单",
"czr": {
_id: $w.auth.currentUser.userId
},
"jtms": $w.textarea1.value,
"khId": it._id
}))- 关闭弹窗时置空 cur_kh 变量
因需要动态添加标签,且以 tag 的形式展示,因此自定义一个标签组件,在客户列表中自定义列展示,也在新增、编辑等页面展示
具体创建流程如下:
- 选择标签列为自定义列
- 自定义插槽添加如下节点
循环展示普通容器,普通容器中的文本为标签内容,按钮为删除按钮,我们给这个普通容器添加一点样式
循环展示下方的按钮为新增按钮
数据来源为当前行标签数据,标签存储为字符串格式,因此需要转成数组格式渲染
循环展示数据表达式如下:
$w.table1.cell_bq.record 为当前行数据
!!$w.table1.cell_bq.record.bq ?
$w.table1.cell_bq.record.bq.split(",").map((it) => ({
label: it,
value: it,
})) : [];- 点击新增按钮
点击时设置全局变量 cur_kh 为当前行数据并打开弹窗
弹窗中放入一个文本字段标签内容,点击确定时调用修改单条方法更新当前行数据
更新当前行数据入参为:
$w.page.dataset.state.cur_kh 为当前行信息
$w.input1.value 弹窗中文本内容
({
"bq": $w.page.dataset.state.cur_kh.bq ? `${$w.page.dataset.state.cur_kh.bq},${$w.input1.value}` : $w.input1.value,
"params": {
"filter": ({
"where": {
"$and": [{
"$and": [{
"_id": {
"$eq": $w.page.dataset.state.cur_kh._id
}
}]
}]
},
"relateWhere": {}
})
}
})并调用客户跟踪记录表新增记录,参数结构如下:
({
"xw": "添加标签",
"czr": {
_id: $w.auth.currentUser.userId
},
"jtms": `添加【${$w.input1.value}标签`,
"khId": $w.page.dataset.state.cur_kh._id
})-
关闭弹窗时置空
cur_kh变量 -
点击删除按钮
点击删除按钮时弹窗进行二次确认,成功后获取当前行数据进行更新即可,参数如下:
$w.table1.cell_bq.record 为当前行数据
$w.index_repeater1 为当前点击删除按钮的下标
({
"bq": $w.table1.cell_bq.record.bq.split(',').filter((_, idx) => idx !== $w.index_repeater1).join(','),
"params": {
"filter": ({
"where": {
"$and": [{
"$and": [{
"_id": {
"$eq": $w.table1.cell_bq.record._id
}
}]
}]
},
"relateWhere": {}
})
}
})- 因需要展示客户动态, 因此可以通过 tab 来分别展示基本信息、客户动态
- 当 是否拉黑 为 拉黑 时展示拉黑原因模块展示拉黑相关信息
- 当 客户类型 为 公司 时展示公司模块表单可录入公司信息
- 客户动态用数据列表展示选择数据模型为 客户跟踪记录
数据筛选条件如下:
- 所属员工字段改为人员字段,因人员字段可以接收字符串格式数据展示,但是存储的数据格式为数组,因此在保存时需要对该字段转为字符串,将表单的提交事件参数改为如下:
({
...$w.form2.submitParams,
_id: $w.page.dataset.params._id,
data: {
...$w.form2.submitParams.data,
ssyg: $w.app.common.transSSYG($w.form2.submitParams.data.ssyg)
},
})- 标签字段在详情页中也与列表项中一致
创建一个容器,用文本字段代替label标签,用循环展示组件展示标签内容,后方添加一个按钮用来新增
循环展示组件的内容和外部列表的标签列一致
循环展示组件数据为当前表单项的标签字段数据,因表单中标签数据为字符串,因此需要转成数组赋值给循环组件
表达式内容如下:
!!$w.form2.value.bq ? $w.form2.value.bq.split(',').map(it => ({
"label": it,
"value": it,
})) : []点击新增按钮时,需要弹窗填写新增标签内容,同外部列表操作方式一致 在当前页面中新增一个弹窗容器
点击新增按钮打开弹窗,弹窗中放置一个文本字段
点击弹窗确认按钮时,判断标签内容是否填写,判断成功后执行js脚本,将当前表单的标签字段值重新赋值,加上新增标签内容
js脚本如下:
$w.input4.value 为弹窗中文本内容
({
event
}) => {
const bq = $w.input4.value
if ($w.form2.value.bq) {
$w.input1.setValue({
value: `${$w.form2.value.bq},${bq}`
})
} else {
$w.input1.setValue({
value: bq
})
}
}因新增页面不展示客户动态,因此复制编辑页面一份,删除 tab 组件,只保留主表单子段
修改表单场景为新增
提交事件参数调整如下:
({
...$w.form2.submitParams,
data: {
...$w.form2.submitParams.data,
ssyg: $w.app.common.transSSYG($w.form2.submitParams.data.ssyg)
}
})详情页面和编辑页面一致,因此可以共用编辑页面,列表点击查看跳转详情页面时选择编辑页面, formType 传递为 read
编辑页面中的提交按钮,标签删除、新增按钮均需判断当前表单是否不为只读场景
是否可见表达式如下:
$w.page.dataset.params.formType !== "read"效果如图:
图表展示如下维度客户信息
| 名称 | 图表类型 | 数据源 |
|---|---|---|
| 客户总数 | 统计卡片 | 客户信息/统计 |
| 今日新增客户数 | 统计卡片 | 客户信息/统计 |
| 公海客户数 | 统计卡片 | 客户信息/统计 |
| 黑名单数 | 统计卡片 | 客户信息/统计 |
| 客户总数趋势 | 折线图 | 自定义APIs |
| 新增客户数趋势 | 折线图 | 客户信息 |
| 客户状态分布 | 饼图 | 事件流/自定义变量 |
| 客户阶段分布 | 饼图 | 事件流/自定义变量 |
- 使用统计卡片组件进行渲染,数据源选择统计
- 筛选规则我们去掉拉黑用户
- 字段选择我们选择所有人,统计方式选择计数即可
-
同客户总数,数据源选择客户信息统计
-
过滤条件为:
创建事件大于今天0点,是否拉黑:false
今天0点时间戳new Date(new Date().toLocaleDateString()).getTime()
- 同客户总数,数据源选择客户信息统计
- 过滤条件为:
所属员工:空,是否拉黑:false
-
同客户总数,数据源选择客户信息统计
-
过滤条件为:
是否拉黑:true
该图表主要展示客户信息表数据根据创建时间进行累加,因此需要对表数据进行处理,这里使用自定义APIs开发,这里新加一个APIs方法,使用自定义代码进行处理
具体自定义代码如下:
const dayjs = require("dayjs");
module.exports = async function(params, context) {
const res = await context.callModel({
dataSourceName: "khxx_dpevgv2", // 数据模型名称
methodName: "wedaGetRecordsV2", // 方法名
params: {
select: {
createdAt: true,
},
orderBy: [{
createdAt: "asc",
}, ],
},
});
const formatStemp = (time) => {
return dayjs(time).format("YYYY-MM-DD");
};
const transData = ({
x,
y
}) => {
return {
XLabel: {
Value: x
},
YLabels: [{
Name: "总数",
Value: y
}],
};
};
const getX = (data) => {
return data.XLabel.Value;
};
const getY = (data) => {
return data.YLabels[0].Value;
};
const result = res.records.reduce((arr, itm) => {
if (!arr.length) {
arr.push(
transData({
x: formatStemp(itm.createdAt),
y: 1,
})
);
} else if (getX(arr[arr.length - 1]) === formatStemp(itm.createdAt)) {
arr[arr.length - 1].YLabels[0].Value += 1;
} else {
arr.push(
transData({
x: formatStemp(itm.createdAt),
y: getY(arr[arr.length - 1]) + 1,
})
);
}
return arr;
}, []);
return {
result
};
};该图标需要根据创建时间按天累计进行展示,因此直接使用折线图即可,选择x轴维度为创建时间,y轴统计方式为计数
该图表需要对数据的状态、客户阶段进行翻译 (该两字段为枚举),然后对客户信息表数据根据状态、阶段进行统计计数
操作步骤如下:
- 当前页面新建对象变量:pieChatData
-
当前页面新建 事件流:setPieChatData
-
setPieChatData 中第一步获取 客户信息表 全量数据 因这里只需要 状态、客户阶段 字段,因此配置 关联表查询选择 (对象)如下:
{
"zt": true,
"khjd": true
}- 获取数据成功后执行如下js代码:
({
event
}) => {
const arr = event.detail.records;
const transData = ({
x,
y
}) => {
return {
XLabel: {
Value: x
},
YLabels: [{
Name: "总数",
Value: y
}],
};
};
const formatVal = (val, code) => {
return $w.app.utils.formatEnum(val, code, $w.app);
};
const getChatVal = (type, code) => {
return Object.entries(newArr[type]).map(([key, value]) => {
const newKey = key === "other" ? "未知" : formatVal(key, code);
return transData({
x: newKey,
y: value
});
});
};
const newArr = arr.reduce(
(obj, itm) => {
const ztKey = itm.zt || "other";
obj["zt"][ztKey] = obj["zt"][ztKey] ? obj["zt"][ztKey] + 1 : 1;
const jdKey = itm.khjd || "other";
obj["jd"][jdKey] = obj["jd"][jdKey] ? obj["jd"][jdKey] + 1 : 1;
return obj;
}, {
zt: {},
jd: {},
}
);
const obj = {
zt: getChatVal("zt", "scrm_status"),
jd: getChatVal("jd", "scrm_followStatus"),
};
$w.page.dataset.state.pieChatData = obj;
};- 当前页面显示时调用
setPieChatData
该页面和客户管理页面相似,只是数据过滤条件不同,功能按钮不同,因此复制客户管理页面后进行修改
-
过滤条件为
是否拉黑: true -
全局按钮包含 批量移出客户、批量删除、导出、导入
-
操作按钮包含 查看、编辑、移出、删除
同客户管理拉黑
点击 移出、批量移出 时弹窗进行移出原因填写,确认后修改当前项数据 是否拉黑:false 、 拉黑原因: 空 、 拉黑时间: 空 、 拉黑操作人: 空 等信息
逻辑线如下:
该页面和客户管理页面相似,只是数据过滤条件不同,功能按钮不同,因此复制客户管理页面后进行修改
-
过滤条件为
是否拉黑: false,所属员工:空 -
全局按钮包含 新建、批量删除、批量转移客户、批量领取、导出、导入
-
操作按钮包含 查看、领取、删除
同客户管理批量转移
进入左侧应用设置,选择访问控制,其中选择登录后访问,选择账号密码登录
我们在这里添加一个退出登录功能,可以在文档中心查询退出登录功能如何实现
这里我们点进去看到可以通过点击按钮执行js脚本完成退出登录功能
如法炮制,我们在编辑器左下角代码区的全局区域单击 + 号,在弹出层中选择新建JavaScript方法
在弹窗的代码编辑器中复制上述文档中提示的脚本粘贴进去,修改方法名为 signout 点击右上角保存
继续我们删除设计器左下角产品文档文本组件,修改左侧icon,并添加一个按钮,修改按钮名称以及类型
点击右下角事件中的点击,选择JavaScript方法
选择自定义方法signout
点击事件框右上角保存即可
上述配置完成后,即可发布到体验版查看,发布应用
发布成功后会提供体验版入口,进入即可看到页面
首页
客户管理






































































































