新版数智员工
This commit is contained in:
@@ -2,92 +2,879 @@
|
||||
|
||||
namespace app\store\controller;
|
||||
|
||||
use app\common\controller\Api;
|
||||
use app\common\model\TrafficPoolCompany;
|
||||
use think\Db;
|
||||
use think\facade\Log;
|
||||
|
||||
/**
|
||||
* 客户管理控制器
|
||||
*/
|
||||
class CustomerController extends Api
|
||||
class CustomerController extends BaseController
|
||||
{
|
||||
protected $noNeedLogin = [];
|
||||
protected $noNeedRight = ['*'];
|
||||
|
||||
/**
|
||||
* 获取客户列表
|
||||
* GET /v2/store/customers
|
||||
*
|
||||
* @return \think\Response
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
$params = $this->request->param();
|
||||
|
||||
// 获取分页参数
|
||||
$page = isset($params['page']) ? intval($params['page']) : 1;
|
||||
$pageSize = isset($params['pageSize']) ? intval($params['pageSize']) : 10;
|
||||
$userInfo = request()->userInfo;
|
||||
|
||||
$where = [];
|
||||
// 必要的查询条件
|
||||
$userId = $userInfo['id'];
|
||||
$companyId = $userInfo['companyId'];
|
||||
|
||||
if (empty($userId) || empty($companyId)) {
|
||||
return errorJson('缺少必要参数');
|
||||
}
|
||||
|
||||
// 构建查询条件
|
||||
$deviceIds = Db::name('device_user')->where(['userId' => $userId, 'companyId' => $companyId])->order('id DESC')->column('deviceId');
|
||||
if (empty($deviceIds)) {
|
||||
return errorJson('设备不存在');
|
||||
}
|
||||
$wechatIds = [];
|
||||
foreach ($deviceIds as $deviceId) {
|
||||
$wechatIds[] = Db::name('device_wechat_login')
|
||||
->where(['deviceId' => $deviceId])
|
||||
->order('id DESC')
|
||||
->value('wechatId');
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 搜索条件
|
||||
if (!empty($params['keyword'])) {
|
||||
$where['alias|nickname|wechatId'] = ['like', '%' . $params['keyword'] . '%'];
|
||||
}
|
||||
// if (!empty($params['email'])) {
|
||||
// $where['wa.bindEmail'] = ['like', '%' . $params['email'] . '%'];
|
||||
// }
|
||||
// if (!empty($params['name'])) {
|
||||
// $where['wa.accountRealName|wa.accountUserName|wa.nickname'] = ['like', '%' . $params['name'] . '%'];
|
||||
// }
|
||||
|
||||
// 构建查询
|
||||
$query = Db::table('s2_wechat_friend')
|
||||
->where($where)
|
||||
->whereIn('ownerWechatId',$wechatIds)
|
||||
->group('wechatId'); // 防止重复数据
|
||||
try {
|
||||
$userId = $this->userInfo['id'] ?? 0;
|
||||
$companyId = $this->userInfo['companyId'] ?? 0;
|
||||
|
||||
// 克隆查询对象,用于计算总数
|
||||
$countQuery = clone $query;
|
||||
$total = $countQuery->count();
|
||||
|
||||
// 获取分页数据
|
||||
$list = $query->page($page, $pageSize)
|
||||
->order('id DESC')
|
||||
->select();
|
||||
|
||||
|
||||
// 格式化数据
|
||||
foreach ($list as &$item) {
|
||||
$item['labels'] = json_decode($item['labels'], true);
|
||||
$item['createTime'] = date('Y-m-d H:i:s', $item['createTime']);
|
||||
if (empty($userId) || empty($companyId)) {
|
||||
return json(['code' => 401, 'msg' => '请先登录']);
|
||||
}
|
||||
|
||||
// 获取设备信息
|
||||
$device = $this->device;
|
||||
if (empty($device) || empty($device['wechatId'])) {
|
||||
return json(['code' => 404, 'msg' => '设备未绑定微信']);
|
||||
}
|
||||
|
||||
$wechatId = $device['wechatId'];
|
||||
|
||||
// 获取微信账号ID
|
||||
$wechatAccount = Db::table('s2_wechat_account')
|
||||
->where('wechatId', $wechatId)
|
||||
->field('id')
|
||||
->find();
|
||||
|
||||
if (empty($wechatAccount)) {
|
||||
return json(['code' => 404, 'msg' => '微信账号不存在']);
|
||||
}
|
||||
|
||||
$accountId = $wechatAccount['id'];
|
||||
|
||||
// 分页参数
|
||||
$page = intval($this->request->param('page', 1));
|
||||
$limit = intval($this->request->param('limit', 10));
|
||||
$pageSize = intval($this->request->param('pageSize', 10));
|
||||
|
||||
if ($page <= 0) $page = 1;
|
||||
if ($limit <= 0) $limit = $pageSize > 0 ? $pageSize : 10;
|
||||
if ($limit > 100) $limit = 100;
|
||||
|
||||
// 搜索关键词
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
|
||||
// 筛选条件
|
||||
$status = $this->request->param('status', ''); // 状态:潜在、活跃、沉默、流失
|
||||
$value = $this->request->param('value', ''); // 价值:高、中、低
|
||||
$lifecycle = $this->request->param('lifecycle', ''); // 生命周期
|
||||
|
||||
// 构建查询条件
|
||||
// 从流量池公司表查询,关联流量池总表和微信好友表
|
||||
// 注意:s2_wechat_friend 表没有 ck_ 前缀,使用数组形式 join 可以避免自动添加前缀
|
||||
$query = Db::name('traffic_pool_company')
|
||||
->alias('tpc')
|
||||
->join('traffic_pool tp', 'tp.id = tpc.poolId', 'left')
|
||||
->join(['s2_wechat_friend' => 'wf'], 'wf.wechatId = tp.wechatId AND wf.ownerWechatId = \'' . $wechatId . '\'', 'left')
|
||||
->where([
|
||||
['tpc.companyId', '=', $companyId],
|
||||
['tpc.ownerAccountId', '=', $accountId], // 归属当前微信账号
|
||||
['tpc.status', '=', TrafficPoolCompany::STATUS_NORMAL], // 正常状态
|
||||
]);
|
||||
|
||||
// 关键词搜索(昵称、微信号、手机号)
|
||||
if (!empty($keyword)) {
|
||||
$query->where(function($query) use ($keyword) {
|
||||
$query->where('tp.nickname', 'like', '%' . $keyword . '%')
|
||||
->whereOr('tp.wechatAlias', 'like', '%' . $keyword . '%')
|
||||
->whereOr('tp.mobile', 'like', '%' . $keyword . '%')
|
||||
->whereOr('tpc.realName', 'like', '%' . $keyword . '%')
|
||||
->whereOr('tpc.phone', 'like', '%' . $keyword . '%');
|
||||
});
|
||||
}
|
||||
|
||||
// 状态筛选(根据生命周期)
|
||||
if (!empty($lifecycle)) {
|
||||
$lifecycleMap = [
|
||||
'潜在' => TrafficPoolCompany::LIFECYCLE_NEW,
|
||||
'活跃' => TrafficPoolCompany::LIFECYCLE_FOLLOWING,
|
||||
'沉默' => TrafficPoolCompany::LIFECYCLE_SILENT,
|
||||
'流失' => TrafficPoolCompany::LIFECYCLE_LOST,
|
||||
];
|
||||
if (isset($lifecycleMap[$lifecycle])) {
|
||||
$query->where('tpc.lifecycle', '=', $lifecycleMap[$lifecycle]);
|
||||
}
|
||||
}
|
||||
|
||||
// 价值筛选(根据意向度或等级)
|
||||
if (!empty($value)) {
|
||||
$valueMap = [
|
||||
'高' => TrafficPoolCompany::INTENTION_HIGH,
|
||||
'中' => TrafficPoolCompany::INTENTION_MEDIUM,
|
||||
'低' => TrafficPoolCompany::INTENTION_LOW,
|
||||
];
|
||||
if (isset($valueMap[$value])) {
|
||||
$query->where('tpc.intentionLevel', '=', $valueMap[$value]);
|
||||
}
|
||||
}
|
||||
|
||||
// 统计总数
|
||||
$total = $query->count();
|
||||
|
||||
// 获取列表数据
|
||||
$list = $query->field('tpc.id,tpc.poolId,tpc.companyId,tpc.ownerAccountId,tpc.realName,tpc.phone,tpc.email,tpc.lifecycle,tpc.intentionLevel,tpc.level,tpc.remark,tpc.createTime,tp.nickname,tp.avatar,tp.wechatId,tp.wechatAlias,tp.mobile,tp.gender,tp.region,tp.signature,wf.id as friendId,wf.alias as friendAlias,wf.nickname as friendNickname')
|
||||
->order('tpc.id desc')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
// 格式化数据
|
||||
$result = [];
|
||||
foreach ($list as $item) {
|
||||
// 获取标签
|
||||
$tags = Db::name('traffic_pool_tag')
|
||||
->where([
|
||||
['poolCompanyId', '=', $item['id']],
|
||||
['isDel', '=', 0]
|
||||
])
|
||||
->column('tagName');
|
||||
|
||||
// 获取最后互动时间(从行为记录表)
|
||||
$lastBehavior = Db::name('traffic_pool_behavior')
|
||||
->where('poolCompanyId', $item['id'])
|
||||
->order('behaviorTime desc')
|
||||
->find();
|
||||
|
||||
$lastContact = '';
|
||||
if (!empty($lastBehavior) && !empty($lastBehavior['behaviorTime'])) {
|
||||
$lastContact = date('Y-m-d H:i:s', intval($lastBehavior['behaviorTime']));
|
||||
}
|
||||
|
||||
// 获取价值评估(从RFM或估值相关表,这里先使用模拟数据)
|
||||
$valuation = $this->calculateCustomerValuation($item['id']);
|
||||
|
||||
// 状态映射
|
||||
$lifecycleMap = [
|
||||
TrafficPoolCompany::LIFECYCLE_NEW => '潜在',
|
||||
TrafficPoolCompany::LIFECYCLE_FOLLOWING => '活跃',
|
||||
TrafficPoolCompany::LIFECYCLE_CONVERTED => '已成交',
|
||||
TrafficPoolCompany::LIFECYCLE_SILENT => '沉默',
|
||||
TrafficPoolCompany::LIFECYCLE_LOST => '流失',
|
||||
];
|
||||
|
||||
// 价值映射
|
||||
$intentionMap = [
|
||||
TrafficPoolCompany::INTENTION_HIGH => '高',
|
||||
TrafficPoolCompany::INTENTION_MEDIUM => '中',
|
||||
TrafficPoolCompany::INTENTION_LOW => '低',
|
||||
TrafficPoolCompany::INTENTION_UNKNOWN => '低',
|
||||
];
|
||||
|
||||
$result[] = [
|
||||
'id' => intval($item['id']),
|
||||
'poolCompanyId' => intval($item['id']),
|
||||
'name' => $item['realName'] ?? $item['nickname'] ?? '未知',
|
||||
'nickname' => $item['nickname'] ?? '',
|
||||
'wechatId' => $item['wechatAlias'] ?? $item['wechatId'] ?? '',
|
||||
'avatar' => $item['avatar'] ?? '',
|
||||
'phone' => $item['phone'] ?? $item['mobile'] ?? '',
|
||||
'email' => $item['email'] ?? '',
|
||||
'status' => $lifecycleMap[$item['lifecycle'] ?? TrafficPoolCompany::LIFECYCLE_NEW] ?? '潜在',
|
||||
'value' => $intentionMap[$item['intentionLevel'] ?? TrafficPoolCompany::INTENTION_UNKNOWN] ?? '低',
|
||||
'tags' => $tags ?: [],
|
||||
'lastContact' => $lastContact,
|
||||
'nextFollow' => !empty($item['nextFollowTime']) && is_numeric($item['nextFollowTime'])
|
||||
? date('Y-m-d', intval($item['nextFollowTime']))
|
||||
: '',
|
||||
'notes' => $item['remark'] ?? '',
|
||||
'addedDate' => !empty($item['createTime']) && is_numeric($item['createTime'])
|
||||
? date('Y-m-d', intval($item['createTime']))
|
||||
: '',
|
||||
'valuation' => $valuation,
|
||||
];
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'list' => $result,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'limit' => $limit
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('获取客户列表失败: ' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '获取失败:' . $e->getMessage()]);
|
||||
}
|
||||
unset($item);
|
||||
|
||||
return successJson([
|
||||
'list' => $list,
|
||||
'total' => $total
|
||||
], '获取成功');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户详情
|
||||
* GET /v2/store/customers/:id
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function detail()
|
||||
{
|
||||
try {
|
||||
$userId = $this->userInfo['id'] ?? 0;
|
||||
$companyId = $this->userInfo['companyId'] ?? 0;
|
||||
|
||||
if (empty($userId) || empty($companyId)) {
|
||||
return json(['code' => 401, 'msg' => '请先登录']);
|
||||
}
|
||||
|
||||
// 获取设备信息
|
||||
$device = $this->device;
|
||||
if (empty($device) || empty($device['wechatId'])) {
|
||||
return json(['code' => 404, 'msg' => '设备未绑定微信']);
|
||||
}
|
||||
|
||||
$wechatId = $device['wechatId'];
|
||||
|
||||
// 获取微信账号ID
|
||||
$wechatAccount = Db::table('s2_wechat_account')
|
||||
->where('wechatId', $wechatId)
|
||||
->field('id')
|
||||
->find();
|
||||
|
||||
if (empty($wechatAccount)) {
|
||||
return json(['code' => 404, 'msg' => '微信账号不存在']);
|
||||
}
|
||||
|
||||
$accountId = $wechatAccount['id'];
|
||||
|
||||
// 获取客户ID
|
||||
$customerId = intval($this->request->param('id', 0));
|
||||
if (empty($customerId)) {
|
||||
return json(['code' => 400, 'msg' => '客户ID不能为空']);
|
||||
}
|
||||
|
||||
// 查询客户详情
|
||||
// 注意:s2_wechat_friend 表没有 ck_ 前缀,使用数组形式 join 可以避免自动添加前缀
|
||||
$customer = Db::name('traffic_pool_company')
|
||||
->alias('tpc')
|
||||
->join('traffic_pool tp', 'tp.id = tpc.poolId', 'left')
|
||||
->join(['s2_wechat_friend' => 'wf'], 'wf.wechatId = tp.wechatId AND wf.ownerWechatId = \'' . $wechatId . '\'', 'left')
|
||||
->where([
|
||||
['tpc.id', '=', $customerId],
|
||||
['tpc.companyId', '=', $companyId],
|
||||
['tpc.ownerAccountId', '=', $accountId],
|
||||
])
|
||||
->field('tpc.*,tp.*,wf.id as friendId,wf.alias as friendAlias,wf.nickname as friendNickname')
|
||||
->find();
|
||||
|
||||
if (empty($customer)) {
|
||||
return json(['code' => 404, 'msg' => '客户不存在']);
|
||||
}
|
||||
|
||||
// 获取标签
|
||||
$tags = Db::name('traffic_pool_tag')
|
||||
->where([
|
||||
['poolCompanyId', '=', $customerId],
|
||||
['isDel', '=', 0]
|
||||
])
|
||||
->column('tagName');
|
||||
|
||||
// 获取流量池标签(系统标签或微信标签)
|
||||
// 注意:从表结构看,isSystem字段在tagDefineId关联的标签定义表中
|
||||
// 这里先获取所有标签,后续可以根据tagType区分
|
||||
$allTags = Db::name('traffic_pool_tag')
|
||||
->alias('tpt')
|
||||
->join('traffic_pool_tag_define tptd', 'tpt.tagDefineId = tptd.id', 'left')
|
||||
->where([
|
||||
['tpt.poolCompanyId', '=', $customerId],
|
||||
['tpt.isDel', '=', 0]
|
||||
])
|
||||
->field('tpt.tagName,tptd.isSystem')
|
||||
->select();
|
||||
|
||||
$trafficPoolTags = [];
|
||||
foreach ($allTags as $tag) {
|
||||
// 系统标签或微信标签(tagType=1)作为流量池标签
|
||||
if (!empty($tag['isSystem']) || (!empty($tag['tagType']) && $tag['tagType'] == 1)) {
|
||||
$trafficPoolTags[] = $tag['tagName'];
|
||||
}
|
||||
}
|
||||
|
||||
// 获取来源信息
|
||||
$sources = Db::name('traffic_pool_source')
|
||||
->where('poolCompanyId', $customerId)
|
||||
->order('createTime desc')
|
||||
->select();
|
||||
|
||||
$sourceChannel = '未知';
|
||||
$addTime = '';
|
||||
if (!empty($sources)) {
|
||||
$firstSource = $sources[0];
|
||||
$sourceChannel = $firstSource['sourceName'] ?? '未知';
|
||||
$addTime = !empty($firstSource['createTime']) && is_numeric($firstSource['createTime'])
|
||||
? date('Y-m-d', intval($firstSource['createTime']))
|
||||
: '';
|
||||
}
|
||||
|
||||
// 获取互动统计
|
||||
$interactionStats = $this->getInteractionStats($customerId);
|
||||
|
||||
// 获取价值评估
|
||||
$valueEvaluation = $this->getValueEvaluation($customerId);
|
||||
|
||||
// 获取用户旅程(最近记录)
|
||||
$journey = $this->getCustomerJourney($customerId, 10);
|
||||
|
||||
// 获取消费偏好(从行为记录分析)
|
||||
$preferences = $this->getCustomerPreferences($customerId);
|
||||
|
||||
// 状态映射
|
||||
$lifecycleMap = [
|
||||
TrafficPoolCompany::LIFECYCLE_NEW => '潜在',
|
||||
TrafficPoolCompany::LIFECYCLE_FOLLOWING => '活跃',
|
||||
TrafficPoolCompany::LIFECYCLE_CONVERTED => '已成交',
|
||||
TrafficPoolCompany::LIFECYCLE_SILENT => '沉默',
|
||||
TrafficPoolCompany::LIFECYCLE_LOST => '流失',
|
||||
];
|
||||
|
||||
$conversionStatus = $lifecycleMap[$customer['lifecycle'] ?? TrafficPoolCompany::LIFECYCLE_NEW] ?? '潜在';
|
||||
|
||||
// 生成首字母
|
||||
$name = $customer['realName'] ?? $customer['nickname'] ?? '未知';
|
||||
$initials = mb_substr($name, 0, 1, 'UTF-8');
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
// 基础信息
|
||||
'id' => intval($customer['id']),
|
||||
'poolCompanyId' => intval($customer['id']),
|
||||
'initials' => $initials,
|
||||
|
||||
// 好友概览
|
||||
'nickname' => $customer['nickname'] ?? '',
|
||||
'remarkName' => $customer['realName'] ?? '',
|
||||
'wechatId' => $customer['wechatAlias'] ?? $customer['wechatId'] ?? '',
|
||||
'wechatPhone' => $customer['mobile'] ?? '',
|
||||
'wechatLocation' => $customer['region'] ?? '',
|
||||
'avatar' => $customer['avatar'] ?? '',
|
||||
'conversionStatus' => $conversionStatus,
|
||||
'sourceChannel' => $sourceChannel,
|
||||
'addTime' => $addTime,
|
||||
|
||||
// 基础信息
|
||||
'realName' => $customer['realName'] ?? '',
|
||||
'sex' => $this->getGenderText($customer['gender'] ?? 0),
|
||||
'age' => $this->calculateAge($customer['birthday'] ?? ''),
|
||||
'personalPhone' => $customer['phone'] ?? '',
|
||||
'email' => $customer['email'] ?? '',
|
||||
'idNumber' => $this->maskIdNumber($customer['idCard'] ?? ''),
|
||||
'address' => $customer['address'] ?? '',
|
||||
|
||||
// 标签
|
||||
'tags' => $tags ?: [],
|
||||
'trafficPoolTags' => $trafficPoolTags ?: [],
|
||||
|
||||
// 互动统计
|
||||
'interactionStats' => $interactionStats,
|
||||
|
||||
// 价值评估
|
||||
'valueEvaluation' => $valueEvaluation,
|
||||
'valuationRank' => 'TOP 8%', // 需要计算
|
||||
'valuationTrend' => '+12%', // 需要计算
|
||||
|
||||
// 用户旅程
|
||||
'journey' => $journey,
|
||||
|
||||
// 消费偏好
|
||||
'preferences' => $preferences,
|
||||
|
||||
// AI预测(需要实现)
|
||||
'aiProfile' => [
|
||||
'summary' => '该用户为典型的高净值客户,消费频率高且偏好高端产品。',
|
||||
'predictions' => [
|
||||
'预计未来7天内有85%概率下单',
|
||||
'流失风险极低(5%),建议通过会员活动维持粘性',
|
||||
'最佳触达时间:工作日12:00-14:00或周末下午'
|
||||
]
|
||||
],
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('获取客户详情失败: ' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '获取失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新客户信息
|
||||
* PUT /v2/store/customers/:id
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
try {
|
||||
$userId = $this->userInfo['id'] ?? 0;
|
||||
$companyId = $this->userInfo['companyId'] ?? 0;
|
||||
|
||||
if (empty($userId) || empty($companyId)) {
|
||||
return json(['code' => 401, 'msg' => '请先登录']);
|
||||
}
|
||||
|
||||
// 获取设备信息
|
||||
$device = $this->device;
|
||||
if (empty($device) || empty($device['wechatId'])) {
|
||||
return json(['code' => 404, 'msg' => '设备未绑定微信']);
|
||||
}
|
||||
|
||||
$wechatId = $device['wechatId'];
|
||||
|
||||
// 获取微信账号ID
|
||||
$wechatAccount = Db::table('s2_wechat_account')
|
||||
->where('wechatId', $wechatId)
|
||||
->field('id')
|
||||
->find();
|
||||
|
||||
if (empty($wechatAccount)) {
|
||||
return json(['code' => 404, 'msg' => '微信账号不存在']);
|
||||
}
|
||||
|
||||
$accountId = $wechatAccount['id'];
|
||||
|
||||
// 获取客户ID
|
||||
$customerId = intval($this->request->param('id', 0));
|
||||
if (empty($customerId)) {
|
||||
return json(['code' => 400, 'msg' => '客户ID不能为空']);
|
||||
}
|
||||
|
||||
// 验证客户是否存在且归属当前账号
|
||||
$customer = Db::name('traffic_pool_company')
|
||||
->where([
|
||||
['id', '=', $customerId],
|
||||
['companyId', '=', $companyId],
|
||||
['ownerAccountId', '=', $accountId],
|
||||
])
|
||||
->find();
|
||||
|
||||
if (empty($customer)) {
|
||||
return json(['code' => 404, 'msg' => '客户不存在']);
|
||||
}
|
||||
|
||||
// 获取更新参数
|
||||
$updateType = $this->request->param('updateType', ''); // wechat, personal, tags
|
||||
|
||||
$updateData = [];
|
||||
$updateFields = [];
|
||||
|
||||
// 更新微信资料
|
||||
if ($updateType === 'wechat' || $this->request->has('remarkName')) {
|
||||
$remarkName = $this->request->param('remarkName', '');
|
||||
if ($remarkName !== '') {
|
||||
$updateData['realName'] = $remarkName; // 备注名存储在realName字段
|
||||
$updateFields[] = '备注名';
|
||||
}
|
||||
}
|
||||
|
||||
// 更新基础信息
|
||||
if ($updateType === 'personal') {
|
||||
$realName = $this->request->param('realName', '');
|
||||
$sex = $this->request->param('sex', '');
|
||||
$age = $this->request->param('age', '');
|
||||
$phone = $this->request->param('phone', '');
|
||||
$email = $this->request->param('email', '');
|
||||
$idNumber = $this->request->param('idNumber', '');
|
||||
$address = $this->request->param('address', '');
|
||||
|
||||
if ($realName !== '') {
|
||||
$updateData['realName'] = $realName;
|
||||
$updateFields[] = '姓名';
|
||||
}
|
||||
if ($sex !== '') {
|
||||
$updateData['gender'] = $sex === '男' ? 1 : ($sex === '女' ? 2 : 0);
|
||||
$updateFields[] = '性别';
|
||||
}
|
||||
if ($age !== '') {
|
||||
// 根据年龄计算生日(简化处理)
|
||||
$birthYear = date('Y') - intval($age);
|
||||
$updateData['birthday'] = $birthYear . '-01-01';
|
||||
$updateFields[] = '年龄';
|
||||
}
|
||||
if ($phone !== '') {
|
||||
$updateData['phone'] = $phone;
|
||||
$updateFields[] = '手机号';
|
||||
}
|
||||
if ($email !== '') {
|
||||
$updateData['email'] = $email;
|
||||
$updateFields[] = '邮箱';
|
||||
}
|
||||
if ($idNumber !== '') {
|
||||
$updateData['idCard'] = $idNumber;
|
||||
$updateFields[] = '身份证号';
|
||||
}
|
||||
if ($address !== '') {
|
||||
$updateData['address'] = $address;
|
||||
$updateFields[] = '住址';
|
||||
}
|
||||
}
|
||||
|
||||
// 更新标签
|
||||
if ($updateType === 'tags') {
|
||||
$tags = $this->request->param('tags', []);
|
||||
if (is_array($tags)) {
|
||||
// 获取客户信息(用于获取identifier和companyId)
|
||||
$customerInfo = Db::name('traffic_pool_company')
|
||||
->where('id', $customerId)
|
||||
->field('identifier,companyId')
|
||||
->find();
|
||||
|
||||
if (!empty($customerInfo)) {
|
||||
// 软删除旧标签(只删除站内标签,保留微信标签和系统标签)
|
||||
// 通过关联标签定义表判断是否为站内标签
|
||||
Db::name('traffic_pool_tag')
|
||||
->alias('tpt')
|
||||
->join('traffic_pool_tag_define tptd', 'tpt.tagDefineId = tptd.id', 'left')
|
||||
->where([
|
||||
['tpt.poolCompanyId', '=', $customerId],
|
||||
['tptd.tagType', '=', 2], // 站内标签
|
||||
['tpt.isDel', '=', 0]
|
||||
])
|
||||
->update([
|
||||
'tpt.isDel' => 1,
|
||||
'tpt.deleteTime' => time()
|
||||
]);
|
||||
|
||||
// 添加新标签(站内标签)
|
||||
foreach ($tags as $tag) {
|
||||
if (!empty($tag)) {
|
||||
// 查找或创建标签定义
|
||||
$tagDefine = Db::name('traffic_pool_tag_define')
|
||||
->where([
|
||||
['companyId', 'in', [$companyId, 0]],
|
||||
['tagName', '=', $tag],
|
||||
['tagType', '=', 2], // 站内标签
|
||||
['isDel', '=', 0]
|
||||
])
|
||||
->order('companyId desc') // 优先使用公司自定义标签
|
||||
->find();
|
||||
|
||||
if (empty($tagDefine)) {
|
||||
// 创建标签定义
|
||||
$tagDefineId = Db::name('traffic_pool_tag_define')->insertGetId([
|
||||
'companyId' => $companyId,
|
||||
'tagType' => 2, // 站内标签
|
||||
'tagCode' => 'custom_' . time() . '_' . rand(1000, 9999),
|
||||
'tagName' => $tag,
|
||||
'isSystem' => 0,
|
||||
'status' => 1,
|
||||
'createTime' => time(),
|
||||
]);
|
||||
} else {
|
||||
$tagDefineId = $tagDefine['id'];
|
||||
}
|
||||
|
||||
// 检查标签是否已存在
|
||||
$existTag = Db::name('traffic_pool_tag')
|
||||
->where([
|
||||
['poolCompanyId', '=', $customerId],
|
||||
['tagDefineId', '=', $tagDefineId],
|
||||
['isDel', '=', 0]
|
||||
])
|
||||
->find();
|
||||
|
||||
if (empty($existTag)) {
|
||||
Db::name('traffic_pool_tag')->insert([
|
||||
'poolCompanyId' => $customerId,
|
||||
'identifier' => $customerInfo['identifier'],
|
||||
'companyId' => $customerInfo['companyId'],
|
||||
'tagDefineId' => $tagDefineId,
|
||||
'tagType' => 2, // 站内标签
|
||||
'tagName' => $tag,
|
||||
'source' => 1, // 手动
|
||||
'operatorId' => $userId,
|
||||
'createTime' => time(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$updateFields[] = '标签';
|
||||
}
|
||||
}
|
||||
|
||||
// 更新客户信息
|
||||
if (!empty($updateData)) {
|
||||
$updateData['updateTime'] = time();
|
||||
Db::name('traffic_pool_company')
|
||||
->where('id', $customerId)
|
||||
->update($updateData);
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '更新成功',
|
||||
'data' => [
|
||||
'updatedFields' => $updateFields
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('更新客户信息失败: ' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '更新失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算客户估值
|
||||
*
|
||||
* @param int $poolCompanyId 客户ID
|
||||
* @return int
|
||||
*/
|
||||
private function calculateCustomerValuation($poolCompanyId)
|
||||
{
|
||||
// TODO: 实现真实的估值计算逻辑
|
||||
// 可以从订单表、行为记录表等计算
|
||||
return 50000; // 模拟数据
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取互动统计
|
||||
*
|
||||
* @param int $poolCompanyId 客户ID
|
||||
* @return array
|
||||
*/
|
||||
private function getInteractionStats($poolCompanyId)
|
||||
{
|
||||
// 统计聊天消息数
|
||||
$chatCount = Db::name('traffic_pool_behavior')
|
||||
->where([
|
||||
['poolCompanyId', '=', $poolCompanyId],
|
||||
['behaviorType', '=', 1] // 发送消息
|
||||
])
|
||||
->count();
|
||||
|
||||
// 统计朋友圈互动数
|
||||
$momentsCount = Db::name('traffic_pool_behavior')
|
||||
->where([
|
||||
['poolCompanyId', '=', $poolCompanyId],
|
||||
['behaviorType', 'in', [9, 10]] // 点赞朋友圈、评论朋友圈
|
||||
])
|
||||
->count();
|
||||
|
||||
// 统计红包转账总额(从行为记录中获取)
|
||||
$redPacketTotal = Db::name('traffic_pool_behavior')
|
||||
->where([
|
||||
['poolCompanyId', '=', $poolCompanyId],
|
||||
['behaviorType', '=', 7] // 支付
|
||||
])
|
||||
->sum('amount');
|
||||
$redPacketTotal = round(floatval($redPacketTotal ?? 0), 2);
|
||||
|
||||
// 计算活跃度评分(简化计算)
|
||||
$activeScore = min(100, ($chatCount * 2 + $momentsCount * 3 + $redPacketTotal / 10));
|
||||
|
||||
// 获取最后互动时间
|
||||
$lastBehavior = Db::name('traffic_pool_behavior')
|
||||
->where('poolCompanyId', $poolCompanyId)
|
||||
->order('behaviorTime desc')
|
||||
->find();
|
||||
|
||||
$lastInteraction = '从未互动';
|
||||
if (!empty($lastBehavior) && !empty($lastBehavior['behaviorTime'])) {
|
||||
$time = intval($lastBehavior['behaviorTime']);
|
||||
$diff = time() - $time;
|
||||
if ($diff < 3600) {
|
||||
$lastInteraction = '刚刚';
|
||||
} elseif ($diff < 86400) {
|
||||
$lastInteraction = '今天 ' . date('H:i', $time);
|
||||
} elseif ($diff < 172800) {
|
||||
$lastInteraction = '昨天 ' . date('H:i', $time);
|
||||
} else {
|
||||
$lastInteraction = date('Y-m-d H:i', $time);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'lastInteraction' => $lastInteraction,
|
||||
'chatCount' => intval($chatCount),
|
||||
'momentsCount' => intval($momentsCount),
|
||||
'redPacketTotal' => number_format($redPacketTotal, 2),
|
||||
'activeScore' => intval($activeScore)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取价值评估
|
||||
*
|
||||
* @param int $poolCompanyId 客户ID
|
||||
* @return array
|
||||
*/
|
||||
private function getValueEvaluation($poolCompanyId)
|
||||
{
|
||||
// TODO: 实现真实的价值评估计算
|
||||
// 可以从RFM模型、CLV模型、社交裂变模型等计算
|
||||
|
||||
return [
|
||||
'totalValuation' => 58600,
|
||||
'models' => [
|
||||
[
|
||||
'name' => 'RFM 贡献模型',
|
||||
'value' => 52000,
|
||||
'weight' => 0.5,
|
||||
'score' => 92
|
||||
],
|
||||
[
|
||||
'name' => 'CLV 终身价值模型',
|
||||
'value' => 78000,
|
||||
'weight' => 0.3,
|
||||
'score' => 88
|
||||
],
|
||||
[
|
||||
'name' => '社交/裂变模型',
|
||||
'value' => 15000,
|
||||
'weight' => 0.2,
|
||||
'score' => 75
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户旅程
|
||||
*
|
||||
* @param int $poolCompanyId 客户ID
|
||||
* @param int $limit 限制数量
|
||||
* @return array
|
||||
*/
|
||||
private function getCustomerJourney($poolCompanyId, $limit = 10)
|
||||
{
|
||||
// 从行为记录表获取
|
||||
$behaviors = Db::name('traffic_pool_behavior')
|
||||
->where('poolCompanyId', $poolCompanyId)
|
||||
->order('behaviorTime desc')
|
||||
->limit($limit)
|
||||
->select();
|
||||
|
||||
$journey = [];
|
||||
$typeMap = [
|
||||
1 => '发送消息',
|
||||
2 => '接收消息',
|
||||
3 => '浏览',
|
||||
4 => '点击',
|
||||
5 => '咨询',
|
||||
6 => '下单',
|
||||
7 => '支付',
|
||||
8 => '退款',
|
||||
9 => '点赞朋友圈',
|
||||
10 => '评论朋友圈',
|
||||
];
|
||||
|
||||
foreach ($behaviors as $behavior) {
|
||||
$type = $typeMap[$behavior['behaviorType']] ?? '未知行为';
|
||||
$content = $behavior['behaviorName'] ?? $type;
|
||||
if (!empty($behavior['targetName'])) {
|
||||
$content .= ': ' . $behavior['targetName'];
|
||||
}
|
||||
|
||||
$journey[] = [
|
||||
'type' => $type,
|
||||
'content' => $content,
|
||||
'time' => !empty($behavior['behaviorTime']) && is_numeric($behavior['behaviorTime'])
|
||||
? date('Y-m-d H:i:s', intval($behavior['behaviorTime']))
|
||||
: '',
|
||||
'source' => '存客宝',
|
||||
'actionType' => $this->getActionType($behavior['behaviorType']),
|
||||
'amount' => !empty($behavior['amount']) && floatval($behavior['amount']) > 0
|
||||
? '¥' . number_format(floatval($behavior['amount']), 2)
|
||||
: '',
|
||||
];
|
||||
}
|
||||
|
||||
return $journey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取行为类型
|
||||
*
|
||||
* @param int $behaviorType 行为类型
|
||||
* @return string
|
||||
*/
|
||||
private function getActionType($behaviorType)
|
||||
{
|
||||
if (in_array($behaviorType, [6, 7, 8])) {
|
||||
return 'transaction'; // 交易
|
||||
} elseif (in_array($behaviorType, [1, 2, 9, 10])) {
|
||||
return 'social'; // 社交
|
||||
} elseif (in_array($behaviorType, [3, 4, 5])) {
|
||||
return 'footprint'; // 轨迹
|
||||
} else {
|
||||
return 'flow'; // 流量
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消费偏好
|
||||
*
|
||||
* @param int $poolCompanyId 客户ID
|
||||
* @return array
|
||||
*/
|
||||
private function getCustomerPreferences($poolCompanyId)
|
||||
{
|
||||
// TODO: 从行为记录和订单记录分析消费偏好
|
||||
return [
|
||||
'categories' => ['智能数码', '精品咖啡', '商务休闲'],
|
||||
'recentItems' => ['iPhone 16 Pro', 'iPad Air'],
|
||||
'coreInterest' => '数码发烧友 & 品质生活追求者'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取性别文本
|
||||
*
|
||||
* @param int $gender 性别代码
|
||||
* @return string
|
||||
*/
|
||||
private function getGenderText($gender)
|
||||
{
|
||||
$map = [
|
||||
0 => '保密',
|
||||
1 => '男',
|
||||
2 => '女',
|
||||
];
|
||||
return $map[$gender] ?? '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算年龄
|
||||
*
|
||||
* @param string $birthday 生日
|
||||
* @return int
|
||||
*/
|
||||
private function calculateAge($birthday)
|
||||
{
|
||||
if (empty($birthday)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$birthTimestamp = strtotime($birthday);
|
||||
if ($birthTimestamp === false) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$age = date('Y') - date('Y', $birthTimestamp);
|
||||
if (date('md', $birthTimestamp) > date('md')) {
|
||||
$age--;
|
||||
}
|
||||
|
||||
return $age;
|
||||
}
|
||||
|
||||
/**
|
||||
* 脱敏身份证号
|
||||
*
|
||||
* @param string $idNumber 身份证号
|
||||
* @return string
|
||||
*/
|
||||
private function maskIdNumber($idNumber)
|
||||
{
|
||||
if (empty($idNumber) || strlen($idNumber) < 8) {
|
||||
return $idNumber;
|
||||
}
|
||||
|
||||
return substr($idNumber, 0, 4) . str_repeat('*', strlen($idNumber) - 8) . substr($idNumber, -4);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user