## GitHub 同步说明
| 项 | 内容 |
|:---|:---|
| 仓库 | https://github.com/fnvtk/cunkebao_v3 |
| 分支 | develop |
| 方向 | 本地 → GitHub(单向 push) |
| 是否拉取远程 | 否(未 fetch / pull / merge) |
| 是否改本地文件 | 否(仅 git add/commit,未编辑任何源文件正文) |
| 基准 commit | 0643e78a |
| 变更规模 | 709 files, +26436 / -3407 lines |
## 目录变更统计
| 目录 | 文件数 | 说明 |
|:---|---:|:---|
| 开发文档/ | 404 | 四端进行中/已完成需求、接口文档、官网需求、部署脚本 |
| Server/ | 95 | 后端 API、迁移脚本、触客宝/超管/存客宝服务 |
| Cunkebao/ | 85 | 移动端 H5、设备绑定、客服、转号审批等 |
| Touchkebao/ | 68 | PC 工作台、AI 获客、算力中心、Glass UI |
| SuperAdmin/ | 33 | 超管项目中心、算力计费、内容库 |
| 官网/ | 19 | 新增存客宝官网静态页(index/pricing/solutions 等) |
| .cursor/ | 3 | Cursor 规则与 Skill |
| .obsidian/ | 2 | Obsidian 工作区配置 |
## 重点新增/更新(本地为准)
- 官网/: 完整静态站点(index、pricing、manual、solutions、api)
- 开发文档/1、需求/修改/*_进行中_20260530.md(存客宝/触客宝/AI数智员工)
- 开发文档/1、需求/官网需求.md
- 开发文档/5、接口/开发进度_接口文档.md 等接口文档迭代
- Touchkebao Glass UI 与 P0 收尾、通道获客算力
- SuperAdmin 算力计费规则、项目工作台
## 刻意未上传(本地保留)
- node_modules/
- .DS_Store、.smart-env/
- .开发文档_nested_git_backup/(旧嵌套 git 备份)
- **/__pycache__/
## 验收
GitHub 浏览: https://github.com/fnvtk/cunkebao_v3/tree/develop
Co-authored-by: Cursor <cursoragent@cursor.com>
126 lines
4.8 KiB
PHP
126 lines
4.8 KiB
PHP
<?php
|
|
|
|
namespace app\chukebao\controller;
|
|
|
|
use app\chukebao\service\CompanyWechatScopeService;
|
|
use library\ResponseHelper;
|
|
use think\Db;
|
|
|
|
class CustomerServiceController extends BaseController
|
|
{
|
|
|
|
public function getList(){
|
|
$accountId = $this->getUserInfo('s2_accountId');
|
|
$userId = $this->getUserInfo('id');
|
|
$companyId = $this->getUserInfo('companyId');
|
|
if (empty($accountId)){
|
|
return ResponseHelper::error('请先登录');
|
|
}
|
|
|
|
// 侧栏真源:与存客宝 ck_device 绑定一致;有项目设备链时不混入客服个人 IM 号(避免 2 台设备却显示 3 个号)
|
|
$accountIds = CompanyWechatScopeService::resolveKefuSidebarAccountIds((int)$companyId, (int)$accountId);
|
|
|
|
if (empty($accountIds)) {
|
|
return ResponseHelper::success([]);
|
|
}
|
|
|
|
$list = Db::table('s2_wechat_account')->alias('wa')
|
|
->join(['s2_device' => 'd'],'wa.currentDeviceId = d.id','LEFT')
|
|
->whereIn('wa.id',$accountIds)
|
|
->order('wa.id desc')
|
|
->group('wa.id')
|
|
->field([
|
|
'wa.*',
|
|
'd.imei',
|
|
'd.memo as deviceMemoFromDevice',
|
|
'd.extra',
|
|
'd.alive as s2DeviceAlive',
|
|
])
|
|
->select();
|
|
|
|
$ckDevicesByImei = [];
|
|
try {
|
|
$imeis = [];
|
|
foreach ($list as $row) {
|
|
if (!empty($row['imei'])) {
|
|
$imeis[] = $row['imei'];
|
|
}
|
|
}
|
|
if (!empty($imeis)) {
|
|
$ckRows = Db::name('device')
|
|
->where('companyId', $companyId)
|
|
->where('deleteTime', 0)
|
|
->whereIn('imei', array_unique($imeis))
|
|
->field('imei,alive,memo,model,brand,extra')
|
|
->select();
|
|
foreach ($ckRows as $ck) {
|
|
$ckDevicesByImei[$ck['imei']] = $ck;
|
|
}
|
|
}
|
|
} catch (\Exception $e) {
|
|
// ck device 表不可用时仍返回 S2 列表
|
|
}
|
|
|
|
foreach ($list as $k=>&$v){
|
|
$v['createTime'] = !empty($v['createTime']) ? date('Y-m-d H:i:s',$v['createTime']) : '';
|
|
$v['updateTime'] = !empty($v['updateTime']) ? date('Y-m-d H:i:s',$v['updateTime']) : '';
|
|
$v['labels'] = json_decode($v['labels'] ?? '', true) ?: [];
|
|
$momentsSetting = Db::name('kf_moments_settings')->where(['userId' => $userId,'companyId' => $companyId,'wechatId' =>$v['id']])->find();
|
|
$v['momentsMax'] = !empty($momentsSetting['max']) ? $momentsSetting['max'] : 5;
|
|
$v['momentsNum'] = !empty($momentsSetting['sendNum']) ? $momentsSetting['sendNum'] : 0;
|
|
$v['deviceExtra'] = json_decode($v['extra'],true) ?: [];
|
|
$v['deviceExtra']['imei'] = $v['imei'];
|
|
$memoFromDevice = $v['deviceMemoFromDevice'] ?? ($v['deviceMemo'] ?? '');
|
|
$v['deviceExtra']['memo'] = $memoFromDevice ?: ($v['deviceExtra']['memo'] ?? '');
|
|
|
|
$imei = $v['imei'] ?? '';
|
|
if ($imei && isset($ckDevicesByImei[$imei])) {
|
|
$ck = $ckDevicesByImei[$imei];
|
|
$ckExtra = !empty($ck['extra']) ? (json_decode($ck['extra'], true) ?: []) : [];
|
|
if (!empty($ck['memo'])) {
|
|
$v['deviceExtra']['memo'] = $ck['memo'];
|
|
}
|
|
if (!empty($ck['model']) || !empty($ck['brand'])) {
|
|
$v['deviceExtra']['market_name'] = trim(($ck['brand'] ?? '') . ' ' . ($ck['model'] ?? ''));
|
|
}
|
|
if (isset($ck['alive'])) {
|
|
$v['deviceAlive'] = (int)$ck['alive'];
|
|
}
|
|
if (isset($ckExtra['battery'])) {
|
|
$v['deviceExtra']['battery'] = $ckExtra['battery'];
|
|
}
|
|
}
|
|
if (!empty($v['s2DeviceAlive']) && (int)$v['s2DeviceAlive'] === 1) {
|
|
$v['deviceAlive'] = 1;
|
|
}
|
|
|
|
$v['isOnline'] = ((int)($v['deviceAlive'] ?? 0) === 1)
|
|
|| ((int)($v['wechatAlive'] ?? 0) === 1)
|
|
|| ((int)($v['keFuAlive'] ?? 0) === 1);
|
|
|
|
unset(
|
|
$v['accountUserName'],
|
|
$v['accountRealName'],
|
|
$v['accountNickname'],
|
|
$v['extra'],
|
|
$v['imei'],
|
|
$v['deviceMemo'],
|
|
$v['deviceMemoFromDevice'],
|
|
$v['s2DeviceAlive'],
|
|
);
|
|
}
|
|
unset($v);
|
|
|
|
usort($list, function ($a, $b) {
|
|
$oa = !empty($a['isOnline']) ? 1 : 0;
|
|
$ob = !empty($b['isOnline']) ? 1 : 0;
|
|
if ($oa !== $ob) {
|
|
return $ob - $oa;
|
|
}
|
|
return strcmp($a['nickname'] ?? '', $b['nickname'] ?? '');
|
|
});
|
|
|
|
return ResponseHelper::success($list);
|
|
}
|
|
}
|