1387 Commits

Author SHA1 Message Date
Manus AI
f7a7c56ce4 chore(触客宝): 全量构建部署 kr-kf 生产并通过线上 smoke
Docker 构建 touchkebao-web、发布 dist 与 Nginx 反代;验收 17/17 + Playwright ok。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 21:18:18 +08:00
Manus AI
2753265edb sync(本地→GitHub): 2026-06-03 21:17 全量单向同步
## GitHub 同步说明

| 项 | 内容 |
|:---|:---|
| 仓库 | https://github.com/fnvtk/cunkebao_v3 |
| 分支 | develop |
| 方向 | 本地 → GitHub(单向 push) |
| 是否拉取远程 | 否(未 fetch / pull / merge) |
| 是否改本地源文件 | 否(仅 git add/commit,未编辑业务/文档正文) |
| 远程基准 | origin/develop @ b5b40e8b |
| 本批工作区变更 | 999 files, +47622 / -4007 lines |

## 一并推送的本地已有 commit(此前未 push)

1. 3aa7ecf8 deploy: 官网 ckb.quwanzhi.com 宝塔上线 DNS+SSL+验收文档
2. 16a70045 fix(官网): 桌面导航仅保留一个留资 CTA 并重新上线
3. dbe7b7aa fix(官网): Hero 改为主 CTA 按钮并去掉重复 1200+ 数据条
4. ac82726e chore(部署): 五端上线验收46项并统一留资与缓存版本

## 本 commit 目录统计

| 目录 | 文件数 | 说明 |
|:---|---:|:---|
| 开发文档/ | 639 | 10、项目管理、未完成项跨仓汇总、五端需求/部署/接口 |
| Server/ | 129 | 后端 API、迁移、部署相关 |
| Touchkebao/ | 80 | PC 工作台、获客、算力 |
| 官网/ | 61 | ckb.quwanzhi.com 静态站与 CTA 迭代 |
| Cunkebao/ | 49 | 移动端场景/设置/流量池 |
| SuperAdmin/ | 26 | 超管后台 |
| .cursor/ | 11 | 规则与 Skill |
| 其他 | 14 | docker-compose、.obsidian、.codegraph 等 |

## 重点文件(本地为准)

- 开发文档/1、需求/修改/未完成项与跨仓验收汇总.md
- 开发文档/10、项目管理/账号密码与登录环境一览.md
- 开发文档/10、项目管理/(新建项目管理目录与截图)
- 官网/ 全站 + 宝塔上线文档
- 五端上线验收 46 项(见 ac82726e)

## 刻意未上传

- node_modules/、.DS_Store、.smart-env/
- .开发文档_nested_git_backup/
- **/__pycache__/

## 验收

- 提交页(含本说明): https://github.com/fnvtk/cunkebao_v3/commit/HEAD
- 分支树: https://github.com/fnvtk/cunkebao_v3/tree/develop

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 21:17:41 +08:00
Manus AI
ac82726e12 chore(部署): 五端上线验收46项并统一留资与缓存版本
扩展官网验收覆盖 Hero/JSON/AI 组件;留资弹窗试用文案改为300积分;静态资源 v=20260603。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 21:11:21 +08:00
Manus AI
dbe7b7aa7c fix(官网): Hero 改为主 CTA 按钮并去掉重复 1200+ 数据条
副标题下改为「7天免费试用每天送300积分」试用按钮,移除 hero-stats 与 social 行。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 21:26:44 +08:00
Manus AI
16a700459e fix(官网): 桌面导航仅保留一个留资 CTA 并重新上线
宽屏不再注入移动端克隆按钮,并加固 CSS 与缓存版本号,避免顶栏出现两个「领取资料并咨询」。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 21:25:06 +08:00
Manus AI
3aa7ecf8c0 deploy: 官网 ckb.quwanzhi.com 宝塔上线 DNS+SSL+验收文档
阿里云 A 记录指向存客宝宝塔,Nginx 443 泛域名证书,部署脚本补 SSL/DNS 步骤。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-31 19:15:04 +08:00
Manus AI
b5b40e8bf3 docs: 记录 AI数智员工 kr-phone 宝塔部署进度
standalone 挂载 aistaff:prod Docker :3104,验收 27/28。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-30 22:19:04 +08:00
Manus AI
7793093d0a fix: workphone BFF 在 SDK 不可用时降级返回 200
connection/status 不再因 workphone.quwanzhi.com 502 报 500;补 20260530 部署进度。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-30 22:03:04 +08:00
Manus AI
5182529a58 sync(本地→GitHub): 2026-05-30 21:41 全量单向同步
## 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>
2026-05-30 21:41:40 +08:00
Manus AI
0643e78a79 docs: 补推四端需求汇总、进行中需求与 workspace 最新态
以本地为准,未拉取远程。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-30 04:25:29 +08:00
Manus AI
5517457929 sync: 以本地为准同步全量变更至 GitHub
含四端需求文档、前端/后端/超管/触客宝迭代及部署脚本更新;未拉取远程。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-30 04:24:56 +08:00
Manus AI
711783baf5 部署:超管改宝塔 Node API 托管,弃 PM2 superadmin
全量 deploy 步骤 9 切至 deploy_superadmin_baota_node_api.sh;更新防治总表与开发进度。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 23:03:38 +08:00
Manus AI
11ad2df6f3 部署:三端重发与超管 standalone tar 修复
超管 PM2 改为 SuperAdmin 子目录 + tar 保留 pnpm 链接,避免 rsync 断链导致 next 模块缺失。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 21:48:06 +08:00
Manus AI
751543ab7a 触客宝:修复包名冲突并更新生产环境变量
Touchkebao 独立命名为 touchkebao,.env.production 对齐 /s2api 与 WS 配置,便于本地 vite 构建后部署。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 21:15:17 +08:00
Manus AI
2f6187d2fa 部署:DB配置迁移幂等化与场景媒体OSS同步命令
新增 config:sync-media 与 sync_db_config_tencent,部署流程接入 2f 步骤;修复 workbench/内容库迁移 SQL 在生产库可重复执行。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 21:10:24 +08:00
Manus AI
42e597349a docs: 4、前端/UI 配图 batch final 2026-05-26 22:21:38 +08:00
Manus AI
949ec2ec6a docs: 4、前端/UI 配图 batch 4 2026-05-26 22:21:25 +08:00
Manus AI
0a7604f9ab docs: 4、前端/UI 配图 batch 3 2026-05-26 22:21:16 +08:00
Manus AI
fd38980751 docs: 4、前端/UI 配图 batch 2 2026-05-26 22:21:07 +08:00
Manus AI
0264644c2a docs: 4、前端/UI 配图 batch 1 2026-05-26 22:20:58 +08:00
Manus AI
10396c9ac9 docs: 开发文档配图 工作手机对接
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 22:20:40 +08:00
Manus AI
b83ee46978 docs: 开发文档配图 images
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 22:20:33 +08:00
Manus AI
99251dcef8 docs: 开发文档配图 9、手册
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 22:20:26 +08:00
Manus AI
4c8174d75d docs: 开发文档配图 8、部署/scripts
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 22:20:17 +08:00
Manus AI
06a7a7be5c docs: 开发文档配图 8、部署/images
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 22:20:09 +08:00
Manus AI
93387f64e0 docs: 开发文档配图 7、数据库
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 22:20:01 +08:00
Manus AI
fa5185952d docs: 开发文档配图 6、后端
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 22:19:54 +08:00
Manus AI
3df3bcd9a5 docs: 开发文档配图 5、接口
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 22:19:41 +08:00
Manus AI
c49dc76a04 docs: 开发文档配图 4、前端/images
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 22:19:33 +08:00
Manus AI
1daae21236 docs: 开发文档配图 3、原型
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 22:19:25 +08:00
Manus AI
63963b5ee6 docs: 2、架构配图 AI开发流程.jpg
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 22:19:09 +08:00
Manus AI
ec45da57cf docs: 2、架构配图 architecture-overview
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 22:18:49 +08:00
Manus AI
1775f0d45a docs: 2、架构配图 architecture
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 22:18:41 +08:00
Manus AI
3a4a09eedb docs: 2、架构配图 doc-headers
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 22:18:33 +08:00
Manus AI
ea226961bd docs: 开发文档配图 1、需求
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 22:17:45 +08:00
Manus AI
7f5b96b88f docs: 开发文档配图 10、使用手册
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 22:17:34 +08:00
Manus AI
2815287c2e docs: flatten 开发文档 MD/脚本并同步 obsidian workspace
第一批:207+ Markdown、脚本与 .obsidian/workspace.json;以本地为准。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 22:16:19 +08:00
Manus AI
03ae8c07b6 feat: 同步本地全量开发到 GitHub(前端/后端/超管/开发文档规则)
含 AI 助手、抖音 OAuth、流量池推送、支付获客、SuperAdmin 设备与场景管理等本地迭代;以本地为准单向推送,未拉取远程。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 22:12:40 +08:00
Manus AI
a3bda0ccac fix: API获客 sceneId=11 列表500与小宝AI网关联通
Query whereIn 合并 legacy 场景;karuoAi 120s 超时,避免误降级 doubao 欠费。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 16:08:15 +08:00
Manus AI
1cd7027e1a 部署:扩展验收脚本、支付证书同步与生产全量上线复盘
补齐宝塔生产 payment.overlay/证书同步流程,新增 extended 验收覆盖 AI/支付/海报,并修复 smoke 测试对生产 BASE 与 scene8 计划的适配。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 15:51:21 +08:00
Manus AI
84c5085955 fix(home): 修复首页空白——补全 CkbIconShell 导入与登录态恢复
homeIcons 缺少 CkbIconShell 导致首页 React 崩溃;PermissionRoute 支持 token 会话并等待 hydration;persist getItem 兼容旧格式。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 12:40:58 +08:00
Manus AI
c9bd563fed fix(web): 修复部署后空白页与 index.html 缓存问题
登录态 hydration 期间显示加载态而非 null;index.html 禁缓存;本地跳过更新检测误报。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 12:10:33 +08:00
Manus AI
0df622bbfd fix(ui): 付款码获客图标与文案强调留资获客语义
场景获客首页改用齿轮全局设置,今日获客与付款码场景图标更清晰,计划卡片与弹窗文案突出支付后留资/跳转。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 11:33:37 +08:00
Manus AI
148962cbc2 feat(场景获客): 付款码mock验收+计划编辑对齐+场景字典修正
本地无威富通时可 mock 下单/模拟支付;更新计划与创建规则对齐;编辑回填 sceneConf;抖音 env 合并。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 20:19:16 +08:00
Manus AI
a3179fae9d style(场景获客): 空状态新建计划按钮间距统一
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 20:12:57 +08:00
Manus AI
beb13bb280 fix(场景获客): 统一新建计划按钮与 payment notify 配置
好友迁移等全场景列表展示新建计划;PaymentConfigService 默认 notify 指向 /v1/pay/notify;非 API 计划创建后回列表便于付款码验收。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 20:12:35 +08:00
Manus AI
5b0766d2b4 fix(场景获客): API获客去重并合并legacy统计到id=11
旧库 sceneId 6/9 若 DB 名仍含 API,统计并入规范 API 卡片,展示名强制用目录,避免两张「API获客」。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 20:10:12 +08:00
Manus AI
448662cce7 fix: BasicSettings sceneCatalog 导入路径修正以通过 Docker 构建
steps 目录下 ../../sceneCatalog 无法解析,改用 @/pages/mobile/scenarios/sceneCatalog。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 19:56:18 +08:00
Manus AI
a6ba0af9f8 feat(付款码获客): 微信Native支付全链路+开放对接+前端付款码弹窗
复用 PaymentService 生成付款码;支付回调后小程序授权手机号入池;开放 /v1/open/payment 供第三方对接。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 19:45:14 +08:00
Manus AI
17ac4da44a feat(场景获客): 参考存客宝补齐全量场景与前端独占计划页
全量场景卡片+AI Beta区;抖音/小红书/扫码/付款码/公众号走 localStorage 计划管理;后端场景保持原 API 列表。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 19:39:37 +08:00
Manus AI
9614a3138d fix(场景获客): 仅展示已实现场景,修复列表接口500
过滤未落地场景;修复 scenarioTags 已是数组时 json_decode 报错;接口失败时降级展示可用场景。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 19:35:45 +08:00
Manus AI
b5facefde8 feat(场景获客): 补全11种场景含扫码获客,统一渐变图标
后端合并 SCENE_CATALOG 保证列表完整;前端 SceneIcon 替换杂乱图片;附 plan_scene 落库迁移。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 19:32:42 +08:00
Manus AI
48fc5a7a7d fix(统计): 场景今日真实获客数,首页简化,编辑计划直达计划接口
场景列表 count 改为 task_customer 今日统计;首页汇总三指标+紧凑场景卡;已有计划自动补 apiKey 并开放计划接口面板。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 18:46:13 +08:00
Manus AI
cd7d00b4f8 fix(海报获客): 暂停计划仍可扫码留资,列表支持批量启用
已暂停不再返回「任务已结束」;修正 posters 数组 URL 解析;Tab 改为已启用/已暂停/全部并支持独立计划批量启用。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 16:23:06 +08:00
Manus AI
f9d6c3034f deploy: 三端宝塔部署 kr-op/kr-kf/kr-admin 及全栈验收
SuperAdmin standalone + PM2:3000;acceptance_tencent_all 23 项全通过。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 14:14:57 +08:00
Manus AI
175f9942e1 fix: AssignGroupModal 从 traffic-pool/api 导入分组接口
list/components 下 ../api 指向 list/api.ts 无 getGroups 导出,导致 Docker build 失败。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 14:05:21 +08:00
Manus AI
3e0cc92cfc ui(plan): 计划状态三标签筛选,导出按钮单行三列
客资弹窗底部文档/表格/Webhook 并排;计划列表增加全部/已启动/已暂停筛选。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 13:19:33 +08:00
Manus AI
db85605352 fix(plan): 修复客资列表500并支持全局Webhook回退
traffic_pool_company 查询改用 identifier|phone|ownerWechatId;扩展查询失败降级不抛500;计划未配 Webhook 时回退全局配置。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 13:17:46 +08:00
Manus AI
434d09183b feat(plan): 客资列表支持一键推送到 Webhook
新增 export-batch 接口,弹窗底部增加全宽 Webhook 推送按钮,与文档/表格导出并列布局。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 13:15:51 +08:00
Manus AI
c553198d5a feat(plan): 海报获客客资列表筛选与导出
已获客/已添加弹窗统一为客户列表,支持状态筛选、旅程详情,以及 Markdown/CSV 导出。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 13:14:19 +08:00
Manus AI
c05fe80b89 feat(设备管理): 功能开关与手机端 device_taskconf 同步
统一 DeviceTaskConfigService 读写;修复 task-config 校验与布尔转换;详情返回 features;autoAddFriend 与 autoCustomerDev 联动。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 13:04:31 +08:00
Manus AI
700f3ab5d2 fix(设备管理): 修复扫码二维码获取失败与本地 Docker 缺配置
补 device_bind 默认配置与 .env.docker [api] 段;新增 POST /v1/devices/qrcode BFF;前端弹窗即拉码并展示真实错误。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 12:47:08 +08:00
Manus AI
4a8ef1aec8 fix: 双环境 Docker 部署——本地 /api 与线上 ckbapi 分离
- docker-compose.yml 本地 VITE_API_BASE_URL=/api,Server 选择性挂载保留 vendor
- 新增 docker-compose.prod.yml 供宝塔构建用 ckbapi 直连
- Server Dockerfile 修复 composer(插件白名单、git/unzip、.dockerignore)
- composer.json 与 lock 对齐 5.1.*

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 12:35:42 +08:00
Manus AI
82bc96884a fix(local): 修复本地首页 dashboard 500 并接通 dev API
.env.docker 注释中的 h5= 导致 parse_ini_file 失败,异常页模板路径为空进而全站 500;同步 npm dev 反代与 dashboard 路由别名。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 12:33:36 +08:00
Manus AI
745d14c93f fix(设备管理): 默认在线Tab,扫码未绑微信设备在全部可见
列表查询改为 LEFT JOIN,避免无微信登录记录的扫码设备被过滤;前端 Tab 顺序调整为在线/离线/全部。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 12:21:30 +08:00
Manus AI
b838ae8fc1 fix: 设备管理获取二维码 Endroid 3.9 枚举调用修复
生产 endroid/qr-code 3.9 要求 ErrorCorrectionLevel::HIGH() 实例化调用,
旧写法传字符串导致 /v1/api/device/add 返回 HTTP 500。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 12:20:34 +08:00
Manus AI
06ca494e02 fix: 海报获客手机号解密改直连微信 API
EasyWeChat phone_number 未注册导致 decryptphone 500,阻断入池与 Webhook。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 10:28:47 +08:00
Manus AI
4ed9821c5d fix: 海报小程序码 AppId 改回 wx789850448e26c91d
存客宝小程序 ID 以用户确认为准;落地页保持 pages/poster/index2,缓存前缀升 v5。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 10:21:01 +08:00
Manus AI
f0ba9b387d fix: 海报小程序码落地页改回 pages/poster/index2
误将小程序码指向表单页 input2;恢复获客海报页,生产切换存客宝 AppId。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 10:14:59 +08:00
Manus AI
12cd0808bc fix: 海报获客弹窗改为小程序码,H5表单链接隔离
默认 poster_qrcode_mode=miniprogram;弹窗主区展示微信无限码,
H5获客表单仅作下方可复制链接,与小程序码分离。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 10:05:33 +08:00
Manus AI
07bfc890c2 fix: 场景获客容错、海报二维码URL与部署Skill沉淀
去掉二维码URL的.png后缀(Nginx静态404);删除老游条文案;401优先跳登录;
新增存客宝宝塔部署Skill汇总历次踩坑。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 09:59:44 +08:00
Manus AI
67dfc0a87c perf: 海报获客二维码缓存与 PNG 直链,加快弹窗与扫码页
二维码 Redis 缓存、qrImageUrl 可缓存 PNG、海报 getone GET+缓存,管理端弹窗秒开。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 09:49:26 +08:00
Manus AI
ffd66d0f5e fix: MiniProgramQrCodeService 使用全局 config() 并完善 smoke 测试
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 09:44:32 +08:00
Manus AI
afe4dbd131 fix: 海报获客二维码默认改 H5,避免扫码进老游条小程序
wx789850448e26c91d 微信侧绑定老游条首页;posterQrcodeMode=h5 生成存客宝表单二维码。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 09:44:14 +08:00
Manus AI
1f46065ebc fix(workbench): 朋友圈列表 config 改直连查询并 setAttr
修复 relation 未挂载导致 config 为空;避免 data[] 触发 Model::data 报错。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 06:48:16 +08:00
Manus AI
597941fbd1 fix(workbench): 朋友圈列表改用 setAttr 写入 config
避免 $item->data[] 触发 ThinkPHP Model::data() 参数错误。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 06:47:30 +08:00
Manus AI
ec2d4957e4 fix(workbench): 修复朋友圈同步列表500与token stale
朋友圈列表改用 data.config 数组避免 Model 属性异常;getList 加 try/catch;
JwtAuthTrait 补 getUserInfo 别名;前端 request 每次读取最新 token。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 06:05:43 +08:00
Manus AI
5c1463ffa9 fix: 运营报告推送真实数据并附带鉴权详情链接
飞书/企微不再误用客资变更模板;推送含汇总数据与 HMAC token 详情页,支持今日/近3日/近7日查看。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 05:59:25 +08:00
Manus AI
6ec8df94dc fix(api): 修复工作台 JWT 鉴权与流量池分页 count 污染
Workbench 等控制器改用 JwtAuthTrait 替代失效的 request->userInfo;
TrafficPoolGroupService 与 getUserList 使用 clone count,避免 500 与错误分页。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 05:50:04 +08:00
Manus AI
a72772d5ee fix(touchkebao): 405/500 修复 — s2api 反代 + 错误进通知中心
宝塔 kr-kf 增加 /s2api 反代;axios 错误写入通知中心;chukebao 接口 nullable 与 count 修复。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 05:44:26 +08:00
Manus AI
ac47668016 fix(chukebao): 修复触客宝 PC 页 500 — count 污染 query 与 nullable 字段
wechatFriend/chatroom/notice/followUp/todo 等接口在生产环境返回 500,改用 clone count 与字段兜底。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 05:38:09 +08:00
Manus AI
3f7e1a4af6 fix(touchkebao): 接口错误汇总到通知中心,不再弹 Toast
将 axios 拦截器中的 500 等错误写入铃铛通知「接口异常」Tab,同类错误合并计数,避免聊天页被 Toast 打断。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 05:34:29 +08:00
Manus AI
f40649a534 fix(api): StatsController 改用 JwtUtil 鉴权,生产 Dashboard 接口恢复
docker-compose 生产构建固定 VITE_API_BASE_URL=ckbapi,修复宝塔部署 405 与首页接口错误。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 05:32:03 +08:00
Manus AI
99cbabbaaf fix: 修复生产 JWT 鉴权导致全站数据接口 500
getUserInfo 改从 Authorization 解析 token,避免 userInfo 写入 param 触发
variable type error;补齐 app\http\middleware\jwt 并统一 middleware 别名。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 05:27:07 +08:00
Manus AI
04747667b5 docs(obsidian): 8、部署 01~05 子目录侧栏配色
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 05:13:38 +08:00
Manus AI
9eaedd2c28 fix: 场景获客 Webhook 推送改为简洁双块 IM 格式
飞书/企微仅标题+客户信息+用户旅程,去掉计划归因与 JSON 附录;HTTP 改为精简 JSON。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 05:07:22 +08:00
Manus AI
667a4ea656 fix: 修正 OpsReportSection 的 api 导入路径以通过 Docker 构建
../api 无法解析导致 cunkebao-web 镜像 build 失败,改为同目录 ./api。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 05:05:27 +08:00
Manus AI
2e856b1922 feat: 场景获客总设置新增运营报告定时推送(默认09:00)
按公司汇总各平台客资数据,支持 Webhook/飞书定时与立即发送,含预览面板。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 05:02:33 +08:00
Manus AI
e403704ab3 ui: 编辑计划高级设置改为默认折叠的图标入口
分销/Webhook/计划接口/拉群四块合并为 4 宫格图标,点击展开后再编辑。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 04:59:18 +08:00
Manus AI
d1c926205a fix: 全局 Webhook 推送增加公司边界校验并写入开发铁律
推送前校验 plan.companyId 与 customer.task_id,跨公司或不归属本计划的客资不推送。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 04:56:53 +08:00
Manus AI
b8fa23b6a5 feat(plan): 新建计划保存后跳转编辑页展示计划接口区块
创建成功后进入 /scenarios/edit/:planId 第一步并提示 Webhook/API 配置。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 04:54:30 +08:00
Manus AI
96b828b1bf ui: 将 Webhook/计划接口移入编辑计划并加强删除确认
操作菜单仅保留编辑/复制/删除;Webhook 与入站计划接口配置整合到基础设置步骤。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 04:49:53 +08:00
Manus AI
9ddcf79bac ui: 场景获客右上角设置改为纯图标按钮
移除「总设置」文字,保留齿轮图标置于导航栏右侧,与「新建计划」并列。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 04:47:26 +08:00
Manus AI
65fa2986b1 fix: 存客宝小程序码 opcache 与接口结构化返回
Docker opcache 缓存旧 EasyWeChat 代码导致 500;开启 validate_timestamps,接口返回存客宝小程序元数据+qrCode,前端兼容解析。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 04:42:03 +08:00
Manus AI
69e7c282ca fix: 海报获客小程序码改用 curl 直连微信 API
Docker 挂载后无 vendor 导致 EasyWeChat 不可用;新增 MiniProgramQrCodeService 直连 getwxacodeunlimit,失败时 H5 二维码兜底。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 04:37:04 +08:00
Manus AI
109e9fef37 fix: 飞书 Webhook 自动转 msg_type 并校验响应体 code
飞书机器人返回 HTTP 200 但 code=19002(缺少 msg_type)时原先误判为成功;现自动适配飞书文本格式并在 body code≠0 时报错。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 04:33:01 +08:00
Manus AI
8ce1455555 fix(deploy): 修复首页500错误与runtime日志权限
runtime/log 目录 root 属主导致 PHP-FPM 写日志 Fatal Error 返回500;启动时 chown/chmod runtime,前端 user-info 改走 /v1/auth/info。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 04:27:12 +08:00
Manus AI
69bdce481f 增强场景获客 Webhook 载荷:计划归因、时间线与标签汇总
推送 JSON 现包含 attribution(来源计划/场景/入计划时间)、planTimeline、tags 合并及带 planId 的行为轨迹,便于外部系统识别客资从哪条计划来、做了什么。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 04:23:46 +08:00
Manus AI
10527ecfa8 feat(场景获客): 总设置页(全局 Webhook / 默认设备 / 统一流量池)
公司级配置与单计划并存:全局 Webhook 叠加推送、新建计划预填设备、客资自动入指定流量池分组。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 04:19:31 +08:00
Manus AI
82c15699cc fix(场景获客): 补全 Webhook 批量导入触发与校验
订单 Excel/电话场景批量客资、计划创建更新 URL 校验、smoke 测试脚本。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 04:13:22 +08:00
Manus AI
092161316e chore(cursor): 存客宝 v3 开发文档先行规则 alwaysApply
开发前必读开发铁律与 5、接口;收尾回写文档与进度。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 04:11:59 +08:00
Manus AI
faf907b61b feat(场景获客): 计划级客资出站 Webhook 推送与配置 UI
计划可配置出站 URL,客资进入或状态变更时 POST 完整数字化客资(电话、标签、行为轨迹);操作菜单与编辑计划均可配置。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 04:09:42 +08:00
Manus AI
2ecf9e331d fix(deploy): 恢复 Docker 编排与配置文件,修复登录路由
docker-compose.yml 被清空导致前端退出、后端 .env 挂载失效;从 git 历史与镜像反编译重建全套 Docker 配置,修复 nginx 登录重写走 ThinkPHP 路由。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 03:26:17 +08:00
Manus AI
1537b06278 docs(obsidian): 存客宝开发文档侧栏目录配色
对齐工作手机四类分类色,启用 cunkebao-devdoc CSS snippet 与图谱着色。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 03:24:54 +08:00
wong
bcbc92ad57 sync: Cunkebao 自动点赞页与记录;Touchkebao 会话列表与存储、文档清理
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 09:43:36 +08:00
wong
41ffb8a220 自动点赞提交 2026-05-12 16:58:46 +08:00
wong
1d11f01115 bug优化 2026-04-29 16:27:53 +08:00
wong
1deebf3f5c feat(media): 微信消息媒体 OSS 归档与下载地址回写
- 新增 MediaArchiveJob、MediaOssArchiveService、WechatMediaArchiveService 与回填命令
- Message/DataProcessing 等支持归档调度与持久化下载 URL
- WebSocket 控制器整理;朋友圈与文档/定时任务说明更新

Made-with: Cursor
2026-04-14 09:37:00 +08:00
wong
ae1120f1e3 11111 2026-04-09 11:07:54 +08:00
wong
b597ed2275 素材允许添加小程序 2026-04-07 16:08:44 +08:00
wong
1ecfacce49 发送小程序打通 2026-04-03 16:53:50 +08:00
wong
b20f00be53 处理存客宝内容库无法采集问题 2026-04-01 10:54:23 +08:00
wong
168fff56c1 1111 2026-03-24 11:09:13 +08:00
wong
689705ef70 111 2026-03-11 14:22:14 +08:00
wong
6629a9ad14 11 2026-03-11 14:20:30 +08:00
wong
af79d1e860 代码提交 2026-03-11 14:01:50 +08:00
wong
38379fac65 触客宝功能提交 2026-02-04 11:53:28 +08:00
wong
7534dd79fa 新版流量池服务端提交 2026-02-04 10:58:56 +08:00
wong
f956fc949b 存客宝新版流量池提交 2026-02-04 10:58:02 +08:00
wong
442be3b02c 代码优化 2026-02-02 17:02:49 +08:00
wong
208c25b2f7 流量池详情 2026-02-02 14:53:06 +08:00
wong
dd4b43c10e 流量池更新优化 2026-02-02 14:51:25 +08:00
wong
519a9b1d1b 流量池服务端 2026-02-02 11:00:26 +08:00
wong
f0c278a35f 流量池前端 2026-02-02 10:59:12 +08:00
wong
ab5885f2a0 代码兼容性提交 2026-01-31 15:19:11 +08:00
wong
9bc1d7d7bf 存客宝代码优化 2026-01-31 15:08:42 +08:00
wong
4d8fd96190 111 2026-01-20 11:04:28 +08:00
wong
7f7a5357a0 朋友圈优化 2026-01-19 14:29:01 +08:00
wong
28adf38f2f 代码同步 2026-01-19 14:21:19 +08:00
wong
b2abe9f570 代码提交 2026-01-19 11:36:42 +08:00
wong
5aa8657640 去除无用代码 2026-01-19 11:31:35 +08:00
wong
44dbe20b03 代码优化 2026-01-19 11:11:36 +08:00
wong
3a0621aab8 1 2026-01-19 10:51:18 +08:00
wong
28f4479d3c 111 2026-01-19 10:43:11 +08:00
wong
e356ae67c7 优化 2026-01-19 10:39:51 +08:00
wong
6b31889261 代码优化 2026-01-19 10:30:09 +08:00
wong
16aec83f02 消息优化 2026-01-19 10:01:40 +08:00
wong
9fe0f9f677 代码优化 2026-01-19 09:48:53 +08:00
wong
ee4dcb1b8b 测试 2026-01-19 09:26:55 +08:00
wong
5dfaba943b 优化 2026-01-17 17:20:23 +08:00
wong
bac66de046 代码优化 2026-01-17 17:03:48 +08:00
wong
9b8f3b1b2b 代码优化 2026-01-17 15:58:57 +08:00
wong
56d5f0b5a3 优化 2026-01-17 15:42:00 +08:00
wong
51a731ea68 任务调度器提交 2026-01-17 15:17:05 +08:00
wong
002f03e037 优化 2026-01-16 16:51:00 +08:00
wong
a250819d87 代码优化 2026-01-16 16:42:51 +08:00
wong
135e58f1e2 修改默认朋友圈分页 2026-01-16 11:55:45 +08:00
wong
58b6cd2340 支持定时采集所有朋友圈 2026-01-16 11:55:07 +08:00
wong
b21f40942e 好友迁移由30分钟改为10分钟 2026-01-16 10:17:47 +08:00
wong
dd9fa3d0f2 缓存问题 2026-01-15 14:24:50 +08:00
wong
430b8eee91 全局配置服务功能提交 2026-01-15 14:24:25 +08:00
wong
cec64b889b 全局配置前端功能提交 2026-01-15 14:22:23 +08:00
wong
443ef6ad2d 好友迁移 + 定时器优化 2026-01-13 15:22:19 +08:00
wong
3e8b607948 好友切换 2026-01-13 10:20:33 +08:00
wong
9eeaaa70c4 1111 2026-01-12 17:02:31 +08:00
wong
80ae205cb3 1111 2026-01-12 14:38:29 +08:00
wong
cd38e0eedd 去除无用代码 2026-01-12 09:44:12 +08:00
wong
66d217d5f1 入群欢迎语功能提交 2026-01-09 17:05:17 +08:00
wong
2b128195bf 代码同步 2026-01-08 11:50:32 +08:00
wong
e469537ac5 场景获客支持拉群 2026-01-08 10:45:41 +08:00
wong
b2e84a2259 1、新增一个所有好友的流量池
2、旧版场景获客数据迁移
3、场景获客功能兼容旧版数据
2026-01-07 10:41:39 +08:00
wong
228d59402f 测试表单可修改 2026-01-06 11:07:53 +08:00
wong
ea2dd8cab2 代码提交 2026-01-06 10:58:29 +08:00
乘风
efcbb06eb2 Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into develop 2026-01-05 11:11:54 +08:00
乘风
61e720c247 去掉重复文件 2026-01-05 11:11:49 +08:00
wong
88418386a4 Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into develop 2026-01-05 11:04:39 +08:00
wong
f096e07ec8 同步 2026-01-05 11:04:08 +08:00
乘风
046b00404e 同步 2026-01-05 10:52:26 +08:00
乘风
9bb6390f9a Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into develop 2026-01-05 10:47:16 +08:00
乘风
cb612746a6 数据中心变化 2026-01-05 10:47:10 +08:00
wong
8beb2cb6fe Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into develop 2026-01-05 10:44:17 +08:00
wong
aabd8037a6 同步 2026-01-05 10:44:08 +08:00
乘风
408c6a2029 数据同步 2026-01-05 10:19:51 +08:00
乘风
ba0ebcf273 数据中心同步 2026-01-05 10:16:20 +08:00
乘风
0457528dd0 同步 2026-01-05 09:59:07 +08:00
乘风
e6037ccfd5 Merge branch 'develop' into feature/yongpxu-dev 2026-01-05 09:55:03 +08:00
wong
ce1c434ea8 代码提交 2026-01-05 09:34:48 +08:00
wong
b4c813311b 自动建群提交 + 触客宝发布朋友圈优化 2026-01-04 17:03:51 +08:00
wong
08d2a811b7 触客保优化 2026-01-04 15:58:47 +08:00
乘风
256afaee12 可以的 2026-01-04 12:19:12 +08:00
乘风
198c62081b 构建触客宝CKB 2026-01-04 12:14:17 +08:00
乘风
53b679aeb6 Refactor MomentPublish component: update styling for account cards, enhance selected state visuals, and switch to customer store for account data management. 2026-01-04 12:07:17 +08:00
wong
64358df1c1 内容库采集支持链接类型 2025-12-30 11:06:46 +08:00
wong
b0d9481cdb 提现bug处理 2025-12-29 18:00:46 +08:00
wong
db0985504a 新版群添加功能 2025-12-29 16:09:22 +08:00
wong
82f9b9371a 提现优化 2025-12-29 16:02:24 +08:00
wong
d9ee7e9402 分销优化 2025-12-29 15:25:04 +08:00
wong
b609eeb0eb 1 2025-12-29 15:18:39 +08:00
wong
203e8c8eaf 渠道新增用户权限 2025-12-29 15:12:57 +08:00
wong
2c90f6f33c 场景获客优化子账号只能展示自己的数据 2025-12-29 15:01:44 +08:00
wong
88bc7ef3cd 金额处理 2025-12-29 10:59:32 +08:00
乘风
5ae0d997d9 小问问题处理一下 2025-12-26 18:15:33 +08:00
wong
879396e91c 代码提交 2025-12-26 10:42:33 +08:00
wong
b8754f6174 内容库群选择功能 + 场景获客分页修复 2025-12-25 15:39:57 +08:00
wong
302617cd81 1、修复场景获客数据统计问题
2、修复默认头像破图问题
3、修复健康分展示问题
4、修复获客场景_好友迁移编辑会跳到其他类目问题
2025-12-24 15:27:57 +08:00
wong
79fd133821 Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into develop 2025-12-23 17:02:12 +08:00
wong
4f2b34522f 代码提交 2025-12-23 17:01:59 +08:00
乘风
853a7f32d2 Merge branch 'feature/yongpxu-dev' into develop 2025-12-23 16:07:57 +08:00
乘风
eea18cc0c7 Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into develop 2025-12-23 16:07:48 +08:00
乘风
0e173668c5 接触所有限制 2025-12-23 16:07:33 +08:00
乘风
abdcff813c 处理 2025-12-23 15:45:15 +08:00
wong
0ac84bd033 Merge branch 'feature/bug' into develop 2025-12-23 15:33:30 +08:00
wong
4c12619f0e 流量分发展示数据优化 2025-12-23 15:30:08 +08:00
乘风
dc1f194bcc Merge branch 'feature/yongpxu-dev' into develop 2025-12-23 11:53:24 +08:00
乘风
7a89b80c8f feat: 配置挪到外边 2025-12-23 11:52:06 +08:00
wong
7a4f40de54 1111 2025-12-23 11:38:25 +08:00
wong
988d7fcdd3 1 2025-12-23 11:26:29 +08:00
wong
236ef09538 1111 2025-12-23 11:22:28 +08:00
wong
d914f097c0 1 2025-12-23 10:09:42 +08:00
wong
c37e5aca35 111 2025-12-23 09:57:50 +08:00
wong
5e198d1021 去除多余的日志输出 2025-12-23 09:52:08 +08:00
wong
cc91552c36 朋友圈采集自己的任务完成 2025-12-22 16:35:08 +08:00
wong
f208bbdb9a 优化渠道 2025-12-20 16:45:25 +08:00
wong
20f567da37 渠道功能完善 2025-12-20 11:27:47 +08:00
wong
9b45ebea0e 去除多余的日志 2025-12-20 11:17:30 +08:00
wong
7a0ae54705 代码提交优化新增的渠道码限制24小时有效 2025-12-20 11:09:04 +08:00
wong
3f5a2189ce 代码优化 2025-12-20 10:34:09 +08:00
wong
36d6f94177 1 2025-12-19 17:26:55 +08:00
wong
c046bdab30 优化内容库 2025-12-19 17:25:56 +08:00
wong
0c8f1d6419 优化界面 + 新增修改密码功能 2025-12-19 14:46:14 +08:00
乘风
f8810f5199 去掉无用工作区 2025-12-18 16:49:54 +08:00
wong
bc6421b1af Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into develop 2025-12-18 16:23:10 +08:00
wong
2f00ee176b 分销提交 2025-12-18 16:23:01 +08:00
乘风
44da1feed3 Merge branch 'yongpxu-wechatFriends' into feature/yongpxu-dev 2025-12-18 15:46:19 +08:00
wong
66c8623138 11111 2025-12-18 15:36:25 +08:00
乘风
e12fbad859 Merge branch 'develop' into yongpxu-wechatFriends 2025-12-18 15:13:45 +08:00
乘风
183df2cac3 Merge branch 'develop' into yongpxu-dev 2025-12-18 15:11:13 +08:00
乘风
7f1fda40dc 增强GroupContextMenu和VirtualContactList组件:在GroupContextMenu中添加遮罩层右键事件回调以支持菜单在新位置打开,优化SidebarMenu中的右键菜单逻辑以避免闪烁,提升用户体验。 2025-12-18 15:09:58 +08:00
wong
d42a2df286 1 2025-12-18 15:09:51 +08:00
wong
1a345fff79 1 2025-12-18 10:50:50 +08:00
乘风
7724d60a05 更新联系人上下文菜单和朋友圈组件:修复移动分组成功消息的重复调用,优化样式以增强用户体验,重构相关组件以使用currentCustomer替代currentKf,确保数据一致性和可读性。 2025-12-18 10:44:38 +08:00
wong
1cb7903e37 渠道代码提交 2025-12-18 10:34:50 +08:00
wong
d8183909ca 1111 2025-12-17 17:58:08 +08:00
wong
7f7d5d5c39 代码优化 2025-12-17 17:04:57 +08:00
乘风
72cf3fd81c 重构VirtualContactList组件:统一项目高度以实现一致的布局,并通过重构代码格式来提高可读性。此更改通过确保所有项目类型共享相同的高度来增强虚拟滚动期间的视觉稳定性。 2025-12-17 16:39:47 +08:00
乘风
65167be7f8 更新联系人分组逻辑:将分组类型从“group”更改为“chatroom”,并在联系人上下文菜单中添加移动分组的回调函数。调整相关组件以支持新的分组管理功能,确保在移动分组时使用正确的类型。 2025-12-17 16:20:58 +08:00
wong
7dda34a779 分销功能提交 2025-12-17 16:20:46 +08:00
乘风
bde1d36005 重构GroupContextMenu组件:调整上下文菜单的样式并增强组处理逻辑,以禁用默认组或未分组组的编辑/删除操作。更新联系人组的排序逻辑,以区分默认组和未分组组的优先级。 2025-12-16 16:42:00 +08:00
乘风
631e8d00e2 增强应用组件中的ErrorFallback UI,并更新SidebarMenu以实现新的联系人存储集成。重构微信组件,支持虚拟滚动,提升性能。在微信好友组件中实现新的群组管理功能。 2025-12-16 16:24:10 +08:00
乘风
3720987997 Refactor FriendsCircle component: Simplified onError handler for image loading 2025-12-12 10:24:29 +08:00
wong
8e4ce2aee2 Merge branch 'feature/bug' into develop 2025-12-11 17:35:22 +08:00
wong
08fc18da86 php代码提交 2025-12-11 17:32:59 +08:00
wong
b2760cde0e 群、好友支持客服微信号筛选 2025-12-11 17:32:27 +08:00
wong
61926503cf 流量分发筛选状态功能 2025-12-11 17:31:33 +08:00
wong
84a51b8f91 好友转移 2025-12-11 17:30:40 +08:00
乘风
4f8a69a511 1 2025-12-11 09:40:54 +08:00
wong
4537305dfc Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into develop 2025-12-11 09:37:44 +08:00
wong
be901a92f6 Merge branch 'feature/test' into develop 2025-12-11 09:37:12 +08:00
wong
1067a15916 朋友圈导出默认30天 2025-12-11 09:36:37 +08:00
乘风
2a41d88ce3 STEP:10 2025-12-11 09:36:35 +08:00
乘风
80027e8fa3 STEP:9 2025-12-11 09:36:22 +08:00
乘风
df893296ba STEP:8 2025-12-11 09:36:09 +08:00
乘风
77e9f763cd STEP:7 2025-12-11 09:35:59 +08:00
乘风
c531957ce8 STEP:6 2025-12-11 09:35:47 +08:00
乘风
257ca5014b STEP:4 2025-12-11 09:35:10 +08:00
wong
1c3e567b9a test 2025-12-11 09:35:03 +08:00
乘风
4aacb7bcb6 STEP:3 2025-12-11 09:34:59 +08:00
乘风
e7fef596d4 STEP:2 2025-12-11 09:34:48 +08:00
乘风
5d95ca6f53 STEP:1 2025-12-11 09:34:38 +08:00
wong
3cd6bf9dba test 2025-12-11 09:33:53 +08:00
wong
67a4bd6e3a Merge tag 'v1.1.2' into develop 2025-12-11 09:31:26 +08:00
wong
c33c62aa6d Merge branch 'release/v1.1.2' 2025-12-11 09:31:19 +08:00
乘风
581e09d45b Merge branch 'develop' into yongpxu-dev 2025-12-10 18:05:11 +08:00
乘风
688a200999 go 2025-12-10 18:03:51 +08:00
wong
cc89df7957 Merge branch 'feature/group' into develop 2025-12-10 17:58:34 +08:00
wong
198e0434b3 群发 2025-12-10 17:58:08 +08:00
wong
55fe2b46df 代码优化 2025-12-10 17:56:55 +08:00
乘风
c7e934f23d 重构MessageRecord组件以支持虚拟化,从而提高大型消息列表的性能。引入VirtualizedMessageList以实现高效渲染,并使用React.memo优化渲染逻辑以防止不必要的重新渲染。 2025-12-10 17:09:08 +08:00
乘风
7752dd136f 重构消息处理逻辑以提高同步和性能,并增强ProfileCard组件以实现快速回复功能。 2025-12-10 16:02:01 +08:00
乘风
af2f32da4a Update package.json to set module type, refactor postcss.config.js to use ES module syntax, and remove vite.config.ts file. 2025-12-10 15:57:02 +08:00
乘风
81859dfacb gogoo 2025-12-10 11:58:47 +08:00
wong
3e145ca123 算力功能改版 2025-12-09 15:01:38 +08:00
wong
c9a5d3091f 内容库优化 + 内容库导入功能 2025-12-09 14:59:24 +08:00
乘风
4374ad5036 1 2025-12-09 11:55:53 +08:00
乘风
1ca2ca7058 更新技术栈文档,新增TanStack Query和Sentry组件,优化项目结构说明,添加关于兼容性和工程化的详细信息。 2025-12-09 11:11:53 +08:00
wong
9fe15b1bec Merge branch 'feature/groupCreate' into develop 2025-12-06 17:13:53 +08:00
wong
a557319b3e 算力功能 2025-12-06 17:13:07 +08:00
wong
f06eb0e9d7 消息同步 2025-12-06 17:12:07 +08:00
乘风
1be091522e 新增asyncMessageStatus接口以获取消息状态,并在消息处理逻辑中集成该接口以优化消息同步。调整ProfileCard组件的状态管理以支持快速回复功能。 2025-12-06 17:10:57 +08:00
wong
63df35b79c 代码优化 2025-12-05 11:36:03 +08:00
wong
2697da15aa 消息聊天自动分配好友 2025-12-05 10:45:21 +08:00
wong
ced0f993de 分组及好友列表优化 2025-12-05 10:44:43 +08:00
wong
55629a8ce4 Merge branch 'feature/groupPush' into develop 2025-12-04 16:05:38 +08:00
wong
923c6a5904 Merge branch 'feature/bug' into feature/groupPush 2025-12-04 16:02:25 +08:00
wong
b63e87d0cf 1 2025-12-04 16:01:51 +08:00
wong
96caa71f03 内容库可以选择设备 2025-12-04 15:56:19 +08:00
wong
b9b1877e04 自动点赞功能优化 2025-12-04 11:10:55 +08:00
wong
81daa8cfdc 修复备注 2025-12-04 09:43:25 +08:00
乘风
903ea005e3 通过向UpdateDataInfoParams添加conRemark并重构相关组件,增强好友信息更新功能。删除未使用的本地数据库更新逻辑并简化DetailValue组件中的数据处理。 2025-12-03 18:44:42 +08:00
乘风
580e9f4f73 Remove package.json file from MCP directory 2025-12-03 16:56:02 +08:00
乘风
51e08d1b2a Merge branch 'develop' into yongpxu-dev2 2025-12-03 16:55:35 +08:00
乘风
3708f5a61e 优化消息同步处理逻辑,提升系统性能和稳定性。 2025-12-03 16:54:21 +08:00
wong
58377b4670 代码提交 2025-12-03 16:29:40 +08:00
wong
5bb5aa3d5a 代码优化 2025-12-03 16:18:10 +08:00
wong
71def21369 消息优化 2025-12-03 14:06:07 +08:00
乘风
1ea565fbdf 先写好接口 2025-12-03 11:03:38 +08:00
乘风
56a9a015f1 重构ChatWindow和MessageEnter组件以提高性能和可维护性。利用useCallback和useMemo进行备忘录化,增强文件类型处理,并简化消息呈现逻辑。在MessageList中引入SessionItem组件以更好地管理会话。 2025-12-03 09:46:19 +08:00
wong
0708b5a134 Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into develop 2025-12-02 17:47:32 +08:00
wong
242065d298 微信分组及消息置顶 2025-12-02 17:47:22 +08:00
乘风
b5d6214521 Remove pin/unpin chat session API methods and update message list handling for pinning sessions. 2025-12-02 15:28:57 +08:00
乘风
9c5bccb23c Merge branch 'develop' into yongpxu-dev 2025-12-02 10:54:28 +08:00
wong
5f5b567749 调整消息同步处理 2025-12-02 10:51:27 +08:00
wong
354d6c4f62 代码优化 2025-12-01 18:09:06 +08:00
wong
2c37314c4f 代码优化 2025-12-01 17:41:57 +08:00
乘风
1dbdee155e 添加高频轮询接口白名单,优化请求截流逻辑以支持特定接口的无截流请求。允许通过配置控制截流功能,提升请求处理的灵活性和用户体验。 2025-12-01 16:58:58 +08:00
wong
2939b63998 1 2025-12-01 16:47:57 +08:00
wong
ff7097f2d9 1 2025-12-01 16:46:23 +08:00
wong
33cd4de3c5 Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into wong-dev 2025-12-01 16:44:38 +08:00
wong
0bc6c3a22a 代码优化 2025-12-01 16:41:40 +08:00
wong
44edfe7a81 消息同步优化 2025-12-01 16:25:15 +08:00
wong
d3fae3bbd0 队列优化 2025-12-01 15:42:54 +08:00
乘风
11a4ec8c67 在MessageList组件中添加未知联系人补充功能,异步拉取缺失的头像和昵称信息,并更新会话和联系人数据库,以提升用户信息的准确性和完整性。 2025-12-01 15:09:25 +08:00
乘风
75aab08a72 在MessageList组件中实现对丢失头像或昵称的详细获取,并相应更新会话状态和数据库,以确保准确显示用户信息。 2025-12-01 14:43:33 +08:00
乘风
2cc2184002 增强MessageRecord组件以管理消息加载期间的滚动行为。为消息容器和加载状态引入引用,通过在加载更多消息时保留滚动位置来确保平滑的用户体验。 2025-12-01 14:34:19 +08:00
乘风
57c398f537 更新MessageRecord组件中的消息过滤逻辑,以包括其他消息类型(-10001),以改进系统消息的处理并增强用户体验。 2025-12-01 14:27:39 +08:00
乘风
7d9f94984c 在MessageRecord组件中实现parseSystemMessage实用程序,从系统消息中提取纯文本,通过删除HTML标签并为相关内容添加红包图标来增强消息渲染。 2025-12-01 14:23:10 +08:00
乘风
8659049ae9 增强MessageRecord组件以支持RedPacketMessage和TransferMessage的额外道具,从而更好地处理单击事件和套接字请求。更新禁用的转接消息的样式以改善用户体验。 2025-12-01 12:23:19 +08:00
乘风
1ca5b30c15 Refactor TransferMessage component to improve layout and styling. Adjust icon size and add status display for better user experience. 2025-12-01 10:59:50 +08:00
乘风
b9bc0a71a9 在MessageRecord组件中增加微信转账消息支持,更新消息过滤逻辑。调整RedPacketMessage样式以获得更好的布局。 2025-12-01 10:56:05 +08:00
乘风
13ae8bd76f Enhance message filtering to support WeChat red packet messages, allowing for sender identification in chat records. 2025-12-01 10:38:27 +08:00
wong
31fdabaa15 Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into wong-dev
# Conflicts:
#	Server/composer.json
2025-12-01 10:27:35 +08:00
wong
43e9930b45 Merge tag 'v1.1.1-a' into develop 2025-12-01 10:17:25 +08:00
wong
a77192ebc2 Merge branch 'release/v1.1.1-a' 2025-12-01 10:17:18 +08:00
wong
181a792d0a Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into develop
# Conflicts:
#	Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx
2025-12-01 10:15:45 +08:00
wong
43f6a061e1 解决代码冲突问题 2025-12-01 10:05:26 +08:00
超级老白兔
05bba1a461 Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into develop 2025-11-28 23:00:44 +08:00
超级老白兔
97fc9959bb Refactor cache clearing functionality in settings page to improve user feedback and error handling. Update WeChat API functions to support debounce options for contact and group list retrieval. Enhance message rendering logic to include red packet messages and improve user identification in chat records. 2025-11-28 23:00:40 +08:00
wong
ad2d1c27ab 优化代码 2025-11-28 17:03:05 +08:00
wong
97144e4ebe Merge tag 'v1.1.1' into develop 2025-11-28 16:55:27 +08:00
wong
0cc30e5cb1 Merge branch 'release/v1.1.1' 2025-11-28 16:55:20 +08:00
超级老白兔
eebb73f49e Merge branch 'develop' into yongpxu-dev
# Conflicts:
#	Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx   resolved by yongpxu-dev version
2025-11-28 16:52:00 +08:00
超级老白兔
72626e3367 优化AI触发条件,确保只有对方发送的文字消息才会触发AI处理,提升消息处理逻辑的准确性。 2025-11-28 16:50:30 +08:00
wong
07a44e104d 导出表格优化兼容 2025-11-28 16:48:35 +08:00
wong
e4a8975baa 代码提交 2025-11-28 16:44:08 +08:00
超级老白兔
23de3cac7e 新增同步状态提示栏,优化消息列表组件的同步逻辑,支持逐页同步并即时更新UI,提升用户体验和代码可读性。 2025-11-28 16:32:23 +08:00
wong
0244871fc1 好友/群聊迁移优化 2025-11-28 16:04:29 +08:00
wong
5691d78004 朋友圈表格导出 2025-11-28 16:03:56 +08:00
超级老白兔
9e76d39707 移除ToContract组件中的一键转回按钮,调整代码以改善可读性和维护性。 2025-11-28 15:49:35 +08:00
wong
5087190816 代码提交 2025-11-28 15:46:21 +08:00
超级老白兔
ce47856c81 优化消息列表组件,增加加载状态的视觉反馈,支持根据微信号进行会话筛选,并重构相关逻辑以提升用户体验和代码可读性。 2025-11-28 15:33:32 +08:00
wong
a03c162962 111 2025-11-28 15:32:02 +08:00
wong
e658a32244 1 2025-11-27 18:10:23 +08:00
wong
637dcddee2 聊天消息记录状态 + 表格导出底层 2025-11-27 17:09:53 +08:00
超级老白兔
5c7940538d 更新QuickWords组件的默认选项,将活动标签从公共快捷语调整为个人快捷语,以改善用户体验。 2025-11-27 17:07:48 +08:00
超级老白兔
499b36b03b 新增公共快捷语选项并调整部门快捷语标签,优化QuickWords组件的用户体验。 2025-11-27 17:06:20 +08:00
超级老白兔
9e101e924e 新增最终消息同步处理逻辑,确保在消息发送后正确更新消息状态和相关数据。 2025-11-27 17:00:36 +08:00
超级老白兔
10f876f2f6 新增获取群聊成员列表API,优化消息记录组件以支持群聊成员信息展示,调整状态管理以防止跨联系人数据串联。 2025-11-27 16:38:28 +08:00
超级老白兔
ed4d305165 更新数据处理参数类型,调整消息处理逻辑以支持异步数据库同步,确保消息发送时正确传递群聊和个人信息。 2025-11-27 16:06:40 +08:00
超级老白兔
a5cc4e5e18 优化AI消息处理逻辑,区分AI辅助模式与AI接管模式,确保在接管模式下直接发送消息而不经过MessageEnter组件。同时,调整相关组件的状态管理,提升代码可读性和维护性。 2025-11-27 15:07:34 +08:00
超级老白兔
65da14032b 1、发起群聊全选✔ 2025-11-27 10:45:13 +08:00
wong
7e2dd2914d 健康分功能提交 + 微信客服页面改版 2025-11-26 11:17:23 +08:00
超级老白兔
644dd8912d 重构CustomerList组件样式和结构。更新了用户头像样式,以获得在线/离线状态的更好视觉反馈,添加了头像包装以保持布局一致性,并调整了在线指示器的尺寸和颜色。 2025-11-25 16:05:30 +08:00
超级老白兔
d7e08fe420 Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-11-25 15:38:59 +08:00
超级老白兔
cd27506388 cf 2025-11-25 15:37:51 +08:00
超级老白兔
78de6c92b7 新增好友添加任务列表API,优化NavCommon组件,移除未使用的消息处理逻辑,简化代码结构,提升可读性和维护性。 2025-11-25 15:36:43 +08:00
wong
cd41190663 好友添加记录 2025-11-25 14:52:46 +08:00
wong
bbdfdc136b Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into develop 2025-11-24 17:19:06 +08:00
wong
5a52b56d89 代码优化 2025-11-24 17:18:49 +08:00
超级老白兔
d8d1003d7a Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into develop 2025-11-24 15:38:20 +08:00
超级老白兔
c801490c2f 删除腾讯地图定位服务修复说明文档,更新CustomerList组件样式以支持离线用户状态,调整MessageList组件的加载状态管理,增强filter工具以支持XML格式位置消息的识别。 2025-11-24 15:37:57 +08:00
wong
30b33ec974 朋友圈采集优化 2025-11-24 11:03:31 +08:00
wong
4140e6d3d7 Merge branch 'feature/rating' into develop 2025-11-24 10:14:06 +08:00
wong
1786dec900 代码优化 2025-11-24 10:13:30 +08:00
超级老白兔
bda57a84e8 优化LocationMessage组件,添加经纬度格式化功能,生成静态地图预览图URL,改进位置信息展示,增强用户体验和视觉效果。 2025-11-22 16:15:33 +08:00
超级老白兔
deac306f69 修正SelectMap组件中的经纬度标记顺序,更新XML生成逻辑以确保特殊字符转义,提升地图位置选择的准确性和稳定性。 2025-11-22 15:57:16 +08:00
超级老白兔
43b60a0049 优化SelectMap组件,增强TMap API加载和初始化逻辑,添加API可用性检查,改进标记样式创建和错误处理,提升用户体验和稳定性。 2025-11-22 14:16:40 +08:00
超级老白兔
ab2ee050de 优化SelectMap组件,更新IP定位服务的实现,增强错误处理逻辑,确保用户位置获取更加准确和稳定,同时调整样式以提升用户体验。 2025-11-22 11:53:54 +08:00
超级老白兔
21de87ca32 更新SelectMap组件以使用新版腾讯地图API,优化地图加载和用户位置获取逻辑,增强错误处理和用户体验。 2025-11-22 11:45:53 +08:00
超级老白兔
782031cbc6 增强SelectMap组件,添加用户当前位置获取功能,优化地址解析逻辑和错误处理,改进加载状态显示,提升用户体验。 2025-11-21 19:02:41 +08:00
超级老白兔
6c9551be05 腾讯地图功能锁定 2025-11-21 18:30:12 +08:00
wong
36eb67a217 客服评分体系优化 2025-11-21 17:22:33 +08:00
超级老白兔
7dcbcfacc1 重置一下 2025-11-21 16:33:02 +08:00
超级老白兔
2c9bda734e 重构SelectMap组件,集成腾讯地图API,替换AMap。更新了标记处理和地址检索逻辑,以利用腾讯的地理编码和位置建议服务。增强的错误处理和加载状态,以改善用户体验。 2025-11-21 15:51:50 +08:00
超级老白兔
d296d019e5 Enhance MessageEnter component by adding a map selection feature. Introduced a new button for map visibility and integrated SelectMap component for improved user interaction. 2025-11-21 14:55:56 +08:00
wong
ccf947c64d Merge branch 'release/rating' into develop 2025-11-21 14:47:42 +08:00
wong
3faee0d8be 客服健康分提交 2025-11-21 14:38:29 +08:00
超级老白兔
fe12f78c1d 优化MessageRecord组件的自动滚动逻辑,确保在新消息到达且没有视频状态变化时才滚动到底部,同时修复了对消息长度的引用以提高性能。 2025-11-21 10:34:55 +08:00
超级老白兔
a2b0041490 更新NavCommon组件中的按钮图标和文本,将内容管理按钮的图标更改为SendOutlined,并将其文本更改为“发朋友圈”。同时,调整powerCenter模块中的数据结构,注释掉内容库配置部分,并优化样式代码。更新ContentManagement组件的标题为“发朋友圈”。 2025-11-20 16:50:20 +08:00
wong
235e3ed994 代码优化 2025-11-20 16:18:16 +08:00
wong
4baa82449c Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into wong-dev
# Conflicts:
#	Server/composer.json
2025-11-20 16:15:30 +08:00
超级老白兔
84ced0c32a 优化ProfileModules组件中的状态管理,使用选择器函数订阅匹配的客服对象,避免不必要的重新渲染。同时在DetailValue组件中引入useRef进行深度比较,确保只有在值真正变化时才更新编辑状态。 2025-11-20 16:13:57 +08:00
wong
beabe1c191 Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into release/friend 2025-11-20 16:12:09 +08:00
wong
719ed98996 代码提交 2025-11-20 16:11:27 +08:00
wong
b0a60e428c 账号评分 2025-11-20 16:07:57 +08:00
超级老白兔
16a8656b49 从消息推送助手任务创建中删除弃用的提示示例,更新推送历史API以使用pushTypeCode和状态,并通过改进的类型处理和UI调整来增强PushHistory组件。 2025-11-20 15:52:05 +08:00
超级老白兔
7fdb993f57 Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into develop 2025-11-20 14:59:03 +08:00
超级老白兔
0e56f0808c 在NavCommon组件中实现缓存清除功能,包括localStorage、sessionStorage和IndexedDB。在缓存清除期间添加加载消息并相应地更新菜单选项。 2025-11-20 14:57:18 +08:00
超级老白兔
bd45a9252a 存一下 2025-11-20 14:32:24 +08:00
wong
5a5160b92d 代码优化 2025-11-20 11:48:31 +08:00
wong
3e78122659 composer依赖提交 2025-11-20 11:45:53 +08:00
wong
57d85765c3 1 2025-11-20 10:43:29 +08:00
wong
067ae0fd10 1 2025-11-20 10:36:17 +08:00
wong
bf421e1a49 1 2025-11-19 17:58:54 +08:00
wong
339f06e630 1 2025-11-19 17:53:03 +08:00
wong
367a250fba 代码优化 2025-11-19 15:52:47 +08:00
wong
73110acb37 Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into wong-dev 2025-11-19 15:32:43 +08:00
wong
0912a0067e 内容库优化 2025-11-19 15:28:24 +08:00
wong
5faf83e437 内容库优化 2025-11-19 15:02:10 +08:00
超级老白兔
3f76f37a4c 从pnpm-lock.yaml中删除dexie依赖项,并更新PushHistory组件中的列标题,以提高清晰度。 2025-11-19 14:39:03 +08:00
wong
f0574ef555 Merge tag 'v1.1.0' into develop 2025-11-19 10:38:29 +08:00
超级老白兔
304059bef7 Merge branch 'develop'
# Conflicts:
#	.gitignore   resolved by develop version
#	Moncter/.gitignore   resolved by develop version
#	Touchkebao/.gitignore   resolved by develop version
#	Touchkebao/.vite/deps/_metadata.json   resolved by develop version
2025-11-19 10:29:40 +08:00
超级老白兔
c60e361aa3 gogo 2025-11-19 10:27:21 +08:00
wong
273ea73b1b 代码优化 2025-11-19 10:22:19 +08:00
超级老白兔
faae2906b2 Merge branch 'develop' into yongpxu-dev2 2025-11-19 10:17:39 +08:00
超级老白兔
4db65c2cbf Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-11-19 10:06:09 +08:00
超级老白兔
bb2b681ae0 重构充值订单管理:更新OrderList接口以支持灵活的数据类型,增强订单状态处理,并改进订单记录的UI交互。实现付款连续逻辑,并通过更好的状态表示来优化订单详细信息呈现。 2025-11-19 10:04:32 +08:00
超级老白兔
e9aa500800 优化消息推送助手组件中的代码格式,统一导航路径的换行方式,更新描述文本以改善用户体验。 2025-11-19 09:43:06 +08:00
超级老白兔
6eeabe4203 重构ProfileCard组件以支持新的DetailValue组件,优化个人资料和群组信息的编辑逻辑。引入状态管理以处理contract的变化,确保数据一致性。更新样式以改善用户体验,并移除不再使用的状态和逻辑。 2025-11-18 18:02:30 +08:00
超级老白兔
caef9d7d46 增强TwoColumnSelection组件,添加加载更多功能和状态管理,优化好友列表的分页逻辑。重构联系人数据获取逻辑,移除不再使用的状态管理,确保组件性能和用户体验。更新ProfileCard组件以支持新的联系人加载方式。 2025-11-18 14:22:42 +08:00
wong
f7c0679abc 在线更新换位置 2025-11-18 14:14:29 +08:00
wong
fb9e61aafe 数据统计 2025-11-18 14:14:09 +08:00
wong
316cac8149 好友迁移 2025-11-18 14:13:52 +08:00
wong
4ca75d4907 内容库AI提示词 2025-11-18 14:13:08 +08:00
超级老白兔
7d9f9fbd08 feat: 添加成员功能完成 2025-11-18 11:57:54 +08:00
wong
95eed69b66 依赖提交 2025-11-17 17:52:42 +08:00
wong
8fccc61e38 bug优化 2025-11-17 17:34:46 +08:00
超级老白兔
1582e22756 优化MessageRecord组件中的条件渲染逻辑,使用双重否定确保isOwn状态的正确判断。移除多余的换行以简化代码结构。 2025-11-14 18:54:04 +08:00
超级老白兔
4684e880b1 重构聊天消息处理和分页逻辑。为消息记录参数引入一个新的接口,以增强类型安全性。更新获取聊天消息和聊天室消息的API调用,以支持分页。通过删除不必要的刷新触发器和优化会话状态管理来优化MessageList组件。改进加载状态处理,并确保组件之间消息加载行为的一致性。 2025-11-14 18:23:54 +08:00
wong
227fcae39d 代码优化 2025-11-14 18:19:08 +08:00
超级老白兔
8d5869e6c2 /v1/kefu/message/details 2025-11-14 17:09:43 +08:00
超级老白兔
eca5549592 优化SidebarMenu组件的内容渲染逻辑,使用useRef缓存选项卡内容以提高性能。更新联系人状态管理,添加搜索关键词的防抖处理,确保搜索请求不会频繁触发。引入当前用户ID的检查以优化搜索结果的处理。 2025-11-14 16:19:18 +08:00
超级老白兔
01bf7ee271 添加WebSocket状态管理中的活跃状态请求逻辑,防止频繁请求。引入新的状态变量以跟踪最后请求时间,并在发送活跃状态请求时进行时间间隔检查。清理相关状态以确保一致性。 2025-11-14 15:30:00 +08:00
wong
240743c563 创建部门的同时设置其权限 2025-11-14 14:30:20 +08:00
wong
94fa7a8279 代码提交 2025-11-14 14:14:00 +08:00
wong
5715849de5 代码提交 2025-11-14 11:39:20 +08:00
超级老白兔
dbe4ee692c 更新WeChat API以使用新端点,调整获取可转移客服列表的逻辑。重构ProfileCard组件以更改默认活动选项卡为“profile”,并优化状态管理。移除未使用的状态和逻辑,简化代码结构。 2025-11-14 11:00:21 +08:00
超级老白兔
b3924cdb71 优化CreatePushTask组件中的联系人过滤功能,添加基于标签、城市和昵称/备注的过滤模态。更新过滤模态的样式并增强过滤值的状态管理,以改善用户体验。 2025-11-14 10:07:38 +08:00
超级老白兔
c41d8125da 更新ContractData接口以使extendFields可选并修改相关序列化逻辑。增强数据库模式以支持extendFields作为JSON字符串,并确保在数据操作期间进行正确处理。改进ContactManager中的比较逻辑以包含extendFields。 2025-11-13 17:51:22 +08:00
wong
58243d9c17 手动录入表单新增备注功能 2025-11-13 16:11:56 +08:00
wong
3200f09836 订单明细优化 2025-11-13 16:11:19 +08:00
wong
93b4834539 群推送 2025-11-13 16:10:47 +08:00
wong
268da6e47c 文件上传大小修改为50MB 2025-11-13 16:09:09 +08:00
wong
a74f9b9557 优化客服列表(30天没有在线不显示) 2025-11-13 16:08:44 +08:00
wong
dfe4ff076d 新增更新用户扩展字段 2025-11-13 16:08:12 +08:00
超级老白兔
a6ee45f3e3 重构ProfileCard组件以增强选项卡管理并改善用户体验。引入基于可用键的动态选项卡呈现,并更新选项卡标题的样式。调整活动关键帧和渲染关键帧的状态管理。更新QuickWords和ProfileModules集成。修改GroupModal和QuickReplyModal以使用“destroyOnHidden”以获得更好的模式处理。 2025-11-13 16:07:52 +08:00
wong
cec0eff031 数据同步新增设备更新时间及微信更新时间(在线) 2025-11-13 16:06:39 +08:00
超级老白兔
ae4a165b07 重构数据库管理逻辑,简化用户数据库初始化流程。引入新的数据库管理类以支持动态数据库名称和用户状态管理。更新应用启动逻辑以确保在用户登录时正确初始化数据库。增强持久化数据恢复功能,确保用户数据的可靠性和一致性。 2025-11-13 11:58:12 +08:00
wong
bfaf21a180 Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into develop 2025-11-12 17:44:42 +08:00
wong
75e35f52ab 修改上传文上限 2025-11-12 17:43:57 +08:00
超级老白兔
eb5dbe5066 重构推送历史记录API以使用新端点并更新响应处理。增强PushHistory组件中的错误处理和分页逻辑,以改善用户体验。 2025-11-12 16:17:29 +08:00
超级老白兔
5da3bc3000 Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-11-12 14:02:01 +08:00
超级老白兔
d6a003c3e2 通过添加AI重写功能增强StepSendMessage组件。为AI重写引入新的状态管理,为内容修改实现新的AiRewrite方法,并更新AI操作按钮的样式。通过加载指示符和验证消息改善用户体验。 2025-11-12 12:30:57 +08:00
超级老白兔
f28f9049d5 重构PushTaskModal和PushTask组件以实现好友和消息发送的默认间隔。增强间隔状态管理,并在联系步骤中集成流量池选择。更新样式以改进布局和用户体验。 2025-11-12 12:21:04 +08:00
超级老白兔
e9994bcd8a Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-11-12 11:40:39 +08:00
超级老白兔
b1b68f4397 Add FloatingVideoHelp component to AppRouter for enhanced user assistance. 2025-11-12 11:40:05 +08:00
超级老白兔
87d1e1c44f 重构PushTask组件以支持用于消息管理的脚本组。引入新的状态变量来处理脚本消息和组,更新消息发送逻辑,并增强用于添加和管理脚本组的UI。清理样式,改善消息输入区的用户体验。 2025-11-11 16:20:46 +08:00
超级老白兔
bb72038e2b Implement contact filtering functionality in CreatePushTask component. Add modal for filtering contacts by tags, cities, and nickname/remark. Update styles for filter modal and enhance state management for filter values. 2025-11-11 11:56:38 +08:00
超级老白兔
2f804c7d40 聊天记录列表优化补齐 2025-11-11 10:26:51 +08:00
超级老白兔
5a287a42ac Refactor CreatePushTask component to streamline account and contact selection process. Update styles for layout consistency, enhance user experience with improved step indicators, and simplify state management for selected accounts and contacts. 2025-11-10 18:08:53 +08:00
超级老白兔
d940d36123 Refactor styles and structure for account selection in CreatePushTask component. Update account list display to use a grid layout, enhance account card styling, and improve status indicators for online/offline status. Implement customer list fetching logic on component mount. 2025-11-10 15:55:14 +08:00
超级老白兔
a8b0add92b Merge branch 'yongpxu-dev' into develop 2025-11-08 18:05:15 +08:00
超级老白兔
49d8c4f818 Enhance PopChatRoom component to include a modal for entering chatroom name before group creation, update page size to 10, and improve state management for selected contacts and loading status. 2025-11-08 17:57:28 +08:00
wong
674bbe1c3e 代码提交 2025-11-08 17:29:33 +08:00
超级老白兔
daf504d5fd Add dropdown menu in SidebarMenu for adding friends and creating group chats, including modals for each action 2025-11-08 17:00:40 +08:00
超级老白兔
d722c03744 Add a primary button with a Plus icon to the SidebarMenu and adjust styles for better layout 2025-11-08 16:16:32 +08:00
超级老白兔
6eddf2f6db 同步 2025-11-08 16:02:16 +08:00
超级老白兔
02398e9ced Merge branch 'yongpxu-dev' of https://gitee.com/cunkebao/cunkebao_v3 into yongpxu-dev
# Conflicts:
#	Moncter/app/controller/UserController.php   resolved by yongpxu-dev version
2025-11-08 16:02:05 +08:00
超级老白兔
35c9369d3d Refactor IndexController to return a simple message instead of an iframe. Update AudioRecorder and SimpleFileUpload components to include file name in the upload callback. Modify MessageEnter component to handle new file structure and improve message handling logic. Clean up unused state and enhance video message rendering. Update websocket message management to handle message IDs more robustly. 2025-11-08 15:13:20 +08:00
wong
018a6162da 1 2025-11-08 14:17:10 +08:00
wong
b46c808aa8 代码提交 2025-11-07 17:57:18 +08:00
超级老白兔
921ce713fa 基础数据库建设,数据中心对接数据库 2025-11-07 15:40:33 +08:00
wong
cf75ee4150 代码提交 2025-11-07 15:25:50 +08:00
超级老白兔
a997734c5c 添加 go.sh 脚本以自动执行 PHP 脚本并处理权限检查 2025-11-07 11:31:14 +08:00
超级老白兔
f9c08b6091 重构状态管理:将 useWeChatStore 更名为 useDataCenterStore,并更新相关引用 2025-11-06 18:08:49 +08:00
wong
8daa0a5d5c 门店端优化 2025-11-06 10:34:13 +08:00
超级老白兔
a3af9de4a9 Update typeId in login parameters to a fixed value for consistency in Login component. 2025-11-06 10:21:53 +08:00
超级老白兔
e467a8e6e0 Add typeId to login parameters for session verification in Login component. 2025-11-06 10:20:39 +08:00
wong
46c67e3bfd Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into develop 2025-11-05 16:06:02 +08:00
wong
a6e39c32c1 代码优化 2025-11-05 16:05:45 +08:00
超级老白兔
fa802b50ad Add 404 Not Found route to the router configuration to handle unmatched paths. 2025-11-05 10:37:39 +08:00
超级老白兔
e163ada783 Merge branch 'develop' into yongpxu-dev 2025-11-04 19:34:12 +08:00
超级老白兔
888e2b376a Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-11-04 19:33:36 +08:00
wong
adc22c7d19 代码提交 2025-11-04 17:48:51 +08:00
超级老白兔
ac605baa24 Refactor TrafficDistributionForm component to improve state management and error handling. Update time range parsing logic for better validation and user feedback. Adjust form submission handling and enhance device selection logic. Modify slider maximum value for daily distribution limits. 2025-11-04 16:40:53 +08:00
超级老白兔
05c5915e7b Add description field to PowerPackage interface and update UI to display package descriptions. Enhance styles for description tags in Buy Power component. 2025-11-04 15:09:09 +08:00
wong
a4060fec1b 代码提交 2025-11-03 14:42:16 +08:00
wong
469ab4a085 门店端优化 2025-11-03 14:07:15 +08:00
wong
367a76a54f 代码提交优化 2025-11-03 14:06:58 +08:00
超级老白兔
998e65d98d Update WebSocket configuration to use environment variable for URL instead of hardcoded value. 2025-11-03 10:31:31 +08:00
超级老白兔
a8ba45c08a Merge branch 'yongpxu-dev' of https://gitee.com/cunkebao/cunkebao_v3 into yongpxu-dev 2025-11-03 10:11:16 +08:00
超级老白兔
055b300c5a Update API URLs in development and production environment files to new server endpoints for consistency across environments. 2025-11-03 10:11:12 +08:00
超级老白兔
baf389ec65 Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-11-03 10:04:09 +08:00
超级老白兔
ca7a2e954f Implement power statistics API and enhance Power Management component. Add new Statistics interface and update UI to display formatted usage data. Adjust styles for better readability. 2025-10-31 16:32:04 +08:00
超级老白兔
eada5fe8b1 Refactor AIKnowledgeDetail and AIKnowledgeList components to improve user navigation and search functionality. Update deletion logic to navigate back on success and enhance search with debouncing and keyword handling. Adjust API to support keyword filtering in fetchKnowledgeBaseList. 2025-10-31 16:18:12 +08:00
超级老白兔
fe347965ff Enhance FileUpload component to support additional file types including PDF, TXT, MD, MP4, and AVI. Refactor file type handling logic for improved accuracy and user feedback. Update styles in AIKnowledgeDetail for better layout and user experience, including new sections for prompt rules and independent prompts. 2025-10-31 15:46:51 +08:00
超级老白兔
8d017ed2cb Integrate FileUpload component for material uploads in AIKnowledgeDetail. Update upload logic to handle file types and improve user feedback on upload success or failure. Adjust tab rendering order for better clarity. 2025-10-30 17:04:11 +08:00
超级老白兔
eafca9ae85 Replace custom close button in GlobalPromptModal with Ant Design's CloseOutlined icon for improved consistency and user experience. Simplify the onClose functionality while maintaining existing styles. 2025-10-30 16:44:17 +08:00
超级老白兔
990c7a2693 Refactor API interactions in GlobalPromptModal to utilize new initGlobalPrompt and saveGlobalPrompt functions. Update modal logic to handle prompt configuration more effectively, removing deprecated state management for enabling prompts. Enhance user experience with improved input handling and feedback mechanisms. 2025-10-30 16:42:42 +08:00
超级老白兔
da87f9a865 Refactor GlobalPromptModal to use antd-mobile Popup for improved mobile experience. Update styles for modal footer and enhance user feedback with Toast notifications. Remove deprecated Ant Design Modal components and adjust layout for better usability. 2025-10-30 16:26:15 +08:00
超级老白兔
515c85e600 Enhance Buy Power and Recharge components with loading states and pagination. Update styles for better layout and introduce new pagination UI in the Power Management section. Remove outdated AI knowledge interface documentation. 2025-10-30 16:08:43 +08:00
超级老白兔
6b3948d454 Refactor Power Management component to enhance user experience with improved power package and consumption record management. Introduce new API interfaces for power statistics and consumption records, and update UI elements for better navigation and interaction. 2025-10-30 15:26:48 +08:00
wong
e3afa24795 代码优化 2025-10-30 11:41:22 +08:00
wong
903a29ed09 app更新提示 2025-10-30 10:58:45 +08:00
超级老白兔
6a64e3854c Refactor Recharge component to support custom recharge amounts and quick selection options. Remove version package data and enhance payment handling with improved user feedback. Add AI knowledge management routes and UI elements in the workspace section. 2025-10-29 17:49:51 +08:00
超级老白兔
0d1d082d9e Add manual AI response generation feature in MessageEnter component. Implement handleManualTriggerAi function to allow users to trigger AI replies on demand. Update weChat store with manualTriggerAi function to manage AI request logic and context retrieval. Enhance UI with a reload button for AI responses in AI modes. 2025-10-29 16:56:44 +08:00
wong
8ae8e66a3a 代码优化 2025-10-29 15:48:48 +08:00
wong
f684c4cb3f 代码提交 2025-10-29 15:31:43 +08:00
超级老白兔
44a8fbe0c9 用户操作绝对优先 - 任何用户行为立即打断AI
唯一ID验证 - 防止过期回复覆盖
状态互斥保护 - AI生成 ⇄ 用户输入互斥
智能防抖 - 3秒延迟合并多条消息
2025-10-29 15:21:27 +08:00
超级老白兔
9c5e253d75 Refactor MessageEnter component to streamline AI message handling. Update message sending logic to prioritize passed content, ensuring better integration with AI-assisted and AI takeover modes. Clean up unused code and enhance console logging for improved debugging. 2025-10-29 14:53:15 +08:00
超级老白兔
5fb15592ff Refactor MessageEnter component to handle AI-assisted and AI takeover scenarios. Update weChat store to support multiple AI types for message processing. Remove unnecessary console log in message response handler for cleaner output. 2025-10-29 14:50:55 +08:00
wong
da8bcb04c9 免密登录代码提交 2025-10-29 11:48:02 +08:00
wong
1862b11c0d 修复bug 2025-10-29 10:52:44 +08:00
wong
942f08bbf2 111 2025-10-29 10:38:11 +08:00
wong
175c78c3d0 数智员工免密登录 2025-10-29 10:30:50 +08:00
超级老白兔
0decc34593 Enhance file upload process in devlop.py with progress tracking and remote file validation. Add aiType field to weChatGroup and ContractData interfaces for AI type differentiation. Update ChatWindow component to handle AI configuration changes and ensure proper database updates for contact and message management. Implement database versioning to include aiType in chatSessions and contactsUnified tables, with migration logic for existing data. Improve error handling and user feedback during configuration updates. 2025-10-28 16:55:55 +08:00
wong
18a7055a4e 消息新增ai类型字段 2025-10-28 16:46:38 +08:00
超级老白兔
5638d8c66a Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-10-28 14:32:37 +08:00
wong
1644e478c7 AI对话功能优化 2025-10-28 09:25:39 +08:00
超级老白兔
d1c35b480f Merge branch 'yongpxu-dev' into yongxu-dev3 2025-10-27 17:13:24 +08:00
超级老白兔
3d8c656224 加载动画优化 2025-10-27 17:08:37 +08:00
超级老白兔
364a52b553 Remove Dexie database implementation and related initialization logic. Update Recharge component to fetch and display subscription packages dynamically, enhancing user experience with improved payment handling and UI updates. 2025-10-27 17:07:10 +08:00
超级老白兔
8cff6750f9 Implement AI loading animation in MessageEnter component and enhance user interface. Remove unused loading icon and streamline input area for better user experience during AI response generation. 2025-10-27 16:57:26 +08:00
超级老白兔
d01c91e6ee 只有文字消息才会触发ai回复 2025-10-27 15:49:52 +08:00
超级老白兔
e1893e1506 增加消息类型 2025-10-27 15:45:07 +08:00
超级老白兔
ae4b592bb6 转给他人逻辑处理,包括关闭和删除联系人、联系会话 2025-10-27 15:38:01 +08:00
超级老白兔
c65001399f 完整的wechatAccountId处理
接口查询获取
字段映射兜底(accountId)
保证每个会话都有正确的账号ID
 头像正确显示
好友:avatar 字段
群聊:chatroomAvatar 字段
统一存储到 ChatSession.avatar
 三层兜底机制
从联系人表查询(最快)
从接口获取详情(完整)
使用消息数据创建(兜底)
 数据持久化
联系人数据存入 contactsUnified 表
会话数据存入 chatSessions 表
下次收到消息直接从本地读取
 静默更新机制
所有数据库操作在后台完成
通过 triggerRefresh() 触发UI更新
用户体验流畅无感知
2025-10-27 15:30:27 +08:00
超级老白兔
494ccbbe5e 修复消息列表 2025-10-27 14:49:05 +08:00
超级老白兔
8b28194d00 优化头像兼容 2025-10-27 11:48:22 +08:00
超级老白兔
3ff3e40d8e Enhance MessageList component to include complete contact information during message updates. Implement a new method in ContactManager for retrieving contacts by ID and type, ensuring accurate avatar and nickname display. Trigger UI refresh on data changes to improve user experience. 2025-10-27 11:43:35 +08:00
超级老白兔
77072594d2 Add debug logging for quote message content and session selection in MessageEnter and MessageList components. Update WeChat store to handle empty message content gracefully, improving error handling during message reception. 2025-10-27 11:11:24 +08:00
超级老白兔
3c2fec2122 Refactor MessageRecord component to replace ckchat store with customer store for improved data handling. This change enhances the retrieval of customer information based on the wechatAccountId, streamlining the message processing logic. 2025-10-27 10:38:39 +08:00
超级老白兔
f58070495f Refactor WeChat store logic to enable AI chat processing. Reintroduce data processing and AI chat request handling, improving message handling flow and user experience. 2025-10-27 10:00:32 +08:00
超级老白兔
51c60c55b0 Merge branch 'develop' into yongpxu-dev 2025-10-27 09:55:09 +08:00
超级老白兔
781662026a 优化消息过滤逻辑,新增对语音消息的处理条件,支持通过URL和持续时间判断,同时增强对特殊格式消息的识别,提升代码可读性和用户体验。 2025-10-25 19:15:00 +08:00
超级老白兔
422fed2719 新增音频转文字功能,更新相关API和组件逻辑,优化消息处理流程,提升用户体验和代码可读性。 2025-10-25 19:11:08 +08:00
超级老白兔
27a108027b 移除MessageList组件中的调试日志,新增通过ID查找消息的功能,优化消息处理逻辑,提升代码可读性和用户体验。 2025-10-25 19:00:20 +08:00
超级老白兔
5f1a1e9f0e 新增消息序列号支持,优化消息发送和接收逻辑,更新消息状态处理,提升代码可读性和用户体验。 2025-10-25 18:42:17 +08:00
wong
aa3c07a0b2 ai对话功能提交 2025-10-25 17:41:20 +08:00
超级老白兔
e409618b7a 增强SidebarMenu和MessageList组件,新增对currentContact变化的监听,自动切换聊天tab并显示加载状态,优化消息列表的加载逻辑,提升用户体验和代码可读性。 2025-10-24 17:46:44 +08:00
超级老白兔
7d8573b52a 优化联系人分组统计逻辑,分别提取好友和群组标签ID以提高准确性,更新相关注释以增强代码可读性,同时调整数据库查询条件以支持更灵活的分组查询。 2025-10-24 17:20:09 +08:00
超级老白兔
28cbcea4f9 重构数据库管理逻辑,新增旧数据库清理功能,优化数据库初始化流程,更新联系人管理和消息处理逻辑,提升代码可读性和用户体验。 2025-10-24 16:14:13 +08:00
超级老白兔
e1d4f678ab 重构MessageRecord组件,更新样式文件引用,优化消息转发逻辑,新增消息过滤功能以提升用户体验和代码可读性。 2025-10-24 14:44:25 +08:00
超级老白兔
bb6453d3a2 重构加载更多功能,更新样式和逻辑,优化联系人列表组件的加载状态处理,提升用户体验和代码可读性。 2025-10-24 12:01:01 +08:00
超级老白兔
1b02666531 增强SidebarMenu和MessageList组件,新增对currentContact变化的监听,自动切换聊天tab,优化联系人点击处理逻辑,调整置顶标识为数字类型,提升代码可读性和用户体验。 2025-10-24 11:28:01 +08:00
超级老白兔
3b82908e8a 重构SidebarMenu组件,移除不必要的useEffect,优化消息列表和联系人组件的加载逻辑,合并样式文件以提升代码可读性和维护性。 2025-10-23 20:42:29 +08:00
超级老白兔
8941b87e03 优化消息列表组件,更新刷新状态提示样式,提升用户体验和可读性。 2025-10-23 20:18:45 +08:00
超级老白兔
1f081cdddc 重构联系人分组逻辑,新增获取分组统计信息和分页获取联系人功能,优化联系人列表组件以提升用户体验和代码可读性。 2025-10-23 20:15:59 +08:00
超级老白兔
dc58109829 重构联系人管理逻辑,新增联系人状态管理和数据同步功能,优化联系人列表组件以提升用户体验和代码可读性。 2025-10-23 19:56:57 +08:00
超级老白兔
3b36b19d01 更新TypeScript配置和Vite配置,新增模块路径别名支持,同时优化数据库初始化逻辑,提升代码可读性和结构。 2025-10-23 18:56:16 +08:00
超级老白兔
26af225fff 优化消息排序逻辑,更新生成排序键的注释,明确置顶标识、时间戳和显示名称的处理方式,提升代码可读性。 2025-10-23 18:00:07 +08:00
超级老白兔
1c9f986a97 优化消息列表组件,更新已读标记逻辑,确保仅更新未读数量而不影响时间和排序,提升代码可读性。 2025-10-23 17:30:30 +08:00
超级老白兔
7056e00fcc 更新消息列表组件,新增当前客户状态管理,优化消息筛选逻辑以提升用户体验和代码可读性。 2025-10-23 17:21:25 +08:00
超级老白兔
5c86c2fbf2 优化消息排序逻辑,调整置顶和时间戳的处理方式,以确保置顶消息优先显示,提升消息列表的可读性和用户体验。 2025-10-23 17:14:48 +08:00
超级老白兔
7f4bc0487e 新增消息列表API,优化消息列表组件以支持加载状态和数据同步,提升用户体验和代码可读性。 2025-10-23 17:02:33 +08:00
超级老白兔
75e62cce72 优化消息管理逻辑,新增消息状态管理功能,提升代码结构和可读性。 2025-10-23 15:51:05 +08:00
wong
a5b1861e7d 性能优化 2025-10-23 14:12:40 +08:00
超级老白兔
68a5350c19 移除未使用的客服列表相关代码,优化消息管理逻辑,新增消息状态管理功能,提升代码结构和可读性。 2025-10-23 12:55:57 +08:00
超级老白兔
81f225d9cb 更新TypeScript配置以支持新的模块路径别名,重命名获取客服列表的API函数,替换相关组件,移除不再使用的垂直用户列表组件及其样式,提升代码结构和可读性。 2025-10-23 12:35:30 +08:00
wong
268aedbb6e 代码统一大小写 2025-10-23 09:51:43 +08:00
wong
c2dce8344b 代码提交 2025-10-23 09:34:43 +08:00
超级老白兔
ef45bedf83 新增AI对话加载状态管理,优化消息输入组件以支持加载指示,提升用户体验。 2025-10-22 17:05:35 +08:00
超级老白兔
35b38e8542 Merge branch 'yongpxu-dev' into develop 2025-10-22 16:36:17 +08:00
超级老白兔
c3f1433868 新增AI对话和数据处理接口,优化消息发送逻辑以支持群组和个人消息的处理,提升聊天功能的灵活性和用户体验。 2025-10-22 16:36:02 +08:00
超级老白兔
f137bd2c07 优化消息列表组件,新增消息内容展示和时间格式处理逻辑,更新API以支持分页,提升用户体验和代码可读性。 2025-10-22 15:32:53 +08:00
超级老白兔
5b9ae2b805 优化消息列表组件,新增消息内容展示,调整消息时间格式处理逻辑,更新获取消息列表的API以支持分页,提升用户体验和代码可读性。 2025-10-22 15:28:03 +08:00
超级老白兔
eef514bea1 Merge branch 'yongpxu-dev' into develop 2025-10-22 10:43:36 +08:00
wong
9be85c4c2d 代码优化 2025-10-22 10:42:37 +08:00
超级老白兔
17e81cafc2 更新内容管理模块,调整数据结构以使用更具描述性的字段名,移除不再使用的备注输入,优化定时发布功能的时间格式处理,提升用户体验和代码可读性。 2025-10-22 10:41:19 +08:00
超级老白兔
b6b1d581a3 1 2025-10-22 10:03:25 +08:00
wong
7db48facbd 表单提交功能优化 2025-10-22 09:55:49 +08:00
wong
0958c5f9c7 消息接收数据 2025-10-21 16:48:00 +08:00
wong
7475dbd07c 代码提交 2025-10-21 16:46:55 +08:00
wong
19734415f0 【门店端 - AI数智员工】 代码优化 2025-10-21 11:06:53 +08:00
wong
252d02e7b9 【触客宝】内容管理代码优化 2025-10-21 11:05:57 +08:00
超级老白兔
64ad480bbb 重构内容管理模块,移除不再使用的管理组件和模态框,优化样式布局,新增发布和计划功能组件,提升用户体验和界面一致性。 2025-10-20 15:36:32 +08:00
超级老白兔
4df670d1c9 重构流量池表单组件:添加预设方案选择功能,优化用户数据生成逻辑,增强提交状态管理,提升用户体验和代码可维护性。 2025-10-20 15:04:41 +08:00
wong
52f4a2b1e1 代码提交 2025-10-20 14:52:10 +08:00
wong
527d5341db 代码提交 2025-10-20 14:22:20 +08:00
超级老白兔
a775596719 优化流量池列表样式:调整卡片内边距和菜单按钮位置,提升布局美观性和用户体验。 2025-10-17 18:24:50 +08:00
超级老白兔
3fbf54df99 移除流量池列表样式:删除不必要的样式规则,简化代码结构,提升可维护性和用户体验。 2025-10-17 18:22:38 +08:00
超级老白兔
fbb78ebd50 移除流量池详情及相关组件:删除流量池详情页面、API、数据类型和样式文件,简化代码结构,提升可维护性和用户体验。 2025-10-17 18:18:17 +08:00
wong
87bf284943 代码提交 2025-10-16 16:04:53 +08:00
wong
d319264349 数据同步 2025-10-16 15:36:44 +08:00
超级老白兔
3230bbe2e5 重构流量池模块:更新路由配置,移除流量池列表组件及相关API和样式文件,简化代码结构,提升可维护性和用户体验。 2025-10-16 14:13:36 +08:00
超级老白兔
fc5ae31b2b 优化流量池列表样式:重构样式文件,增加卡片样式和布局,提升视觉效果和用户体验;更新组件逻辑,简化代码结构,增强可维护性。 2025-10-16 14:11:26 +08:00
wong
8bf6cbe1d4 代码提交 2025-10-16 11:42:59 +08:00
wong
1083822513 代码提交 2025-10-16 10:59:08 +08:00
wong
db22100846 代码同步 2025-10-15 16:39:10 +08:00
超级老白兔
d751bbfcd4 优化会话置顶功能,更新状态切换逻辑并添加删除会话功能;新增样式以突出置顶会话,提升用户体验。 2025-10-15 15:50:31 +08:00
超级老白兔
ceffea4c5b 重构流量池模块:更新API接口,优化数据结构,移除不必要的组件,简化代码逻辑,提升可维护性和用户体验。 2025-10-15 15:47:34 +08:00
超级老白兔
a716315311 新增右键菜单功能,支持会话置顶、删除和修改备注操作;更新样式以提升用户交互体验,优化会话信息展示逻辑。 2025-10-15 15:04:48 +08:00
超级老白兔
edf5abcec9 新增消息记录组件对特定消息类型的处理逻辑,支持解析JSON格式内容并优先显示解析后的内容;优化消息过滤条件,确保正确展示消息记录。 2025-10-15 14:37:24 +08:00
wong
d26e5a324d 代码提交 2025-10-15 14:08:56 +08:00
超级老白兔
0275eea6e7 优化聊天会话排序逻辑:新增按置顶状态排序功能,优先显示置顶会话,其次按未读消息数量和显示名称排序,提升用户体验。 2025-10-15 14:08:53 +08:00
超级老白兔
7091adb6a0 更新 weChatGroup 接口,新增 top 字段为可选项,调整 unreadCount 字段为可选;修改 getNewContractList 方法返回类型为 Promise;新增 addCtrlUser 方法以控制终端用户;修复 deleteChatSession 方法参数类型为 number。 2025-10-15 10:49:44 +08:00
wong
f11bc7f686 好友数据同步优化 2025-10-15 10:47:48 +08:00
超级老白兔
3b0b83aae1 删除流量池相关组件:移除BatchAddModal、DataAnalysisPanel、FilterModal及其相关逻辑,简化代码结构,提升维护性。 2025-10-15 10:16:12 +08:00
超级老白兔
038c4149d8 更新聊天窗口和消息列表组件,优先显示用户备注名(conRemark)而非昵称,提升用户信息展示的准确性。 2025-10-14 18:20:51 +08:00
超级老白兔
811ebff52f 新增用户列表数据字段,更新导航组件图标和功能,优化内容管理界面,调整微信页面加载逻辑,移除ProfileCard组件中的调试信息。 2025-10-14 18:13:38 +08:00
wong
8908da5baf 代码提交 2025-10-14 17:45:03 +08:00
wong
204791075e Merge branch 'develop' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into develop 2025-10-14 17:03:27 +08:00
wong
da63930f5e 代码提交 2025-10-14 17:03:05 +08:00
超级老白兔
892e82c1d6 修复未读消息计数逻辑:将未读消息计数从 session.unreadCount 移动到 session.config.unreadCount,确保消息状态正确更新。 2025-10-14 15:29:17 +08:00
超级老白兔
806df16a0b 新增系统推荐备注消息组件,并在消息记录中处理该消息类型;优化未读消息计数逻辑,确保正确显示。 2025-10-14 14:34:50 +08:00
超级老白兔
077f4fd488 Merge branch 'yongpxu-dev' into develop 2025-10-14 10:05:32 +08:00
超级老白兔
0b30cc3ae7 gogogo 2025-10-14 10:05:19 +08:00
超级老白兔
b31e1a47f0 新增快捷回复功能:引入WebSocket支持,优化快捷语发送逻辑,增加发送确认预览,提升用户体验和代码可读性。 2025-10-13 17:28:51 +08:00
超级老白兔
7c36100553 重构快捷语组件:引入Layout组件,优化界面结构和样式,提升用户体验和代码可读性。 2025-10-13 17:23:31 +08:00
wong
f2c25927eb 客服朋友圈配置信息 2025-10-13 17:00:03 +08:00
wong
6e2fc369e2 客服端 内容管理功能 2025-10-13 16:14:33 +08:00
超级老白兔
57f2e0d649 重构快捷语管理模块:新增快捷回复和分组管理功能,优化数据展示和交互逻辑,提升用户体验和代码可读性。 2025-10-13 14:52:10 +08:00
wong
381eb37f1a 代码提交 2025-10-13 11:36:45 +08:00
超级老白兔
2c46cb956f 优化客户管理模块:调整标签页样式,重构联系人展示为表格形式,提升用户体验和代码可读性。 2025-10-11 15:54:39 +08:00
超级老白兔
31c7980a9a 优化客户管理模块:更新标签页样式,调整按钮组件,重构联系人展示为表格形式,提升用户体验和代码可读性。 2025-10-11 15:49:05 +08:00
超级老白兔
b067134ae7 新增聊天记录搜索功能:在聊天窗口中引入ChatRecordSearch组件,优化消息输入区域,更新状态管理以支持聊天记录模型的显示与隐藏,提升用户体验和代码可读性。 2025-10-10 15:45:28 +08:00
超级老白兔
be5a848342 更新功能中心模块:重构KPI统计区域,优化数据展示和样式,提升用户体验和代码可读性。 2025-10-10 14:38:35 +08:00
超级老白兔
1f3d39a1bd 重构功能中心模块:更新功能卡片数据结构,新增KPI统计区域,优化样式和布局,提升用户体验和代码可读性。 2025-10-09 15:42:46 +08:00
wong
e96c65c1ab 代码提交 2025-10-09 15:36:46 +08:00
wong
80495c3594 [] 算力扣款记录优化 2025-09-30 16:10:43 +08:00
wong
1667e5240d 算力套餐列表优化 2025-09-30 16:00:19 +08:00
wong
afb8ba0ef3 套餐优惠计算 2025-09-30 15:20:18 +08:00
wong
ddc3e855d4 支付回调优化 2025-09-30 14:47:26 +08:00
wong
d8f5771dcb 代码提交 2025-09-30 10:47:14 +08:00
wong
5313bc6a18 代码提交 2025-09-30 10:36:51 +08:00
wong
ff7b583f07 算力功能提交 2025-09-29 17:37:21 +08:00
wong
e1fce63876 消息提醒功能优化 2025-09-29 17:36:59 +08:00
超级老白兔
85149e5af0 优化导航组件:新增消息列表功能,定时获取未读消息数量,支持标记消息为已读和全部已读,提升用户体验和代码可读性。 2025-09-29 17:12:17 +08:00
超级老白兔
713956aa4f 更新关键词管理模块:调整关键词请求参数,优化关键词列表的获取逻辑,移除本地筛选功能,改为服务端搜索,提升代码可读性和用户体验。 2025-09-29 16:38:36 +08:00
wong
0da8a75fbe bug修复 2025-09-29 11:57:35 +08:00
wong
4a946062b3 bug修复 2025-09-29 11:57:17 +08:00
wong
5f0c1c8b97 代码优化 2025-09-29 11:46:37 +08:00
wong
0e6b885095 消息中心提交 2025-09-29 11:34:01 +08:00
超级老白兔
f5480bdc58 更新关键词管理模块:新增素材选择功能,优化回复类型切换逻辑,提升用户体验和代码可读性。 2025-09-29 11:05:52 +08:00
wong
5102f02083 消息提醒提交 2025-09-29 09:44:43 +08:00
超级老白兔
e38b0fde51 更新敏感词管理模块:将状态参数类型从字符串改为数字,优化状态切换逻辑,提升代码可读性和类型安全性。 2025-09-28 18:22:17 +08:00
超级老白兔
340f074f01 调整右侧区域的布局样式:移除flex-direction属性以简化样式定义,提升代码可读性。 2025-09-28 18:11:40 +08:00
超级老白兔
cb0256c820 优化素材管理模块:重构数据获取逻辑,使用useRef存储分页状态,简化fetchMaterials函数,提升代码可读性和性能。 2025-09-28 18:11:00 +08:00
超级老白兔
ed43e67047 添加分页功能到关键词、素材和敏感词管理模块:引入Pagination组件,更新请求参数以支持分页,优化数据获取逻辑,提升用户体验。 2025-09-28 18:08:50 +08:00
wong
a923615190 bug修改 2025-09-28 18:06:18 +08:00
超级老白兔
d8074502e0 重构关键词管理模块:更新关键词和敏感词的请求参数类型为数字,增强类型安全性;优化关键词管理界面,改进状态切换和删除功能,增加确认删除的弹窗,提升用户体验。 2025-09-28 17:41:01 +08:00
超级老白兔
1bd3883b18 更新敏感词管理模块:将操作类型参数类型从字符串改为数字,以提高类型安全性和代码可读性。 2025-09-28 16:37:58 +08:00
超级老白兔
0ae2265c50 更新敏感词管理模块:将敏感词管理组件转换为使用forwardRef,增强与父组件的交互能力;修复敏感词状态更新接口的请求方法;优化删除功能,增加确认删除的弹窗;改进敏感词详情获取逻辑,提升用户体验。 2025-09-28 16:36:17 +08:00
超级老白兔
9fa09bb743 Merge branch 'yongpxu-dev' into develop 2025-09-28 16:04:44 +08:00
超级老白兔
c90628a71f 重构关键词管理模块:将关键词管理组件转换为使用forwardRef,增强与父组件的交互能力;优化关键词列表的获取和处理逻辑,提升用户体验。 2025-09-28 16:04:32 +08:00
wong
7b3df95152 修复微信管理数据展示问题 2025-09-28 16:02:03 +08:00
wong
372847f398 修复群成员及好友数据展示问题 2025-09-28 16:01:31 +08:00
wong
f9dd2c6bd6 允许上传ppt格式的文件 2025-09-28 16:00:51 +08:00
wong
34083441d4 修复内容管理素材无法上传的问题 2025-09-28 16:00:28 +08:00
wong
8796ec5fbc 路由提交 2025-09-28 15:59:56 +08:00
超级老白兔
c6639a8259 增强文件和视频上传功能:添加上传成功和失败的反馈信息,检查响应状态以确保上传结果的准确性;更新素材管理模块,优化删除和编辑功能,增加确认删除的弹窗,提升用户体验。 2025-09-28 15:38:06 +08:00
超级老白兔
3b063ff64c 移除冗余文件并重构内容管理模块:删除技术栈和兼容性说明文档,移除多个模态框组件,整合管理组件为统一结构,优化代码组织,提升可维护性。 2025-09-28 14:52:05 +08:00
wong
ef71d83321 代码同步 2025-09-28 10:17:42 +08:00
超级老白兔
a73481808c 同步 2025-09-27 16:59:30 +08:00
超级老白兔
8ff912a4a7 更新接待設置功能:修改API請求路徑以支持新的流量池設置,新增批量設置功能並優化用戶界面,添加空狀態提示以提升用戶體驗。 2025-09-26 17:33:36 +08:00
超级老白兔
c22ab933c7 更新小程序鏈接:將鏈接中的頁面路徑從「input」更改為「input2」,以反映最新的功能需求。 2025-09-26 17:14:11 +08:00
wong
31354272e7 代码提交 2025-09-26 17:06:14 +08:00
超级老白兔
0b50724b2f 調整Ckbox頁面標題及優化CommonConfig組件:將標題更改為「觸客寶」,並重構標籤頁內容顯示邏輯,新增佔位符以顯示未開發功能,提升用戶體驗。 2025-09-26 17:00:26 +08:00
超级老白兔
43209882e4 新增待辦事項相關API接口及聊天窗口功能:在API中添加待辦事項列表、添加和處理功能,並在聊天窗口中整合好友ID以支持待辦事項管理,提升用戶互動體驗。 2025-09-26 16:41:48 +08:00
超级老白兔
5ea49ef6f1 新增跟進提醒相關API接口及聊天窗口功能:在API中添加跟進提醒列表、添加和處理功能,並在聊天窗口中整合好友ID以支持提醒功能,提升用戶互動體驗。 2025-09-26 16:31:32 +08:00
超级老白兔
7699397bda 新增跟進提醒和待辦事項模態框功能:在聊天窗口中添加相應的狀態管理和事件處理,提升用戶互動體驗。 2025-09-26 16:13:02 +08:00
超级老白兔
31217c6fd7 新增聊天窗口擴展功能:在樣式中添加擴展區域,並在聊天窗口中新增「跟進提醒」和「待辦事項」按鈕,提升用戶互動性。 2025-09-26 15:42:18 +08:00
wong
acabbf30da 自动问候功能提交 2025-09-26 15:37:15 +08:00
超级老白兔
adc5dd1742 優化導航組件:調整按鈕顯示內容,新增用戶信息顯示,並在樣式中增加間距設置,提升用戶界面友好性。 2025-09-26 15:36:52 +08:00
超级老白兔
0b398b372e 簡化ProfileCard組件的屬性,移除不必要的onToggleProfile回調,優化個人資料顯示邏輯。 2025-09-26 15:08:44 +08:00
超级老白兔
1d5d46c9ce 優化個人資料顯示邏輯:移除不必要的onToggleProfile回調,簡化ProfileCard組件的屬性,並調整按鈕顯示內容。 2025-09-26 15:06:39 +08:00
超级老白兔
0beeb15245 修正API請求方法:將getChatMessages函數中的request方法替換為request2,以統一請求處理方式。 2025-09-26 14:57:11 +08:00
超级老白兔
43b63908b5 修正API請求方法:將clearUnreadCount和updateConfig函數中的request方法替換為request2,以統一請求處理方式。 2025-09-26 14:56:53 +08:00
超级老白兔
c40d3fe044 Merge branch 'yongpxu-dev' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into yongpxu-dev 2025-09-26 14:56:45 +08:00
超级老白兔
52c93b80f6 feat(微信功能加載優化): 在微信功能中新增isLoadWeChat狀態管理,優化聯絡人及群列表的加載邏輯,根據狀態決定數據來源,提升性能與用戶體驗。 2025-09-26 14:55:39 +08:00
超级老白兔
30fd27e570 Merge branch 'yongpxu-dev' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into yongpxu-dev 2025-09-26 14:35:44 +08:00
超级老白兔
6d7c5202c2 Merge branch 'yongpxu-dev' into yongxu-dev3 2025-09-26 14:34:56 +08:00
超级老白兔
4e63e533d3 Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-09-26 14:34:49 +08:00
超级老白兔
e0feb6dff1 2025-09-26 14:33:43 +08:00
超级老白兔
8e9313eafa 2025-09-26 14:33:31 +08:00
超级老白兔
fa602f3efa Merge branch 'yongpxu-dev' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into yongpxu-dev 2025-09-26 14:23:55 +08:00
超级老白兔
f96377ff12 Merge branch 'yongpxu-dev' into yongpxu-dev4 2025-09-26 12:46:48 +08:00
超级老白兔
7a01efaa52 feat(自動問候功能開發): 新增自動問候功能界面,包含問候規則創建、編輯及列表展示,並優化樣式以提升用戶體驗。更新了PowerNavigation組件以支持右側內容顯示。 2025-09-26 12:46:15 +08:00
wong
1e1b65684d 触客宝 内容管理提交 2025-09-25 17:52:56 +08:00
超级老白兔
e506334689 FEAT => 本次更新项目为: 2025-09-25 15:50:00 +08:00
超级老白兔
d2ab632f2f FEAT => 本次更新项目为: 2025-09-25 15:47:45 +08:00
超级老白兔
5acdd13966 feat(客戶管理頁面優化): 更新客戶管理頁面,調整聯絡人卡片的最小高度,移除不必要的樣式,並整合聯絡人數據加載邏輯,提升頁面性能與用戶體驗。 2025-09-25 15:39:44 +08:00
超级老白兔
b3bf342320 feat(客戶管理與微信功能增強): 在客戶管理頁面中,整合了從狀態管理獲取的聯絡人數據,並動態計算各分類的聯絡人數量。此外,新增了獲取分組列表的API,優化了聯絡人顯示邏輯,提升了用戶體驗。 2025-09-25 15:00:17 +08:00
超级老白兔
27dfb17e70 FEAT => 本次更新项目为: 2025-09-25 14:32:42 +08:00
超级老白兔
6fa17a8f81 Merge branch 'yongpxu-dev' into yongpxu-dev4 2025-09-25 11:31:48 +08:00
超级老白兔
0b48be15aa Merge branch 'yongpxu-dev' into yongxu-dev3
# Conflicts:
#	Touchkebao/src/pages/pc/ckbox/powerCenter/sop-send/index.module.scss   resolved by yongpxu-dev version
#	Touchkebao/src/pages/pc/ckbox/powerCenter/sop-send/index.tsx   resolved by yongpxu-dev version
2025-09-25 11:31:41 +08:00
超级老白兔
0159a246ac FEAT => 本次更新项目为: 2025-09-25 11:28:17 +08:00
超级老白兔
e00bbf576a feat(功能中心新增溝通記錄與樣式調整): 在功能中心中新增「溝通記錄」功能,並對樣式進行調整,包括功能卡片的佈局和顏色設置,以提升視覺效果和用戶體驗。 2025-09-25 11:19:18 +08:00
超级老白兔
88653c1656 feat(功能中心重構與樣式優化): 將功能卡片結構重組為分類展示,新增熱門與新功能標籤,並優化樣式以提升用戶體驗。同時,新增通用配置頁面路由。 2025-09-25 11:08:03 +08:00
wong
31dad88078 兼容性提交 2025-09-25 10:50:43 +08:00
wong
ebc24432ff 去除好友和群的旧数据 2025-09-24 16:22:24 +08:00
超级老白兔
664d86838d Merge branch 'yongpxu-dev' into develop 2025-09-24 16:12:38 +08:00
超级老白兔
58dfbe3084 同步 2025-09-24 16:12:25 +08:00
wong
5a1c0321a6 去除测试代码 2025-09-24 15:05:50 +08:00
wong
1cc5605291 支付回调提交 2025-09-24 15:03:34 +08:00
超级老白兔
fb0cce836d feat(導航組件整合與樣式優化): 將PowerNavigation組件整合至多個頁面,替代冗餘的標題和描述結構,並在樣式上進行優化,提升整體用戶體驗。 2025-09-24 14:23:38 +08:00
wong
55be0c18e2 支付提交 2025-09-24 14:09:27 +08:00
wong
a9680d2464 消息列表 2025-09-24 14:09:09 +08:00
超级老白兔
7060bcce01 feat(接口整合與NavCommon優化): 將多個API請求從request2轉為request,簡化代碼結構。同時,刪除不再使用的NavCommon組件數據,並優化導航邏輯以提升用戶體驗。 2025-09-24 12:00:36 +08:00
wong
3c89d312c6 临时版本提交 2025-09-24 10:37:39 +08:00
超级老白兔
8e9b2920ed Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-09-24 10:12:39 +08:00
超级老白兔
1b2a6d5acd FEAT => 本次更新项目为: 2025-09-23 19:16:02 +08:00
超级老白兔
c32ff2cf29 feat(導航組件優化): 移除NavCommon組件中的冗餘onMenuClick屬性,並優化菜單圖標點擊邏輯以實現路由切換,提升用戶體驗。 2025-09-23 19:15:31 +08:00
超级老白兔
424fc518ff feat(聊天窗口重構與個人資料卡片優化): 刪除冗餘的ChatWindow組件代碼,重構ProfileCard以支持選項卡功能,並整合快捷語錄及朋友圈模塊,提升用戶界面友好性和功能性。 2025-09-23 19:03:55 +08:00
wong
ac5c445b6f 代码提交 2025-09-23 17:43:36 +08:00
wong
59115fbf69 代码提交 2025-09-23 17:42:23 +08:00
wong
7560389e35 回调地址修改 2025-09-23 16:55:18 +08:00
wong
74b39ac480 代码提交 2025-09-23 16:50:34 +08:00
wong
7ed368a137 支付提交 2025-09-23 16:43:18 +08:00
wong
a8a9cf76df bug修复 2025-09-23 16:21:19 +08:00
超级老白兔
fe82be5f21 fix(個人資料卡片邏輯修正): 將群管理權限判斷邏輯中的wechatId改為nickname,以正確反映群主的識別方式,提升功能準確性。 2025-09-23 16:15:55 +08:00
超级老白兔
65fe93421b feat(好友接待配置與聊天窗口優化): 新增獲取好友接待配置的API,並在聊天窗口中整合AI接管狀態的更新邏輯。同時,優化了NavCommon組件,新增設置圖標,提升用戶界面友好性。移除不再使用的NavCommon樣式文件,簡化代碼結構。 2025-09-23 15:49:24 +08:00
超级老白兔
f0cd287f9a feat(未讀消息清除功能): 將清除未讀消息的API函數重命名並新增第二個清除未讀消息的API,優化代碼結構以提升可讀性和維護性。 2025-09-23 14:49:20 +08:00
超级老白兔
4c2c3db052 feat(聊天会话更新): 优化聊天会话处理逻辑,简化未读消息清除和会话更新的代码结构,提高可读性和维护性。 2025-09-23 14:40:36 +08:00
超级老白兔
3a90b1d163 feat(接口更新): 將多個API請求從request轉為request2,並調整相關邏輯以支持新結構,優化代碼可讀性。同時,更新了聊天記錄的排序邏輯,根據未讀消息數量和時間進行排序,提升用戶體驗。 2025-09-23 14:34:19 +08:00
超级老白兔
0eb6716263 FEAT => 本次更新项目为: 2025-09-23 11:54:10 +08:00
超级老白兔
92242bb8ff feat(未讀消息數據結構優化): 更新多個接口和數據結構,將unreadCount移入config對象中,並調整相關邏輯以支持新結構,提升代碼可讀性和維護性。 2025-09-23 11:51:14 +08:00
超级老白兔
8742560e6c feat(好友接待配置): 新增好友接待配置功能及流量池列表获取接口
在api.ts中新增了设置好友接待配置的API接口,并在ChatWindow组件中实现了配置选择功能,允许用户选择接待类型并保存配置。同时,新增了获取流量池列表的接口,优化了用户体验。
2025-09-23 11:20:35 +08:00
wong
fbfe0ba1fd 流量池字段同步 2025-09-22 18:03:45 +08:00
超级老白兔
7b39e68363 FEAT => 本次更新项目为: 2025-09-22 17:59:47 +08:00
超级老白兔
8f0c6c30dc FEAT => 本次更新项目为: 2025-09-22 17:51:09 +08:00
超级老白兔
1423b28663 FEAT => 本次更新项目为: 2025-09-22 17:50:42 +08:00
超级老白兔
1b15125bee 修正聯絡人導入和流量分配模組中的變數名稱,將所有的「pools」更改為「poolGroups」,以統一命名規範並提升代碼可讀性。新增流量池選擇功能,並更新相關表單數據處理邏輯。 2025-09-22 17:47:25 +08:00
超级老白兔
c6adee4502 更新 index.html 和 manifest.json 中的脚本文件名,替换为新的构建版本 index-ROCKxzay.js,以确保正确加载最新的资源。 2025-09-22 16:40:33 +08:00
超级老白兔
51a7f4f2ee FEAT => 本次更新项目为: 2025-09-22 14:44:36 +08:00
超级老白兔
0c846a86b9 1 2025-09-22 14:18:03 +08:00
超级老白兔
0fe7f86ad6 Merge branch 'yongpxu-dev' into yongpxu-dev2
# Conflicts:
#	Cunkebao/dist/.vite/manifest.json
#	Cunkebao/dist/index.html
2025-09-22 14:17:17 +08:00
超级老白兔
69613450c7 FEAT => 本次更新项目为: 2025-09-22 14:15:58 +08:00
超级老白兔
a9b769e510 FEAT => 本次更新项目为: 2025-09-22 14:15:23 +08:00
超级老白兔
deb0eb85b5 feat(流量池管理): 新增流量池添加功能及筛选条件优化
在流量池管理模块中,新增了添加流量池的API接口,并更新了筛选条件的结构,支持初始值的设置和状态的同步。优化了筛选条件的管理逻辑,提升了用户体验和代码可读性。
2025-09-22 11:51:37 +08:00
超级老白兔
ce1eddad68 FEAT => 本次更新项目为: 2025-09-22 10:49:21 +08:00
超级老白兔
a9c41aa9c3 feat(批量加入分组): 更新BatchAddModal组件以支持确认操作
在BatchAddModal组件中新增了确认按钮,优化了用户体验。更新onConfirm函数以接收选项参数,并调整了组件结构以提升可读性。同时,移除了未使用的Toast提示逻辑,简化了批量加入的处理逻辑。
2025-09-22 10:37:11 +08:00
超级老白兔
b0d5068192 Merge branch 'develop' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into develop 2025-09-22 10:24:24 +08:00
wong
2bd82fc6ef bug修复 2025-09-19 17:50:53 +08:00
超级老白兔
17279390d4 FEAT => 本次更新项目为: 2025-09-19 17:45:09 +08:00
超级老白兔
91a95e20a2 Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-09-19 17:44:34 +08:00
超级老白兔
cb7a37fe9e feat(流量分发表单): 优化流量池选择逻辑
移除未使用的流量池列表和搜索功能,改为使用受控组件管理流量池选择。新增流量池选择的校验逻辑,确保用户至少选择一个流量池。同时,简化了目标设置的逻辑,提升了代码可读性和用户体验。
2025-09-19 17:43:32 +08:00
超级老白兔
4498f3542b feat(引用消息功能): 更新群组消息引用逻辑
在消息记录组件中优化了群组消息的引用逻辑,确保引用内容正确显示用户昵称,并移除了不必要的调试日志,提升了代码整洁性。
2025-09-19 17:33:27 +08:00
超级老白兔
fab5204c56 feat(流量分发表单): 添加流量池选择功能
在流量分发表单中引入流量池选择组件,优化了流量池的管理逻辑。新增状态以存储流量池选项,并实现流量池的搜索和选择功能,提升用户体验。同时,移除未使用的输入同步函数,简化代码结构。
2025-09-19 17:14:40 +08:00
wong
24bb2adc70 Merge branch 'develop' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into develop 2025-09-19 17:00:36 +08:00
wong
75230dd3b8 代码同步 2025-09-19 17:00:09 +08:00
超级老白兔
4440da92bf Merge branch 'yongpxu-dev' into develop 2025-09-19 16:59:38 +08:00
超级老白兔
07c22e06ba 同步下 2025-09-19 16:59:08 +08:00
超级老白兔
ef839f65c1 feat(流量分发表单): 优化表单管理和数据获取逻辑
重构流量分发表单组件,使用Form管理字段并引入useWatch以简化状态管理。移除未使用的状态和函数,优化了获取详情数据的逻辑。同时,更新了API请求方法以符合RESTful标准,确保删除计划时使用DELETE请求。
2025-09-19 16:58:40 +08:00
wong
ce6e752905 算力扣费及列表接口 2025-09-19 16:51:03 +08:00
wong
9c1cfda40b 代办事件及提示事项接口 2025-09-19 16:50:32 +08:00
wong
d87d3ed9e1 AI对话接口 2025-09-19 16:49:25 +08:00
wong
c21387874b 好友新增ai接管字段 2025-09-19 16:48:42 +08:00
wong
5b35786c5c ai群公告接口提交包含扣费功能 2025-09-19 16:47:41 +08:00
wong
3829da428c AI改写群公告接口改写 2025-09-19 16:46:06 +08:00
超级老白兔
d8ef4d522c fix(weChat): 移除调试日志以清理代码
在weChat模块中移除了不必要的调试日志,优化了代码整洁性。
2025-09-19 16:35:16 +08:00
超级老白兔
26b7eb7cd8 feat(消息输入与记录): 增强引用消息功能与状态管理
在消息输入组件中添加引用消息的逻辑,确保引用内容能够正确显示。同时,在消息记录组件中实现引用功能的处理,优化了状态管理,提升了用户体验。
2025-09-19 16:34:56 +08:00
超级老白兔
f69659309d Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-09-19 15:49:34 +08:00
超级老白兔
18f2ebf455 feat(消息转发): 更新转发逻辑并优化状态管理
在消息记录和转发模态框中添加了选中聊天记录的更新逻辑,确保转发功能的正确性。同时,简化了转发模态框的参数传递,移除了不必要的回调,提升了用户体验。
2025-09-19 15:40:47 +08:00
超级老白兔
c625e12075 feat(TransmitModal): 添加分页功能并优化联系人显示
在转发模态框中实现了联系人列表的分页功能,提升了用户体验。同时,优化了联系人信息的显示方式,增加了备注信息的样式支持。
2025-09-19 14:54:52 +08:00
超级老白兔
ed58b824ac fix(TransmitModal): 更新转发确认逻辑并优化联系人显示
修正了转发确认时的参数传递,确保选中的微信好友能够正确传递给onConfirm回调。同时,在联系人显示中添加了备注信息的支持,提升了用户体验。
2025-09-19 14:44:51 +08:00
超级老白兔
6fa89dbdf4 fix(TransmitModal): 修复联系人选择时的事件处理逻辑
在联系人项中添加onChange事件处理,确保选择状态能够正确更新。此更改优化了用户交互体验。
2025-09-19 14:35:04 +08:00
超级老白兔
8dc19a522f refactor(TransmitModal): 简化联系人数据结构并优化状态管理
移除冗余的Contact接口,直接使用原始数据类型
使用组件内部状态替代zustand store管理选中联系人
优化搜索和选择逻辑,减少不必要的转换
2025-09-19 14:18:52 +08:00
超级老白兔
dd18b32a09 fix(TransmitModal): 修复联系人选择和禁用状态的逻辑错误
修复了联系人选择时参数命名不一致的问题
调整了禁用状态的判断逻辑,现在已选择的联系人会被正确禁用
2025-09-19 11:37:22 +08:00
超级老白兔
b916180ccd refactor(TransmitModal): 重构转发模态框组件并集成到消息记录
- 删除旧版转发模态框组件及相关文件
- 实现新版转发模态框组件,支持联系人搜索和多选
- 集成转发功能到消息记录组件
- 更新状态管理以支持转发功能
- 优化样式和响应式设计
2025-09-19 11:30:46 +08:00
超级老白兔
593e6c4670 feat(微信聊天): 添加消息转发功能
实现消息多选转发功能,包括:
1. 新增转发模态框组件,支持联系人搜索和多选
2. 在消息记录组件中添加转发操作逻辑
3. 在消息输入区域添加转发操作栏
4. 在微信状态管理中新增转发相关状态和方法
2025-09-19 10:24:25 +08:00
超级老白兔
8a7a717801 fix(MessageEnter): 修复多转发模块显示条件判断错误 2025-09-18 18:34:10 +08:00
超级老白兔
a53460b4cc feat(微信聊天): 实现消息撤回功能并优化右键菜单
添加消息撤回API接口及状态管理
在右键菜单中根据消息发送时间和归属显示撤回选项
优化消息记录组件与状态管理的交互逻辑
2025-09-18 17:58:33 +08:00
超级老白兔
f0f64dd118 feat(weChat): 添加消息多选转发功能
- 在微信聊天记录组件中添加复选框支持多选消息
- 新增showCheckbox状态和updateShowCheckbox方法控制复选框显示
- 重构右键菜单处理逻辑,统一使用onCommad回调
- 添加EnterModule状态管理不同功能模块
- 调整消息项布局样式以适配复选框
2025-09-18 16:38:18 +08:00
超级老白兔
ac53861a85 refactor(websocket): 优化消息处理器并清理认证失败时的缓存数据
- 移除未使用的WebSocket状态检查代码
- 重新组织导入语句顺序
- 改进CmdNotify处理器,在认证失败时清除所有本地存储和数据库
- 将消息处理器调整为异步函数以支持数据库操作
2025-09-18 15:25:33 +08:00
超级老白兔
85c73a5069 refactor(weChat): 移除WebSocket重复连接检查逻辑
不再检查WebSocket连接状态,直接进行连接操作
2025-09-18 15:13:05 +08:00
超级老白兔
94d40308d2 refactor: 移除调试用的console.log语句
清理代码中用于调试的console.log语句,保持代码整洁
2025-09-18 15:12:37 +08:00
超级老白兔
e9fbebdfad refactor(ckchat): 移除调试用的console.log语句
清理代码中用于调试的console.log输出,保持代码整洁
2025-09-18 15:10:50 +08:00
超级老白兔
f58b95cd95 refactor(ckchat): 优化联系人列表创建逻辑并移除调试日志
- 将重复的kfSelected过滤逻辑提取到公共位置
- 为groupType=2的情况添加未分组联系人查询支持
- 移除不必要的console.log调试输出
2025-09-18 15:06:25 +08:00
超级老白兔
d110c97424 feat(数据库): 扩展数据库查询操作符并优化联系人分组逻辑
- 在 DatabaseService 中添加新的查询操作符(notEqual, aboveOrEqual, belowOrEqual, contains, anyOf, notIn, between)
- 修改 createContractList 方法以支持未分组联系人的查询
- 添加调试日志用于跟踪缓存结果和真实分组
2025-09-18 15:04:06 +08:00
超级老白兔
5b5a05d8c5 fix(weChat): 修复消息记录显示和右键菜单问题
- 修正消息记录中自己消息的显示样式
- 更新右键菜单图标和功能项
- 优化联系人列表加载逻辑
- 移除无用代码和注释
2025-09-18 14:39:37 +08:00
超级老白兔
dc93c7f9f4 feat(MessageRecord): 添加右键菜单功能支持消息操作
实现消息记录的右键菜单功能,包括复制、转发、删除和重发等操作。添加ClickMenu组件处理菜单显示和位置调整,并在MessageRecord中集成右键事件处理逻辑。支持暗色主题适配和边界检测防止菜单溢出屏幕。
2025-09-18 10:59:33 +08:00
超级老白兔
065b516bd7 refactor(MessageRecord): 移除未使用的JSON解析函数
该函数未被组件使用,清理冗余代码以保持代码整洁
2025-09-18 10:17:14 +08:00
超级老白兔
7935936da1 FEAT => 本次更新项目为: 2025-09-18 10:09:14 +08:00
超级老白兔
bedd05c6ae fix(朋友圈): 修复删除评论时条件判断错误的问题
refactor(表情资源): 添加新的表情映射文件并更新表情资源
2025-09-18 10:02:36 +08:00
超级老白兔
a32d0781fc refactor(weChat): 重构朋友圈相关类型定义和组件导入路径
将朋友圈相关的类型定义从分散的文件集中到index.data.ts中
更新组件中类型导入路径以保持一致性
添加评论更新逻辑到朋友圈组件
2025-09-17 17:21:20 +08:00
超级老白兔
15a17b5e1d refactor(weChat): 优化朋友圈数据类型和更新逻辑
- 导出 likeListItem 和 CommentItem 接口供其他模块使用
- 重构 CommentItem 接口字段,增加注释说明
- 简化 updateLikeMoment 方法实现
- 新增 updateComment 方法用于更新评论列表
2025-09-17 17:02:27 +08:00
超级老白兔
527a76c480 feat(朋友圈): 添加评论功能及相关样式和接口
- 新增评论接口类型定义和状态管理
- 实现评论输入框和删除评论功能
- 调整评论区域样式,添加删除按钮和输入框样式
- 修改取消评论接口参数命名
2025-09-17 16:56:08 +08:00
wong
02c94a12d5 AI功能提交 2025-09-17 16:51:11 +08:00
wong
706be3efd1 兼容客服端密码登录 2025-09-17 16:49:11 +08:00
超级老白兔
3477b5d575 feat(朋友圈): 实现朋友圈点赞功能并优化组件结构
重构朋友圈组件结构,将核心逻辑拆分为独立组件。主要变更包括:
1. 新增点赞功能及相关API接口
2. 修改snsId类型从number到string以兼容接口
3. 在store中新增updateLikeMoment方法处理点赞状态
4. 提取FriendCard和MomentList为独立组件
5. 优化代码结构提升可维护性
2025-09-17 15:52:15 +08:00
超级老白兔
680c16c7da feat(朋友圈): 重构朋友圈功能,支持好友朋友圈展示和加载更多
- 删除无用的sendMessages.ts文件
- 修改FriendsCircleItem接口,将snsId类型改为number
- 重构weChat store,支持批量添加朋友圈数据
- 优化朋友圈样式,修复图片浮动问题
- 重构api.ts,简化请求参数处理
- 在msgManage.ts中添加朋友圈数据处理逻辑
- 完全重构FriendsCicle组件,支持好友朋友圈展示和加载更多功能
2025-09-17 14:57:58 +08:00
超级老白兔
c9eb19a064 refactor(朋友圈): 重构朋友圈数据管理为统一结构
移除原有的分类型朋友圈状态和方法,合并为统一的MomentCommon状态
更新相关组件以使用新的状态管理方式
2025-09-17 10:44:12 +08:00
超级老白兔
8b2c24d7a1 feat(wechat): 添加朋友圈功能支持
- 在微信状态管理中新增朋友圈相关状态和方法
- 实现朋友圈数据获取、展示和交互功能
- 添加朋友圈API接口和WebSocket消息处理
- 优化朋友圈UI展示和样式
2025-09-17 10:20:31 +08:00
超级老白兔
18b2735f80 feat(朋友圈): 添加朋友圈功能相关组件和状态管理
- 新增朋友圈数据类型定义和接口
- 在状态管理中添加朋友圈相关状态
- 实现朋友圈图片预览功能
- 优化朋友圈样式和交互体验
2025-09-16 18:42:51 +08:00
超级老白兔
d11ae4365c refactor: 移除流量池详情页的添加标签按钮并更新构建文件
移除流量池详情页中未使用的添加新标签按钮组件,同时更新构建生成的manifest.json和index.html文件中的资源引用
2025-09-16 18:04:39 +08:00
超级老白兔
a251697813 Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-09-16 17:53:14 +08:00
超级老白兔
f9f798417b Merge branch 'yongpxu-dev' into yongxu-dev3 2025-09-16 17:53:05 +08:00
超级老白兔
4dc377c16e Merge branch 'yongpxu-dev' into yongpxu-dev4 2025-09-16 17:52:57 +08:00
超级老白兔
e528d8dc51 图标 2025-09-16 17:51:40 +08:00
超级老白兔
81e0f23075 chore: 更新构建产物和.gitignore文件
- 添加dist/*到.gitignore以避免提交构建产物
- 更新Vite构建生成的资源文件哈希值
2025-09-16 17:46:08 +08:00
超级老白兔
a7124c800e refactor: 移除触客宝相关代码及功能模块
feat: 为朋友圈功能添加点赞和评论交互区域样式

style: 优化朋友圈组件代码结构,增加类型定义
2025-09-16 17:40:02 +08:00
超级老白兔
93f0220a71 Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-09-16 16:55:52 +08:00
超级老白兔
1d94368579 feat(朋友圈组件): 实现朋友圈列表分页加载和样式优化
添加分页加载功能,优化样式结构,使用折叠面板管理不同朋友圈类型
重构组件结构,添加加载状态和空状态显示
2025-09-16 16:31:56 +08:00
超级老白兔
21396f5a1d feat(weChat): 添加朋友圈功能组件及样式
- 新增FriendsCircle组件实现朋友圈展示功能
- 添加朋友圈样式文件包含布局和交互样式
- 修改SidebarMenu组件支持朋友圈选项卡
- 根据kfSelected状态控制朋友圈选项卡显示
2025-09-16 15:17:05 +08:00
超级老白兔
4d69c8e570 refactor(ckbox): 优化消息未读计数逻辑并调整导航栏布局
重构未读消息计数逻辑,使用chatSessions中的unreadCount替代原有的messageCount计算方式
调整导航栏中消息按钮和用户下拉菜单的位置顺序
2025-09-16 14:39:15 +08:00
超级老白兔
16e3ccada8 feat(ui): 优化多个组件的样式和功能
- 调整消息记录宽度和样式
- 更新导航栏算力图标和颜色
- 修改垂直用户列表布局和交互
- 移除侧边栏菜单图标
- 添加调试日志和错误处理
2025-09-16 11:47:36 +08:00
超级老白兔
a723b2801e Merge branch 'yongpxu-dev' into develop 2025-09-16 09:57:34 +08:00
wong
f577dbdd70 代码同步提交 2025-09-16 09:57:06 +08:00
超级老白兔
d84c11a459 fix(微信聊天窗口): 添加文件类型消息的消息类型处理
当文件类型为FileType.FILE时,设置消息类型为49以支持文件消息发送
2025-09-15 18:06:07 +08:00
超级老白兔
5c5059681d refactor(router): 统一登录组件文件名并移除无用路由
- 将login组件文件名从小写改为大写以保持命名一致性
- 移除mine模块中不再使用的ckbox路由
2025-09-15 18:04:18 +08:00
超级老白兔
c88431b564 refactor: 移除Cunkebao项目中未使用的微信聊天相关组件和代码
重构Touchkebao项目中的微信API调用逻辑,优化消息记录处理
2025-09-15 18:03:14 +08:00
超级老白兔
d802b9da1f feat(微信客服): 新增微信好友和群列表接口并优化初始化逻辑
- 添加获取微信好友列表和群列表的API接口
- 移除旧的初始化联系人列表逻辑,改用新接口
- 清理不再使用的会话过滤和排序代码
2025-09-15 15:42:05 +08:00
超级老白兔
c91e54370f refactor(websocket/wechat): 优化客服列表排序并简化初始化逻辑
- 在msgManage.ts中按在线状态排序客服列表
- 在main.ts中简化初始化逻辑,使用getAgentList和getChatroomList替代原有复杂逻辑
- 在api.ts中新增相关API接口并统一使用request2
2025-09-15 15:21:24 +08:00
超级老白兔
27f0532491 feat(sop-send): 实现SOP群发任务管理界面及功能
添加SOP任务管理界面,包括任务列表展示、统计卡片、任务增删改查及状态切换功能
实现响应式布局适配不同屏幕尺寸
2025-09-13 18:04:33 +08:00
超级老白兔
d95e07285c feat(精准群发): 实现三步表单流程及响应式设计
添加客户筛选、消息内容和发送设置三个步骤的表单功能
包含预估客户数计算、消息预览和发送摘要展示
优化样式并添加移动端响应式设计
2025-09-13 18:04:25 +08:00
wong
2b3c41ce36 Merge branch 'develop' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into develop 2025-09-13 17:20:57 +08:00
wong
410a43e6a1 触客宝部分功能提交 2025-09-13 17:20:34 +08:00
超级老白兔
a270b8ce3e chore: 更新构建生成的资源文件哈希值 2025-09-13 17:12:16 +08:00
超级老白兔
62e8039138 fix(微信账号详情): 将限制等级字段从字符串类型改为数字类型
修改微信账号详情页面的限制等级字段类型,从字符串类型改为数字类型以匹配后端数据格式。同时更新相关颜色和文本显示的判断逻辑。
2025-09-13 16:14:02 +08:00
超级老白兔
9f01ad0665 feat(微信账号详情): 添加风险评估标签页展示账号限制记录
新增风险评估标签页,用于展示微信账号的限制记录,包括限制原因、日期和风险等级。当没有限制记录时显示"暂无风险记录"。
2025-09-13 16:03:21 +08:00
超级老白兔
268afb7209 feat(traffic-pool): 重构流量池详情页数据结构和样式
- 更新API接口路径和数据结构以匹配新后端实现
- 重构用户详情类型定义和数据处理逻辑
- 优化页面样式和布局
- 实现基于RMM评分的动态标签显示
- 完善统计数据的展示逻辑
2025-09-13 15:47:57 +08:00
超级老白兔
fb362a56af refactor(mobile): 优化页面交互和移除未使用代码
移除未使用的导入和状态变量
将点击事件移至容器元素提升交互体验
减少好友列表每页显示数量为5条
2025-09-13 15:08:42 +08:00
超级老白兔
e5bf979682 Merge branch 'yongpxu-dev' into yongpxu-dev4 2025-09-13 11:57:00 +08:00
超级老白兔
cb05b5beb3 Merge branch 'yongpxu-dev' into develop 2025-09-13 11:56:18 +08:00
超级老白兔
7cf6f1b9fa Merge branch 'yongpxu-dev' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into yongpxu-dev 2025-09-13 11:55:03 +08:00
超级老白兔
5c42c8ad3c 1 2025-09-13 11:54:54 +08:00
超级老白兔
06c8b43950 Merge branch 'yongpxu-dev' into yongpxu-dev4 2025-09-13 11:54:36 +08:00
超级老白兔
7937fea5c3 Merge branch 'yongpxu-dev2' into yongpxu-dev
# Conflicts:
#	Touchkebao/src/pages/pc/ckbox/components/NavCommon/index.tsx   resolved by yongpxu-dev2 version
2025-09-13 11:53:54 +08:00
超级老白兔
4a48bec42d style(NavCommon): 移除headerRight和userInfo中的gap间距
简化样式代码,保持布局一致性
2025-09-13 11:52:52 +08:00
超级老白兔
575e0f2924 feat(功能中心): 新增功能中心相关页面和路由配置
添加SOP群发、AI模型训练、自动打招呼等9个功能页面的基础结构和样式
配置功能中心相关路由
调整消息通知按钮的Badge组件位置
2025-09-13 11:52:18 +08:00
超级老白兔
7749947138 refactor(powerCenter): 更新功能卡片路径从ckbox到powerCenter
统一功能卡片的路径前缀,将原ckbox路径改为powerCenter以保持路由一致性
2025-09-13 11:46:14 +08:00
超级老白兔
66ddb75c13 refactor(powerCenter): 更新功能卡片路径从ckbox到powerCenter
统一功能卡片的路径命名,将原ckbox路径改为powerCenter以保持路由一致性
2025-09-13 11:46:03 +08:00
超级老白兔
625705eaf0 Merge branch 'yongpxu-dev' into yongpxu-dev4 2025-09-13 11:42:03 +08:00
超级老白兔
287c2637ed style(NavCommon): 移除响应式设计和用户信息间隔样式
移除不再需要的移动端响应式设计代码和用户信息组件中的
2025-09-13 11:41:02 +08:00
超级老白兔
f640733276 FEAT => 本次更新项目为: 2025-09-13 11:37:42 +08:00
wong
cc432ba18f 代码提交同步 2025-09-13 10:45:32 +08:00
超级老白兔
fe03a4e9c1 Merge branch 'yongpxu-dev' into yongpxu-dev4 2025-09-13 10:39:34 +08:00
超级老白兔
4c3c32d5a4 Merge branch 'yongpxu-dev' into yongxu-dev3 2025-09-13 10:39:24 +08:00
超级老白兔
0be87ee2db Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-09-13 10:38:58 +08:00
超级老白兔
7d3b5dbbe9 2025-09-13 10:38:36 +08:00
超级老白兔
2e3ac230ca refactor(login): 移除调试日志并修复响应数据解构
移除登录请求中的console.log调试语句
修复从响应数据中解构kefuData的方式,直接从res获取而非res.kefuData
2025-09-13 10:37:33 +08:00
超级老白兔
55286bdfe5 refactor(登录): 移除用户信息中的设备总数字段
简化登录逻辑,不再需要传递和处理设备总数信息
2025-09-13 10:00:03 +08:00
超级老白兔
8b64c1ab0d fix(登录): 更新登录API路径和验证码参数名称
将登录API路径从'/v1/auth/login'改为'/v1/kefu/login'
将验证码参数verifyCode改为verifySessionId以匹配后端接口
更新表单字段名称verificationCode为verifyCode保持一致性
2025-09-13 09:55:07 +08:00
超级老白兔
02b864d657 style(powerCenter): 移除多余边距并清理样式文件格式
清理了index.module.scss文件中的多余边距和空行,保持代码格式整洁统一
2025-09-12 18:10:04 +08:00
超级老白兔
ca1eefb906 feat(NavCommon): 添加关闭按钮并调整抽屉样式
- 在抽屉头部添加关闭按钮,提升用户体验
- 重构抽屉样式结构,分离头部和内容样式
- 调整图标大小和布局,保持视觉一致性
2025-09-12 18:05:18 +08:00
超级老白兔
f2fb543f93 feat(NavCommon): 重构导航组件并优化样式结构
- 新增图标组件并替换原有emoji图标
- 重构导航菜单数据结构并移除无用代码
- 实现菜单项激活状态高亮效果
- 完善用户退出登录功能
- 优化样式文件结构并添加详细注释
- 更新构建产物文件版本
2025-09-12 17:49:23 +08:00
超级老白兔
d3ee0bde5a feat(NavCommon): 重构导航栏样式并添加消息中心和用户菜单功能
- 重新设计用户信息区域样式,增加间距和悬停效果
- 添加消息中心抽屉组件,包含消息列表和操作按钮
- 实现用户下拉菜单,包含个人资料和退出登录选项
- 统一PC端和移动端的导航栏样式
2025-09-12 16:19:08 +08:00
超级老白兔
1ccdfa3e12 refactor(powerCenter): 将功能卡片和标签数据抽离到单独文件
重构功能中心页面,将功能卡片数据和标签数据抽离到独立的index.data.tsx文件中
使用useState和useMemo实现标签过滤功能
2025-09-12 15:47:27 +08:00
超级老白兔
9816d7ec18 feat(powerCenter): 新增功能中心页面及导航配置
添加功能中心页面,包含9个功能卡片展示和分类筛选
更新路由配置和导航菜单数据,移除无用代码和样式
2025-09-12 15:33:54 +08:00
超级老白兔
a2a0cb8461 FEAT => 本次更新项目为: 2025-09-12 10:30:33 +08:00
超级老白兔
76e5004830 Merge branch 'yongpxu-dev' into develop 2025-09-12 10:30:24 +08:00
超级老白兔
14d9790b5c Merge branch 'develop' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into develop 2025-09-12 10:30:15 +08:00
超级老白兔
2480f79a34 同步 2025-09-12 10:30:09 +08:00
wong
084dd23bc2 通讯录导入记录 2025-09-12 10:25:19 +08:00
wong
b989c19a8a 代码提交 2025-09-12 09:40:13 +08:00
超级老白兔
caa57b3e8b refactor(路由): 统一将路由路径从/ckbox更改为/pc
更新用户登录后跳转路径和导航菜单中的路由路径,从/ckbox/weChat改为/pc/weChat,/ckbox/dashboard改为/pc/dashboard,保持路由命名一致性
2025-09-11 18:02:30 +08:00
超级老白兔
f6b0e7b0d9 feat(router): 添加首页路由并调整PC端路由结构
添加新的首页路由组件,实现设备检测自动跳转功能
重构PC端路由路径,移除冗余子路由配置
2025-09-11 17:51:05 +08:00
超级老白兔
4d6b516662 refactor(db): 移除调试用的console.log语句
清理代码中用于调试的console.log输出,保持生产环境日志的整洁性
2025-09-11 17:35:20 +08:00
超级老白兔
1caf567f10 refactor: 移除调试日志和未使用的样式文件
清理数据库操作和WebSocket连接中的调试日志
删除未使用的index.module.scss文件和相关引用
移除已删除的main.ts文件
2025-09-11 17:27:23 +08:00
超级老白兔
1e34ecffc2 refactor: 移除测试相关代码及组件
fix(websocket): 禁用所有Toast提示以优化用户体验

删除开发环境下的测试路由、页面及组件,清理不再使用的代码。同时修改websocket模块,移除所有Toast提示以避免干扰用户操作。
2025-09-11 17:23:47 +08:00
超级老白兔
dd2f58dc78 refactor(profile): 重构用户设置页面为个人资料页面
将用户设置功能从/mine/setting迁移到/profile路径
删除不再使用的mine模块相关代码
更新路由配置以指向新的个人资料页面
2025-09-11 17:20:15 +08:00
超级老白兔
1977aa50c2 chore: 删除示例存储文件examples.ts 2025-09-11 17:15:47 +08:00
超级老白兔
60173a0efb refactor(router): 移除未使用的页面文件并添加用户设置路由
移除多个未使用的页面组件、样式和API文件,包括流量分发、自动建群、朋友圈同步等功能模块。同时添加新的用户设置路由配置到移动端路由模块中。这些变更旨在清理代码库并支持新的用户设置功能。
2025-09-11 17:11:30 +08:00
超级老白兔
fbfce10afb refactor(router): 合并路由模块并删除冗余配置文件
将auth.tsx和index.tsx中的路由配置合并到common.tsx中,并删除不再使用的config.ts文件
2025-09-11 16:48:35 +08:00
超级老白兔
2c539f4316 feat: 重构登录模块并添加验证码功能
- 将登录组件文件名从login.tsx改为Login.tsx
- 添加验证码图片显示和验证功能
- 修改登录后默认跳转路径为/ckbox/weChat
- 移除第三方登录功能
- 更新项目部署路径为tkb-wechat
- 清理无用路由模块
2025-09-11 16:44:35 +08:00
wong
db9e4eb004 Merge branch 'develop' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into develop 2025-09-11 15:16:50 +08:00
wong
bea5f44565 代码优化 2025-09-11 15:16:04 +08:00
超级老白兔
9b3181576f feat: 初始化项目基础架构与核心功能
添加项目基础文件结构、路由配置、API接口和核心组件
实现登录认证、权限控制、WebSocket通信等基础功能
引入antd-mobile UI组件库和Vite构建工具
配置TypeScript、ESLint、Prettier等开发环境
添加移动端适配方案和全局样式
完成首页、工作台、个人中心等基础页面框架
2025-09-11 15:00:57 +08:00
超级老白兔
648a61d09a fix(contact-import): 移除未使用的ContactImportTask导入
fix(traffic-pool): 添加tags存在性检查避免报错
feat(traffic-pool): 添加请求频率限制错误处理
refactor(traffic-pool): 使用防抖优化搜索功能
2025-09-11 14:50:05 +08:00
超级老白兔
18fbf79fa8 refactor(contact-import): 将task状态类型从ContactImportTask改为any
简化状态类型以兼容更多场景,避免类型限制导致的潜在问题
2025-09-11 14:42:00 +08:00
超级老白兔
ff97490a2b fix: 移除重复加载联系记录的副作用逻辑 2025-09-11 14:41:26 +08:00
超级老白兔
dc60dacb02 refactor(contact-import): 重构通讯录导入任务类型定义和样式
- 将ContactImportTask相关类型统一为Allocation类型
- 优化详情页样式和布局
- 移除冗余样式和代码
2025-09-11 14:36:49 +08:00
超级老白兔
b69ad40013 fix(contact-import): 修复联系人导入表单时间格式和设备组选项问题
- 将时间格式从HH:mm:ss简化为HH:mm
- 从config中获取设备组选项而不是手动构造
- 添加加载状态防止重复提交
2025-09-11 11:56:50 +08:00
超级老白兔
8d386d9bdf fix: 修正设备组相关变量名拼写错误
将多处代码中的变量名`deveiceGroups`和`deveiceGroupsOptions`修正为正确的拼写`deviceGroups`和`deviceGroupsOptions`,保持代码一致性
2025-09-11 11:46:36 +08:00
超级老白兔
32f50d676e feat(contact-import): 新增通讯录导入表单页面并优化列表页样式
refactor(contact-import): 重构API接口类型定义和数据结构

style(contact-import): 调整列表页和表单页的样式布局

fix(contact-import): 修复列表页数据加载和搜索功能的问题
2025-09-11 11:38:03 +08:00
超级老白兔
ecb5a741bf Merge branch 'yongpxu-dev' into develop 2025-09-11 10:11:50 +08:00
wong
19977cc536 Merge branch 'develop' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into develop 2025-09-11 10:10:02 +08:00
wong
51312a3a2a 通讯录导入前端 2025-09-11 10:08:26 +08:00
超级老白兔
919f0dc120 feat(表单): 添加采集内容类型选择功能
在表单页面新增采集内容类型选择功能,支持用户选择文本、图片和视频类型
2025-09-11 10:05:21 +08:00
超级老白兔
23c54af27d refactor(weChat): 移除ChatRecord组件中未使用的onSearch属性并重构搜索逻辑
将ChatRecord组件的搜索逻辑移至store管理,移除未使用的onSearch回调属性
更新构建产物文件路径
2025-09-10 18:21:42 +08:00
超级老白兔
9ed298bf3e feat(weChat): 添加清空当前联系人的功能
在weChat模块中添加clearCurrentContact方法用于清空当前联系人和消息,并在ckchat模块中调用该方法确保删除会话时同步清空联系人
2025-09-10 18:08:09 +08:00
超级老白兔
8507b640b2 fix(ckchat): 将deleteChatSession参数类型从string改为number
修改deleteChatSession方法的参数类型以匹配实际使用场景,同时在相关组件中直接使用store中的方法而非直接导入,保持
2025-09-10 17:55:17 +08:00
超级老白兔
3a66a3af3f Merge branch 'yongpxu-dev' into develop 2025-09-10 17:45:02 +08:00
超级老白兔
95303d2960 fix(MessageRecord): 修复消息记录中时间分隔和消息类型的显示问题
调整消息记录组件的时间分隔显示逻辑,将特定类型的消息内容直接显示为HTML,并过滤掉这些消息不进行常规渲染。同时优化消息时间的样式,添加链接颜色和间距设置。
2025-09-10 17:43:56 +08:00
超级老白兔
ad60e684fc feat(消息列表): 添加搜索功能并优化客服筛选逻辑
添加对消息列表的搜索功能,支持根据昵称和备注进行模糊匹配
优化客服筛选逻辑,将筛选和搜索功能合并处理
在转接和转回操作后增加删除聊天会话和本地数据的逻辑
2025-09-10 17:27:59 +08:00
wong
0f1edf3f9c 通讯录导入功能提交 2025-09-10 16:58:22 +08:00
超级老白兔
51b1918f72 feat(微信聊天): 添加聊天记录搜索和客户转接功能
- 在微信聊天界面新增聊天记录搜索组件,支持按时间和关键词搜索
- 添加客户转接功能组件,支持选择目标客服和添加附言
- 实现一键转回功能
- 添加相关API接口和状态管理
- 配置antd中文语言环境和dayjs本地化
2025-09-10 16:52:28 +08:00
超级老白兔
46d6641259 chore: 更新构建产物中的资源引用路径
更新manifest.json和index.html中引用的JS和CSS文件路径,以匹配最新的构建输出
2025-09-10 14:08:29 +08:00
超级老白兔
984b9af719 Merge branch 'yongpxu-dev' into yongpxu-dev4 2025-09-10 11:54:02 +08:00
超级老白兔
42e74a05f5 Merge branch 'yongpxu-dev' into yongxu-dev3 2025-09-10 11:53:46 +08:00
超级老白兔
4364546bc9 Merge branch 'develop' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into develop 2025-09-10 11:52:40 +08:00
超级老白兔
2bcaadbeb6 fix(音频消息): 优化音频URL检测逻辑避免CORS问题
修改音频URL检测逻辑,对于阿里云OSS等外部资源直接返回true,避免fetch HEAD请求触发CORS问题。同时调整crossOrigin设置策略,仅在需要访问音频数据时设置该属性。移除了冗余的备用播放方案,简化错误处理流程。
2025-09-10 11:52:37 +08:00
wong
4385995742 工作台 - 通讯录导入功能 2025-09-10 11:43:47 +08:00
wong
2c7d90e49d 去除队列冗余代码 2025-09-10 11:41:54 +08:00
超级老白兔
bc34346301 fix(MessageEnter): 修正音频消息内容处理逻辑
修复音频消息发送时内容处理不正确的问题,直接使用filePath而不是从中提取url
2025-09-10 11:37:14 +08:00
超级老白兔
5619d2b00e Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-09-10 11:27:20 +08:00
超级老白兔
56a4711189 fix(微信聊天): 修复音频消息上传处理逻辑
修改音频上传回调参数类型为包含URL和时长的对象
优化音频格式检测逻辑,增加更多MP3格式支持
更新消息处理函数以正确处理音频数据
2025-09-10 11:26:49 +08:00
超级老白兔
4b84d5f013 feat(selection): 为所有选择弹窗添加当前页全选功能
- 在弹窗底部添加全选当前页的复选框
- 修改样式以适配新的全选功能
- 移除不再需要的总计数量显示
2025-09-10 11:23:50 +08:00
超级老白兔
c6304d75cf fix: 移除页码为1时的数据请求限制
统一修改多个选择弹窗组件,当页码变化时不再限制第一页的数据请求,确保数据加载逻辑一致
2025-09-10 11:01:13 +08:00
超级老白兔
8fb8e83632 fix(微信聊天): 修复音频消息上传处理逻辑
修改音频上传回调参数类型为包含URL和时长的对象
优化音频格式检
2025-09-10 10:51:21 +08:00
超级老白兔
390d88e0e9 refactor(ProfileCard): 移除未使用的图标和组件导入
清理未使用的antd图标和组件导入,减少代码冗余
2025-09-10 10:46:30 +08:00
超级老白兔
e08c290a66 refactor: 重构TwoColumnSelection组件并移动至新目录
将TwoColumnSelection组件从FriendSelection目录移动到新的TwoColumnSelection目录
优化组件代码格式和样式文件结构
更新相关引用路径
2025-09-10 10:46:02 +08:00
超级老白兔
f60bf294c1 feat(视频消息): 添加视频消息组件并优化消息记录处理
将视频消息处理逻辑从MessageRecord组件中抽离为独立组件VideoMessage
新增视频消息样式文件VideoMessage.module.scss
优化视频消息的加载状态处理和错误显示
2025-09-10 10:02:43 +08:00
超级老白兔
ae4fcbad67 feat(MessageRecord): 添加小程序消息组件并优化消息处理逻辑
将小程序消息处理逻辑抽离为独立组件SmallProgramMessage,优化代码结构
新增对多种小程序类型的样式支持,包括横向布局和垂直图片布局
增强文件消息处理,支持更多文件类型和图标显示
2025-09-09 19:01:24 +08:00
超级老白兔
543e274737 refactor(AudioMessage): 移除调试日志并优化音频播放逻辑
清理了多余的console日志输出,简化了音频播放的错误处理流程
2025-09-09 18:45:44 +08:00
超级老白兔
98a9aa7701 feat(音频消息): 增强音频消息组件功能并改进错误处理
- 支持JSON格式音频URL解析,可获取时长和文本信息
- 添加音频URL可用性检查机制
- 实现更完善的错误处理和重试逻辑
- 增加音频文本显示和错误提示样式
- 优化音频播放控制逻辑,确保跨域访问兼容性
- 将msgId类型改为string以避免潜在类型问题
2025-09-09 18:29:57 +08:00
超级老白兔
04589154ca Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-09-09 18:18:56 +08:00
超级老白兔
03056186c6 feat(微信聊天): 添加语音消息功能支持
实现语音消息的录制、播放和发送功能,包括:
1. 新增AudioRecorder组件用于录音
2. 添加AudioMessage组件展示语音消息
3. 修改消息输入组件支持语音消息类型
4. 调整样式适配语音消息展示
2025-09-09 18:09:38 +08:00
超级老白兔
514b077da4 refactor(SimpleFileUpload): 移除未使用的导入并优化代码结构
清理未使用的React hooks和antd组件导入,保持代码简洁
2025-09-09 16:53:47 +08:00
超级老白兔
c4866e3865 refactor(Upload/MessageEnter): 重构文件上传组件和消息输入界面
- 简化 SimpleFileUpload 组件接口,移除 disabled 和 className 属性,改为通过 type 参数控制文件类型
- 添加 slot 属性支持自定义上传按钮
- 移除不必要的上传状态管理
- 优化 MessageEnter 界面,移除素材菜单和位置功能
- 添加图片和文件上传按钮
2025-09-09 16:53:11 +08:00
超级老白兔
d0953585a7 fix(Upload/MessageEnter): 移除调试日志并完善文件类型判断逻辑
移除SimpleFileUpload组件中上传成功后的console.log
在MessageEnter组件中添加文件类型判断功能,根据文件扩展名确定消息类型并发送相应消息
2025-09-09 16:40:20 +08:00
超级老白兔
6782f188f5 refactor(Upload): 移除调试日志并优化文件上传逻辑
- 移除SimpleFileUpload组件中的console调试日志
- 重构uploadFile方法,使用axios替代原有request方法
- 添加请求头认证和超时设置
- 改进错误处理,返回更具体的错误信息
2025-09-09 16:33:27 +08:00
超级老白兔
cc58e4420e feat(上传组件): 添加SimpleFileUpload组件并集成到聊天窗口
- 创建新的SimpleFileUpload组件实现文件上传功能
- 在聊天窗口替换原有上传按钮为新的组件
- 为上传API添加调试日志
- 移除聊天窗口重复的音频图标导入
2025-09-09 16:06:59 +08:00
超级老白兔
ef95bcb8bc style(MessageRecord): 调整消息记录图标样式以改善对齐
移除verticalAlign属性并添加lineHeight和float属性,使图标在消息记录中更准确地对齐
2025-09-09 15:31:50 +08:00
超级老白兔
a5064263ba fix(MessageEnter): 修正表情选择后直接发送的问题,改为插入输入框
将表情选择后的处理逻辑从直接发送改为插入到输入框末尾,让用户可以继续编辑消息
2025-09-09 15:28:44 +08:00
超级老白兔
7fcde76981 refactor(MessageEnter): 移除未使用的表情选择器状态及相关代码 2025-09-09 15:24:41 +08:00
超级老白兔
24c99c7b95 feat(MessageRecord): 添加微信表情符号解析功能
新增 parseEmojiText 方法用于解析文本中的表情符号格式[表情名称],并将其替换为对应的表情图片。当表情不存在时保持原文本显示。修改了文本消息的渲染逻辑以支持表情显示。
2025-09-09 15:22:29 +08:00
超级老白兔
575aa833b5 fix(EmojiSelection): 修正表情资源路径添加前导斜杠 2025-09-09 15:16:57 +08:00
超级老白兔
c9f9530d09 refactor(EmojiPicker): 优化表情选择器样式并移除未使用的代码
- 将默认触发器从 button 改为 span 以简化结构
- 调整触发器样式使其更紧凑
- 移除未使用的 getAllEmojis 导入
- 删除不再使用的 EmojiPickerDemo 组件
2025-09-09 15:07:23 +08:00
超级老白兔
e76e5629bd refactor(MessageRecord): 重构小程序消息解析逻辑
移除冗余的小程序消息解析代码,统一使用 parseWeappMsgStr 方法处理
简化缩略图获取逻辑,直接使用 parsedData.previewImage
2025-09-09 14:59:54 +08:00
超级老白兔
7853a3ff97 fix(utils): 修复解析小程序消息时URL清理逻辑
处理weappiconurl时,统一替换特殊字符和CDATA标记,确保thumburl正确生成
2025-09-09 14:45:16 +08:00
超级老白兔
983dbf0009 feat(聊天窗口): 添加表情选择器组件并集成到消息输入
refactor(聊天窗口): 重构聊天窗口组件结构,提取消息记录为独立组件

feat(消息解析): 添加微信小程序消息解析功能

style(表情选择器): 添加表情选择器样式文件

chore: 添加xmldom依赖用于XML解析
2025-09-09 14:44:02 +08:00
超级老白兔
f724e91421 feat(表情包): 添加微信表情资源及类型定义模块
新增微信表情图片资源文件及类型安全的TypeScript模块,包含表情分类、路径获取和搜索等功能
2025-09-09 11:23:23 +08:00
超级老白兔
f0bf8f737f build: 更新构建生成的资源文件哈希值
更新了Vite构建生成的JavaScript和CSS文件的哈希值,确保浏览器能正确加载最新版本的文件
2025-09-09 10:46:03 +08:00
超级老白兔
9a25812b81 refactor(微信聊天窗口): 移除无用代码并重构资料显示功能
- 删除注释掉的聊天工具栏代码
- 移除未使用的导入和聊天菜单组件
- 将资料显示控制逻辑移到组件内部
- 简化头部按钮,仅保留资料显示切换功能
2025-09-09 10:43:08 +08:00
超级老白兔
79ccb20135 fix(样式): 调整微信页面布局高度以避免溢出
将高度从100vh改为calc(100vh - 64px),防止内容被顶部导航栏遮挡
2025-09-09 10:00:10 +08:00
超级老白兔
fbc40bb4be Merge branch 'yongpxu-dev' into develop 2025-09-09 09:51:28 +08:00
超级老白兔
cb44735eec style: 统一图表卡片中的引号为单引号 2025-09-09 09:51:07 +08:00
wong
c5bc67771f 代码同步提交 2025-09-09 09:49:45 +08:00
超级老白兔
eb073b04ac refactor(ckbox): 重构导航组件并优化路由配置
将导航菜单数据抽离到独立文件,简化路由配置
移除未使用的NavCommon组件属性,优化微信管理页面结构
2025-09-08 18:52:06 +08:00
超级老白兔
2b48dd8722 refactor(PlaceholderPage): 移除未使用的showFooter属性和MeauMobile组件
style(dashboard): 统一图表组件中的引号为双引号
2025-09-08 18:21:14 +08:00
超级老白兔
9fa647e40e feat(ckbox): 重构监控页面为仪表盘并添加图表功能
将监控页面重命名为仪表盘,保留原有功能组件的同时新增多个图表展示模块
使用echarts实现消息趋势、功能分布等数据可视化
优化布局和样式,提升用户体验
2025-09-08 18:19:03 +08:00
超级老白兔
fe5e7b1633 refactor(wechat): 重构微信聊天界面组件结构,将相关文件移动到weChat子目录下
重构微信聊天界面的组件结构,将原有分散的组件文件整理到weChat子目录中,包括消息列表、联系人列表、垂直用户列表等组件。同时优化了样式文件和类型定义,保持代码结构清晰。修改路由配置以支持新的目录结构。
2025-09-08 18:07:12 +08:00
超级老白兔
6c6d0f328e chore: 删除触客宝模块测试页面 2025-09-08 17:54:41 +08:00
超级老白兔
9a8af03932 Merge branch 'yongpxu-dev' into develop 2025-09-08 17:26:11 +08:00
超级老白兔
6c8becb201 refactor(workspace): 将朋友圈同步任务详情页移动到列表目录下
重构朋友圈同步任务详情页的文件位置,从独立目录移动到列表目录下,保持相关功能文件的组织一致性
2025-09-08 17:25:38 +08:00
wong
cc180dcb65 群功能处理 2025-09-08 17:24:44 +08:00
超级老白兔
d98b056b57 feat(记录页面): 重构朋友圈同步记录页面功能
- 修改API端点从点赞记录变更为发表记录
- 更新日期格式化函数处理时间戳
- 移除记录卡片中不必要的操作者信息
- 调整样式移除最大宽度限制
- 更新页面标题和空状态提示文本
2025-09-08 17:05:30 +08:00
超级老白兔
527402425f feat(朋友圈同步): 新增朋友圈同步记录页面及功能
重构朋友圈同步模块结构,将原MomentsSync组件拆分为list和record两个子模块
添加朋友圈同步记录页面,包含搜索、分页和记录展示功能
优化样式文件组织,删除重复代码
新增api接口和数据模型定义
2025-09-08 16:41:16 +08:00
超级老白兔
dba6ae164c feat(MessageRecord): 添加视频消息预览和下载功能
新增视频消息的预览图显示、加载动画和下载按钮功能。优化视频消息的交互体验,包括点击预览图触发下载请求,显示加载状态,以及视频播放时的下载选项。同时添加了相关样式和动画效果。
2025-09-08 15:53:37 +08:00
超级老白兔
97cb2b072b style(MessageRecord): 统一消息气泡的圆角样式
将消息气泡的圆角从不对称样式(4px 18px 18px 18px)改为统一的10px圆角,提升视觉一致性
2025-09-08 15:27:57 +08:00
超级老白兔
f13d623d14 style(MessageRecord): 调整消息记录样式,优化图片边框和悬停效果
移除卡片悬停动画效果,调整图片尺寸并添加边框
2025-09-08 15:22:09 +08:00
超级老白兔
4e8c728457 feat(消息记录): 增加小程序类型2和文章链接消息样式及处理逻辑
添加对小程序类型2(垂直布局)的支持,包括样式和渲染逻辑
新增文章类型消息的样式和解析处理
增加链接类型消息的样式和解析处理
优化现有小程序消息的样式和布局
2025-09-08 15:19:29 +08:00
wong
e960c0863f 代码提交 2025-09-08 15:03:39 +08:00
超级老白兔
79c1a539bb style(MessageRecord): 优化小程序卡片样式和交互效果
- 调整卡片尺寸、间距和背景渐变效果
- 添加悬停动画和阴影效果
- 优化移动端适配样式
- 移除冗余的SVG图标代码
2025-09-08 14:21:52 +08:00
超级老白兔
bd6f1064fb feat(MessageRecord): 优化消息渲染逻辑并增强样式
重构消息内容解析逻辑,增加对多种消息类型的支持,包括文本、图片、视频、表情包、小程序和文件消息。改进错误处理和兼容性,同时优化UI样式和移动端适配。

- 新增消息类型检测工具函数
- 增强JSON解析和错误处理
- 优化小程序消息的XML解析和显示
- 改进文件消息的图标和文件名显示
- 添加移动端样式适配
- 统一错误消息渲染逻辑
2025-09-08 12:13:38 +08:00
超级老白兔
57ceae97a1 feat(MessageRecord): 新增消息类型样式并优化消息解析逻辑
- 新增图片、小程序和文件消息的样式组件
- 根据msgType字段重构消息解析逻辑,支持多种消息类型
- 添加错误处理和兼容旧版本消息格式
2025-09-08 11:08:00 +08:00
超级老白兔
4fa7834510 Merge branch 'yongpxu-dev' into develop 2025-09-08 10:44:10 +08:00
超级老白兔
8ac5413765 feat(ckchat): 添加根据客服筛选获取合同列表的方法 2025-09-08 10:44:00 +08:00
超级老白兔
524eef9a54 Merge branch 'develop' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into develop 2025-09-08 10:41:44 +08:00
超级老白兔
a4d19c8fc9 refactor(ckchat): 将getSomeContractList改为同步方法并添加类型注释
修改异步方法为同步方法,提高性能并添加类型注释以增强代码可读性
2025-09-08 10:40:42 +08:00
wong
d62701fa85 代码同步 2025-09-08 10:40:18 +08:00
超级老白兔
59da085676 feat(ckchat): 添加根据客服筛选合同列表的功能 2025-09-08 10:39:54 +08:00
超级老白兔
1305bfac59 fix(weChat): 移除消息加载状态的延迟设置
移除setTimeout延迟设置messagesLoading为false的逻辑,改为在try和finally块中直接设置,避免不必要的延迟
2025-09-08 10:32:00 +08:00
超级老白兔
b84a3255c6 fix(weChat): 修复消息加载顺序和加载状态显示问题
调整消息数组拼接顺序,确保新消息正确追加
添加加载状态图标并优化加载更多消息的逻辑
设置消息加载状态延迟更新以避免闪烁
2025-09-08 10:30:22 +08:00
超级老白兔
8682cdab51 Merge branch 'yongpxu-dev' into develop 2025-09-08 09:45:50 +08:00
超级老白兔
2033faff08 feat(weChat): 优化聊天消息加载逻辑并移除无用按钮
添加 isLoadingData 状态控制滚动行为
修改 loadChatMessages 方法支持分页加载
移除聊天窗口的语音和视频通话按钮
更新构建产物文件引用
2025-09-08 09:45:10 +08:00
wong
7f5daa0f85 群功能提交 2025-09-08 09:44:26 +08:00
超级老白兔
b2db8e8f5a Merge branch 'yongpxu-dev' into develop 2025-09-05 17:11:55 +08:00
wong
1c99eee96b 代码提交 2025-09-05 17:10:50 +08:00
wong
689e6d18df 群弹窗 2025-09-05 17:08:26 +08:00
超级老白兔
c4ba459b20 refactor(ckbox): 移除全局profile状态并移至ChatWindow组件
将profile显示状态从父组件移至ChatWindow组件内部管理,简化组件间通信
2025-09-05 17:04:34 +08:00
超级老白兔
ae1ef52113 FEAT => 本次更新项目为: 2025-09-05 16:57:30 +08:00
超级老白兔
03145afa4f feat(ckbox): 添加通用导航组件并优化聊天窗口布局
- 新增NavCommon组件作为通用导航栏
- 优化ChatWindow的flex布局和滚动行为
- 替换原Header为新的导航组件
- 更新构建产物文件
2025-09-05 16:54:18 +08:00
超级老白兔
b03b457e7e style(ckbox): 调整垂直用户列表样式和布局
- 修改侧边栏边框颜色为浅色
- 增加侧边栏宽度和头像尺寸
- 更新用户列表背景色和文字颜色
- 优化选中状态的视觉表现
2025-09-05 16:11:22 +08:00
超级老白兔
f31941425d refactor(聊天窗口): 优化消息列表和记录组件
移除未使用的导入和代码,添加加载更多提示
重构消息列表组件以支持客服筛选功能
2025-09-05 15:50:21 +08:00
超级老白兔
18ec2752ba 同步 2025-09-05 15:36:39 +08:00
超级老白兔
bbf0bee19b Merge branch 'yongpxu-dev' into develop 2025-09-05 15:25:13 +08:00
wong
e3bf844420 群成员功能优化 2025-09-05 15:24:09 +08:00
超级老白兔
afa58712cf fix(MessageList): 修复从store获取chatSessions的方式
使用getChatSessions方法替代直接访问chatSessions状态,确保获取的是最新数据
2025-09-05 15:22:26 +08:00
超级老白兔
f76029c2d9 fix: 添加群组列表到微信账号ID去重逻辑中 2025-09-05 15:14:36 +08:00
超级老白兔
25da77a3b1 refactor(ChatWindow): 使用Antd Divider替换自定义时间分隔线样式
移除MessageRecord.module.scss中自定义的时间分隔线样式,改为使用Antd的Divider组件
2025-09-05 15:08:52 +08:00
超级老白兔
70256b252c fix(chat): 修复新会话未读计数初始化问题
移除ckchat.ts中自动设置unreadCount为0的逻辑,改为在weChat.ts中添加会话时显式设置unreadCount为1,确保新消息有正确未读标记
2025-09-05 14:55:24 +08:00
超级老白兔
e7c109eab1 refactor(wechat): 优化消息接收处理和数据库结构
- 移除未使用的kfUserService导入
- 为weChatGroup和ContractData接口添加serverId字段
- 重构receivedMsg方法,根据消息类型从数据库获取会话信息
- 简化数据库表结构,移除冗余的WithServerId接口
2025-09-05 14:53:09 +08:00
超级老白兔
66241f32af feat(微信): 添加wechatChatroomId字段并优化消息处理逻辑
- 在weChatGroup接口中添加wechatChatroomId字段
- 移除调试用的console.log语句
- 重构消息处理逻辑,简化条件判断并新增会话初始化处理
2025-09-05 11:47:40 +08:00
超级老白兔
2e7f890218 Merge branch 'yongpxu-dev' into develop 2025-09-05 11:06:25 +08:00
超级老白兔
d31edeef65 refactor(weChat): 重构消息处理逻辑并优化组件结构
将消息滚动逻辑移至MessageRecord组件
统一处理群组和好友消息的ID判断
移除ChatWindow中不必要的props传递
2025-09-05 11:05:49 +08:00
wong
55ff880993 代码优化 2025-09-05 10:20:05 +08:00
超级老白兔
7c9ae9e339 refactor(ChatWindow): 提取消息记录组件并优化样式
将消息记录相关逻辑从ChatWindow组件中提取为独立的MessageRecord组件
新增MessageRecord.module.scss文件定义消息样式
简化ChatWindow组件逻辑,提高代码可维护性
2025-09-05 10:03:09 +08:00
超级老白兔
f1a2ee6170 refactor: 删除未使用的Person组件文件 2025-09-04 18:24:31 +08:00
超级老白兔
25a77db3c6 refactor(mine/content): 移除Collapse组件的默认激活状态
构建文件因代码更改而更新
2025-09-04 18:18:07 +08:00
超级老白兔
914538d2ed Merge branch 'yongpxu-dev' into develop 2025-09-04 18:14:01 +08:00
超级老白兔
9c9b982a56 refactor: 重构内容库相关页面和组件
删除不再使用的内容库相关页面、组件和API文件
重命名表单字段以更清晰地表达其用途
优化代码结构和组织方式
2025-09-04 18:13:39 +08:00
超级老白兔
0d18623473 feat(群聊): 实现群聊消息展示及成员信息获取功能
- 在WeChatState中新增currentGroupMembers字段存储群成员信息
- 添加getGroupMembers API接口获取群成员数据
- 重构消息类型定义,统一使用ChatRecord接口
- 修改消息处理逻辑,支持群聊消息展示
- 调整聊天窗口样式,区分群聊和私聊消息显示
- 实现群成员头像和昵称展示功能
2025-09-04 18:07:42 +08:00
wong
876b4e4a5d 代码提交 2025-09-04 17:34:49 +08:00
超级老白兔
a6134bb455 Merge branch 'yongpxu-dev' into develop
# Conflicts:
#	Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/index.tsx   resolved by develop version
2025-09-04 16:33:40 +08:00
超级老白兔
711159aa2b FEAT => 本次更新项目为: 2025-09-04 16:33:03 +08:00
wong
451d737d90 群成员功能 2025-09-04 16:31:48 +08:00
超级老白兔
12d8c39ccf fix(weChat): 修正群聊ID赋值错误并优化初始化流程
- 将wechatChatroomId从contact.chatroomId改为contact.id以修复赋值错误
- 调整chatInitAPIdata中的初始化顺序,先获取群列表再处理唯一ID
- 修改getUniqueWechatAccountIds函数参数以支持群组数据
2025-09-04 15:00:29 +08:00
超级老白兔
54464efdcd fix(ChatWindow): 增加对消息对象的空值检查以防止运行时错误
- 在hasVideoStateChange函数中优先检查消息对象和内容是否存在
- 在groupMessagesByTime函数中过滤掉null和undefined的消息
- 在renderMessage函数中添加null检查并为缺失id的消息生成临时key
2025-09-04 14:51:23 +08:00
超级老白兔
923f0e99b9 fix(ChatWindow): 处理消息内容为null或undefined的情况
添加对消息内容为null或undefined的检查,防止解析时出错
2025-09-04 11:58:36 +08:00
超级老白兔
2d78c2a37a refactor(ChatWindow): 移除调试日志并优化头像显示逻辑
删除无用的console.log调试信息,避免生产环境输出敏感数据
头像显示逻辑改为优先使用contract.avatar,不存在时使用contract.chatroomAvatar作为备选
2025-09-04 11:25:23 +08:00
超级老白兔
37da3e8350 refactor(ProfileCard): 优化图标导入并删除未使用的图标
移除未使用的图标导入并重新组织剩余图标的导入顺序,提高代码整洁度
2025-09-04 11:19:20 +08:00
超级老白兔
890e3c338b fix(ProfileCard): 修复群聊信息编辑时JSON字符串格式问题
修复群名称和群公告编辑时JSON字符串格式错误,将单引号替换为双引号以确保正确解析
2025-09-04 11:07:44 +08:00
超级老白兔
8d9d069267 Merge branch 'yongpxu-dev' into develop
# Conflicts:
#	Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/index.tsx   resolved by develop version
2025-09-04 10:52:55 +08:00
wong
3c68a603af 代码提交 2025-09-04 10:49:22 +08:00
超级老白兔
1f0ef3c64d refactor(chat): 重命名Person组件为ProfileCard并优化代码
修复微信聊天消息可能为undefined的问题
添加调试日志检查消息状态
确保wechatTime字段安全访问
2025-09-04 10:47:57 +08:00
超级老白兔
df0bbddbd5 feat(weChat): 添加联系人存在检查逻辑并优化消息处理
在setCurrentContact方法中添加isExist参数,用于区分新增联系人和已存在联系人的处理逻辑
优化消息处理流程,确保未读消息计数正确更新
2025-09-03 18:33:05 +08:00
超级老白兔
7bd7bd3564 fix(weChat/ckchat): 修复切换联系人时未重置未读计数的问题
在切换联系人时,确保会话的未读计数被正确重置为0。将重置逻辑统一移至addChatSession方法中处理,避免遗漏。
2025-09-03 18:20:24 +08:00
超级老白兔
30cafc5619 refactor(weChat/ckchat): 优化会话更新逻辑并移除冗余代码
- 修复未读消息计数时可能出现的类型问题
- 直接使用chatSessions状态而非getChatSessions方法
- 简化会话更新逻辑,移除不必要的缓存处理
- 使用更简洁的路径引用deepCopy工具
2025-09-03 18:04:39 +08:00
超级老白兔
7e5617bd80 refactor(ckbox): 移除SidebarMenu的currentContract属性并优化状态管理
- 移除不再使用的currentContract属性和相关props传递
- 将chatSessions状态管理移至MessageList组件内部
- 添加CmdFriendInfoChanged消息处理器
- 添加调试日志用于会话更新
2025-09-03 17:55:25 +08:00
超级老白兔
533c16be3f feat(weChat): 添加receivedMsg方法处理接收消息逻辑
新增receivedMsg方法用于处理微信消息接收逻辑,区分当前会话消息和未读消息更新
优化updateChatSession实现,使用深拷贝避免直接修改状态
调整ckchat.data.ts中方法顺序,保持代码整洁
2025-09-03 17:47:43 +08:00
超级老白兔
ff96312d53 feat(websocket): 添加消息处理逻辑并清理无用代码
在websocket消息处理器中添加addMessage调用以处理新消息
移除ChatWindow组件中无用的样式和注释代码
2025-09-03 17:18:19 +08:00
超级老白兔
0bddc4dc5b fix: 修复消息发送后输入框未清空及自动点赞状态切换问题
修复消息发送后输入框未清空的问题,优化自动点赞任务状态切换逻辑
更新消息处理逻辑,修正构建文件引用路径
2025-09-03 17:05:53 +08:00
超级老白兔
9cdfe552e0 fix: 移除currentContract持久化并清理调试日志和WebSocket状态
移除weChat模块中currentContract的持久化配置,避免登录和刷新时数据残留
清理ChatWindow组件中的调试日志
在登录时清空WebSocket连接状态
2025-09-03 16:49:45 +08:00
超级老白兔
c27642bc12 feat(websocket): 添加清空连接状态方法用于退出登录
新增clearConnectionState方法,用于在用户退出登录时清空WebSocket连接状态。该方法会关闭现有连接、停止所有定时器并重置所有相关状态变量,确保用户登出后不会保留之前的连接信息。
2025-09-03 16:36:10 +08:00
超级老白兔
a3ba45626d feat(微信/websocket): 新增消息处理逻辑并优化websocket连接
- 在WeChatState接口中添加addMessage方法用于接收新消息
- 在websocket消息处理器中调用addMessage存储接收到的消息
- 优化websocket连接逻辑,断开时自动尝试重连
- 改进聊天窗口视频状态检测逻辑,减少不必要的滚动
2025-09-03 16:33:52 +08:00
超级老白兔
ef853d2736 feat(weChat): 重构视频消息处理逻辑并优化加载状态管理
- 移除旧的待处理视频请求队列及相关方法
- 新增setVideoLoading和setVideoUrl方法简化视频状态管理
- 优化ChatWindow组件中的视频加载状态检测和滚动行为
- 添加CmdDownloadVideoResult消息处理器自动更新视频URL
2025-09-03 16:06:12 +08:00
超级老白兔
8b1387a8ed feat(weChat): 添加视频请求处理和WebSocket监听功能
- 在weChat store中新增pendingVideoRequests状态和相关操作方法
- 实现WebSocket监听处理视频下载响应
- 重构ChatWindow组件,将视频处理逻辑移至store
- 优化消息分组和渲染逻辑
2025-09-03 15:08:29 +08:00
超级老白兔
647e2a5f7e refactor(weChat): 重构微信聊天模块状态管理
将微信聊天相关状态从组件中提取到独立的zustand store
移除冗余的props传递,使用store管理当前联系人和消息
优化消息加载逻辑,统一处理消息状态
2025-09-03 14:52:32 +08:00
wong
d3ae45a360 群推消息推送优化 2025-09-03 14:34:26 +08:00
超级老白兔
48880bed0d feat(wechat): 实现微信聊天状态管理及消息处理功能
添加微信聊天状态管理store,包含联系人管理、消息发送/接收、未读消息计数等功能
修复聊天窗口参数传递问题,统一使用contract.id作为标识
调整消息加载数量从10条减少到5条
2025-09-03 11:25:10 +08:00
超级老白兔
c6da2062f2 FEAT => 本次更新项目为: 2025-09-03 10:45:19 +08:00
超级老白兔
414660a0a5 refactor(ChatWindow): 移除调试用的console.log语句
清理无用代码,避免生产环境输出不必要的信息
2025-09-03 10:01:26 +08:00
超级老白兔
2995755cda refactor(websocket): 将硬编码的5000毫秒改为5 * 1000以提高可读性 2025-09-03 09:54:40 +08:00
超级老白兔
0d6ac88dea refactor(websocket): 将客服状态查询定时器逻辑移至websocket store
将原本在组件中的客服状态查询定时器逻辑重构到websocket store中,统一管理定时器的启动和停止。同时在连接成功时自动启动定时器,断开连接时自动停止,避免内存泄漏。
2025-09-03 09:52:58 +08:00
超级老白兔
eb63dc1d8d refactor(websocket): 移除消息处理器中的空行 2025-09-03 09:35:09 +08:00
超级老白兔
22560a5ce2 fix(SidebarMenu): 修改搜索框占位文本为"搜索客户..."
原占位文本"搜索联系人、群组"不符合当前业务场景,更新为更准确的"搜索客户..."
2025-09-02 17:58:19 +08:00
超级老白兔
4c5e049c16 feat(ckbox): 添加更新微信好友配置的API和调用
在聊天会话初始化时调用updateConfig API更新好友配置,标记为可聊天状态
2025-09-02 17:51:37 +08:00
超级老白兔
92e9b69348 feat(WechatFriends): 添加搜索功能并优化联系人列表展示
- 新增搜索联系人及群组功能,支持本地数据库查询
- 添加无搜索结果时的提示样式
- 优化联系人列表渲染逻辑,区分搜索模式和分组模式
- 统一联系人项展示样式,支持群组标识
2025-09-02 17:40:11 +08:00
超级老白兔
b08086e3f5 feat 2025-09-02 17:36:53 +08:00
超级老白兔
70877c8c96 feat(ckchat): 添加搜索关键词状态及相关方法
在CkChatState接口中添加searchKeyword字段用于存储搜索关键词,并新增setSearchKeyword和clearSearchKeyword方法用于管理搜索状态
2025-09-02 16:32:25 +08:00
超级老白兔
e35403eaf4 fix(websocket): 处理被踢出通知时跳转到登录页面
当收到"Kicked out"通知时,直接重定向到登录页面以强制用户重新登录
2025-09-02 15:52:44 +08:00
超级老白兔
8d8068f5d6 fix: 将聊天记录请求数量从100减少到10以降低负载 2025-09-02 15:39:27 +08:00
超级老白兔
fd82dfc5e0 fix(ChatWindow): 修复聊天窗口消息请求参数错误
调整了消息请求的参数逻辑,将时间戳改为固定值并修正了群聊和私聊的参数判断条件
2025-09-02 15:27:55 +08:00
超级老白兔
d5e609aa37 feat(聊天窗口): 重构消息输入组件并添加微信风格样式
将消息输入功能从ChatWindow组件中拆分为独立的MessageEnter组件
添加微信风格的样式和交互效果
新增消息处理逻辑和素材选择功能
重命名getMessages为getChatMessages以保持命名一致性
2025-09-02 15:21:54 +08:00
超级老白兔
b3f6d4f6e7 fix(websocket): 移除重复的seq字段并统一在命令中添加
在websocket命令构造中统一添加seq字段,避免在调用处重复设置。移除ckbox页面中重复的seq设置,保持代码一致性。
2025-09-02 14:26:44 +08:00
超级老白兔
4fd8c1f5bf docs(ckbox): 更新注释说明为查询客服用户激活状态 2025-09-02 14:23:36 +08:00
超级老白兔
45c496313d refactor(Skeleton): 重构骨架屏组件结构和样式
- 将原有单一骨架屏拆分为头部、侧边栏、聊天区域和右侧面板多个模块
- 优化骨架屏的样式细节,使其更接近真实UI
- 添加加载状态提示和交互效果
2025-09-02 14:20:03 +08:00
超级老白兔
97bb1c1fa9 refactor(ckbox): 调整样式和清理无用代码
- 删除未使用的MessageEnter组件
- 修复样式文件中缩进不一致问题
- 移除ChatWindow中未使用的store引用
- 调整消息输入框的行数设置
2025-09-02 12:00:31 +08:00
超级老白兔
316a237297 fix(ckchat): 修复聊天窗口输入框事件处理和类型定义
修复输入框的onKeyPress事件为onKeyDown以更好处理键盘事件
更新Person组件props类型定义以支持weChatGroup类型
将getkfUserList改为异步函数以备后续实现
2025-09-02 11:49:26 +08:00
超级老白兔
efa34a1ac6 Merge branch 'develop' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into develop 2025-09-02 11:28:46 +08:00
wong
c146796987 京东联盟 转链 2025-09-02 11:24:23 +08:00
超级老白兔
a67ca442a0 FEAT => 本次更新项目为: 2025-09-02 11:23:18 +08:00
超级老白兔
e7b795f744 refactor(websocket): 优化websocket消息处理及状态检查逻辑
移除调试日志输出并优化微信账号状态检查逻辑
添加深拷贝工具函数并应用于客服列表处理
将状态检查改为间隔10秒的轮询方式
2025-09-02 10:18:25 +08:00
超级老白兔
20658c3ca5 refactor(websocket): 重构websocket模块并添加消息管理器
- 将websocket模块拆分为多个文件,提高代码可维护性
- 新增msgManage.ts处理不同类型的websocket消息
- 优化消息处理逻辑,减少重复代码
- 更新相关文件引用路径
2025-09-02 09:52:38 +08:00
超级老白兔
cfa8182789 refactor(websocket): 移除获取微信账号状态的冗余命令
移除在WebSocket连接成功后发送的获取微信账号状态的命令,该命令在当前场景下不再需要
2025-09-01 17:36:03 +08:00
超级老白兔
a867f25481 fix: 修复FilterModal中用户状态选择类型转换问题
将用户状态选择的值从直接赋值改为使用Number()进行类型转换,确保类型一致性
2025-09-01 17:34:40 +08:00
超级老白兔
9906f14446 FEAT => 本次更新项目为: 2025-09-01 17:33:52 +08:00
超级老白兔
cca999a18d FEAT => 本次更新项目为: 2025-09-01 17:33:47 +08:00
超级老白兔
3198959cb7 feat(群推任务): 添加获取群推任务详情的API接口
feat(客服聊天): 实现搜索功能并优化联系人筛选逻辑

refactor(WebSocket): 重构连接逻辑,增加状态检查和调试信息

fix(WebSocket): 修复页面刷新后重连逻辑,避免重复连接
2025-09-01 17:28:33 +08:00
超级老白兔
832be374b5 refactor(ckchat): 重构联系人列表获取逻辑并添加缓存机制
- 将联系人列表获取逻辑从直接调用改为使用useEffect异步获取
- 为getNewContractList添加缓存机制避免重复计算
- 在相关状态变更时自动清除缓存
- 新增asyncNewContractList方法用于设置联系人列表
2025-09-01 14:58:15 +08:00
超级老白兔
aec0bad35f feat(ckchat): 添加联系人列表缓存功能并重构状态管理
- 新增getContractList方法实现联系人列表缓存
- 重构asyncKfSelected方法以触发多个缓存更新
- 移除asyncNewContractList方法,改为使用createContractList API
- 修复多处拼写错误和代码格式问题
- 优化缓存更新逻辑,避免不必要的重新计算
2025-09-01 14:51:11 +08:00
超级老白兔
f77e8709a8 refactor(ckchat): 重构联系人列表获取逻辑并优化状态管理
- 将联系人列表获取逻辑从组件移至store
- 新增getNewContractList方法替代直接访问状态
- 移除不再使用的getContractList方法
- 优化缓存处理逻辑
- 修复部分语法错误和格式问题
2025-09-01 14:44:46 +08:00
超级老白兔
f05efe464a feat(ckchat): 重构联系人列表逻辑并添加缓存功能
- 将联系人列表构建逻辑提取到独立api文件
- 添加getContractList缓存机制避免重复计算
- 优化kfSelected变更时的缓存清除逻辑
- 移除main.ts中冗余的联系人分组逻辑
2025-09-01 11:40:01 +08:00
超级老白兔
3cb10e217b perf(ckchat): 优化getChatSessions缓存机制避免无限循环
添加缓存机制来避免getChatSessions方法的重复计算,通过检查kfSelected和chatSessions的变化来决定是否重新计算缓存。同时在相关操作后主动清除缓存以确保数据一致性。
2025-09-01 10:43:09 +08:00
超级老白兔
52430d6466 refactor(db): 移除测试相关代码并优化消息列表数据结构
移除数据库测试页面和示例代码,清理测试路由配置
新增MessageListData接口支持微信群组和联系人两种数据类型
优化数据库表结构和服务导出
2025-09-01 10:36:16 +08:00
超级老白兔
64c75772bb feat(数据库): 添加消息列表数据接口和表
refactor(聊天会话): 根据选中客服过滤会话列表

fix(聊天窗口): 移除重复的未读消息清除逻辑

refactor(侧边栏): 统一联系人点击处理逻辑

chore: 删除无用的数据库测试文件
2025-09-01 10:24:15 +08:00
超级老白兔
5437a47e37 feat(聊天窗口): 支持群聊消息获取功能
修改api接口参数和组件逻辑,使聊天窗口能够处理群聊消息
2025-08-30 17:37:55 +08:00
超级老白兔
1605b96173 chore: 更新应用名称并统一logo图片
更新aiApp的应用名称从"Ai数智员工"为"AI数智员工",同时更新了aiApp和ckApp的logo图片
2025-08-30 17:30:49 +08:00
超级老白兔
09e78277b5 feat(ckchat): 添加获取客服用户信息的接口方法 2025-08-30 17:17:09 +08:00
超级老白兔
58ad5b3254 Merge branch 'develop' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into develop 2025-08-30 17:15:50 +08:00
超级老白兔
14813013a7 feat(ckchat): 添加获取客服用户信息的方法并更新相关引用
添加getKfUserInfo方法用于根据微信账号ID获取客服用户信息
更新Person组件中使用新方法替代原有的kfSelectedUser调用
2025-08-30 17:15:22 +08:00
wong
343c142d87 门店端优化 2025-08-30 17:14:57 +08:00
超级老白兔
25b983a9ae Merge branch 'develop' into yongpxu-dev
# Conflicts:
#	Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx   resolved by develop version
#	Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx   resolved by yongpxu-dev version
#	Cunkebao/src/store/module/ckchat/ckchat.data.ts   resolved by yongpxu-dev version
#	Cunkebao/src/store/module/ckchat/ckchat.ts   resolved by yongpxu-dev version
2025-08-30 17:10:30 +08:00
超级老白兔
8e16067cf3 fix(SidebarMenu/WechatFriends): 修复头像显示逻辑,优先展示chatroomAvatar
当contact.avatar不存在时,现在会尝试使用contact.chatroomAvatar作为头像来源
2025-08-30 17:08:06 +08:00
wong
b9d88160a2 触客宝右边栏提交 2025-08-30 17:07:53 +08:00
超级老白兔
b780a4db58 fix(ckchat): 修正获取选中客服信息的方法名拼写错误 2025-08-30 17:07:08 +08:00
超级老白兔
0975995486 feat(ckbox): 重构微信联系人分组逻辑并优化数据库操作
- 在ContactGroupByLabel和weChatGroup接口中添加count和labels字段
- 将weChatGroup重命名为weChatGroupServerId以更准确描述用途
- 修改数据库操作,使用service直接处理数据存储
- 重构createContractList函数,根据groupType查询不同服务获取数据
- 移除VerticalUserList中不必要的异步获取逻辑,直接使用store数据
2025-08-30 17:02:31 +08:00
超级老白兔
14f0a6d7f3 fix: 修正数据库清理中微信群的表名拼写错误 2025-08-30 15:55:54 +08:00
超级老白兔
136e0f6225 refactor(ckbox): 统一接口命名将wechatGroup改为weChatGroup
重构微信相关接口命名,将wechatGroup统一改为weChatGroup以保持命名一致性
更新相关组件、store和数据库服务以适配新接口名称
2025-08-30 15:55:15 +08:00
超级老白兔
8581432bf4 refactor(数据库): 重命名GroupData为wechatGroup并优化数据库初始化
重构GroupData接口为wechatGroup以更准确描述微信群组数据结构
在数据库初始化时添加调试模式清理数据功能
更新所有相关引用和数据库服务实例名称
2025-08-30 15:36:03 +08:00
超级老白兔
5bdd299dad refactor(db): 重构数据库架构使用serverId作为主键
- 将数据库主键从自增id改为直接使用serverId,避免ID冲突
- 简化数据存储和查询逻辑,提高性能
- 添加重复数据检测和去重功能
- 更新相关组件以适配新的数据库接口
- 在应用启动时初始化数据库连接
2025-08-30 15:00:26 +08:00
超级老白兔
d4336ed447 refactor(store): 移除未使用的ckchat模块及相关代码
清理不再使用的ckchat store模块及其相关导入,简化代码结构
2025-08-30 14:23:12 +08:00
超级老白兔
fa1b2b329f Merge branch 'yongpxu-dev' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into yongpxu-dev 2025-08-30 11:52:55 +08:00
超级老白兔
13cb684abd refactor(store): 重构ckchat模块为子目录结构
feat(test): 添加数据库测试页面和工具

将ckchat相关代码移动到store/module/ckchat子目录,包含数据定义和实现文件
添加数据库测试页面和工具类,支持服务器ID与本地ID映射
移除不再使用的initSafeArea函数
2025-08-30 11:52:52 +08:00
wong
f6837f7819 代码提交 2025-08-30 10:58:34 +08:00
超级老白兔
bf2e017ca0 Merge branch 'yongpxu-dev' into yongpxu-dev4 2025-08-29 17:20:25 +08:00
超级老白兔
1defb30632 Merge branch 'yongpxu-dev' into yongxu-dev3 2025-08-29 17:20:19 +08:00
超级老白兔
b3aa52cd91 FEAT => 本次更新项目为: 2025-08-29 17:19:23 +08:00
超级老白兔
06e0ce5106 refactor: 移除测试页面并优化主页面功能
- 删除未使用的测试页面及相关路由配置
- 注释掉主页面中暂时不需要的初始化函数调用
- 添加页面备份文件以备后续参考
2025-08-29 17:14:23 +08:00
超级老白兔
46c31ac653 feat(更新服务): 添加应用自动更新功能
- 在首页添加应用版本检查和更新功能
- 新增updateService.js实现版本比较、下载安装和更新提示
- 支持Android和iOS平台的更新处理
2025-08-29 17:11:27 +08:00
超级老白兔
778f1a3a11 feat(update): 添加应用自动更新功能
- 新增updateService模块实现版本检查、下载和安装功能
- 在首页集成更新检查逻辑
- 支持Android和iOS平台的更新处理
- 添加版本比较和更新提示对话框功能
2025-08-29 17:09:47 +08:00
wong
fb10e43055 代码优化 2025-08-29 17:04:11 +08:00
wong
2deb80fdbf app更新优化 2025-08-29 16:56:50 +08:00
wong
1d731c0006 app更新接口提交 2025-08-29 16:46:47 +08:00
超级老白兔
3dba61f9d9 chore: 更新manifest.json中的appid 2025-08-29 16:03:05 +08:00
超级老白兔
2ba9616f49 docs: 更新应用名称为"Ai数智员工"
修改manifest.json文件中的应用名称,从"Ai智能客服"变更为"Ai数智员工"以反映产品定位更新
2025-08-29 16:00:20 +08:00
超级老白兔
27aa23179f refactor: 更新应用名称和API基础URL
将应用名称从"存客宝"更改为"Ai智能客服"以更准确反映产品功能
更新baseUrl为新的API域名"kr-phone.quwanzhi.com"
2025-08-29 15:59:53 +08:00
超级老白兔
5a23174e8b Merge branch 'yongpxu-dev' into yongpxu-dev4 2025-08-29 15:57:54 +08:00
超级老白兔
e80a31905e Merge branch 'yongpxu-dev' into yongxu-dev3 2025-08-29 15:57:46 +08:00
超级老白兔
c8d63bf93a Merge branch 'yongpxu-dev' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into yongpxu-dev 2025-08-29 15:56:02 +08:00
超级老白兔
02b394f31b feat(聊天窗口): 添加右侧联系人资料卡片组件
将联系人资料卡片从主聊天窗口组件中拆分出来,创建独立的Person组件
实现资料卡片的样式和功能,包括基本信息展示、备注编辑和操作按钮
添加响应式设计支持移动端显示
2025-08-29 15:55:03 +08:00
超级老白兔
a5dcb64a10 feat: 初始化AI应用项目结构并添加基础功能
添加项目基础文件结构包括静态资源、配置文件、页面组件和工具函数
实现web-view通信功能,支持配置传递和消息处理
添加安全区域高度计算工具和全局样式配置
2025-08-29 15:48:59 +08:00
超级老白兔
db4bc8651d feat(客服聊天): 添加获取当前选中客服用户的功能
在CkChatState接口中添加kfSelectedUser方法用于获取当前选中的客服用户信息,并在聊天窗口组件中显示该信息
2025-08-29 15:40:46 +08:00
超级老白兔
8e177534f4 FEAT => 本次更新项目为: 2025-08-29 15:29:48 +08:00
超级老白兔
49a94318ac Merge branch 'develop' into yongpxu-dev
# Conflicts:
#	Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx   resolved by yongpxu-dev version
2025-08-29 15:27:07 +08:00
wong
9fc2dee5d7 触客宝右侧功能提交 2025-08-29 15:16:59 +08:00
wong
d89682027e 门店端优化 2025-08-29 15:16:17 +08:00
超级老白兔
bc7cc6810d feat(db): 添加Dexie数据库支持并重构数据存储结构
添加Dexie作为IndexedDB封装库,实现本地数据存储功能
重构数据接口定义和存储模块结构,优化类型定义
统一数据接口文件位置,增强代码可维护性
2025-08-29 15:13:31 +08:00
超级老白兔
bdc94d853d refactor(组件): 优化微信好友列表和聊天窗口菜单代码结构
重构微信好友列表组件中的getCollapseItems方法,简化代码格式
将聊天窗口的菜单从JSX改为items数组配置方式,提高可维护性
2025-08-29 10:49:13 +08:00
超级老白兔
190f2df15e feat(store): 添加asyncKfSelected方法用于更新选中客服
添加asyncKfSelected方法到ckchat store,用于异步更新选中的客服ID。同时移除未使用的代码和console.log
2025-08-29 10:41:19 +08:00
超级老白兔
7a4831e200 feat(ckchat): 添加客服在线状态显示和选择功能
- 在KfUserListData接口中添加isOnline字段表示客服在线状态
- 在CkChatState中添加kfSelected字段存储当前选中的客服ID
- 重构VerticalUserList组件,使用zustand管理选中状态
- 添加在线状态指示器样式,区分在线和离线状态
- 优化persistUtils工具,添加分片存储功能处理大数据
2025-08-29 10:36:37 +08:00
wong
db2133a4b0 Merge branch 'develop' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into develop 2025-08-29 09:51:56 +08:00
wong
26e519bec0 群创建 代码优化 2025-08-29 09:51:00 +08:00
超级老白兔
bcc2022826 style(SidebarMenu): 调整侧边栏菜单的内边距和移除多余样式
移除搜索框的全局样式和标签栏底部内边距,优化头部容器的内边距
2025-08-29 09:45:24 +08:00
超级老白兔
faefefe186 refactor(ckbox): 优化侧边栏菜单组件和联系人列表
- 将群组图标和文字替换为朋友圈
- 移除未使用的response变量
- 重构联系人列表组件,使用Collapse的items属性替代Panel组件
- 添加CollapseProps类型导入
2025-08-29 09:43:39 +08:00
超级老白兔
519f66f46d Merge branch 'develop' into yongpxu-dev 2025-08-29 09:27:32 +08:00
超级老白兔
bc6d85acca feat(消息列表): 添加未读消息计数和最近一天消息提示
- 在消息列表项中添加未读消息计数气泡显示
- 默认不展开微信好友分组
- 在消息列表底部添加"最近一天的消息"提示栏
2025-08-28 18:30:11 +08:00
超级老白兔
d878a1fcaa feat(WechatFriends): 实现联系人列表分页加载功能
添加react-window依赖并实现分组联系人列表的分页加载功能,优化大数据量下的性能表现
- 每组初始加载20条数据,滚动到底部可点击加载更多
- 添加加载状态提示和"没有更多了"的结束提示
- 优化样式添加加载更多按钮的容器样式
2025-08-28 18:24:05 +08:00
超级老白兔
b4eb2919b1 feat(联系人列表): 实现分组展示功能并重构组件结构
添加新的联系人分组功能,支持按分组展示联系人列表
重构联系人列表组件结构,将原有组件拆分为更清晰的模块
新增状态管理逻辑用于存储和获取分组联系人数据
移除不再使用的旧样式文件和组件
2025-08-28 18:17:40 +08:00
超级老白兔
64b5518309 fix(api): 处理401错误时跳转到登录页面并显示提示
添加对401错误的处理逻辑,当用户未登录或登录过期时,显示提示并跳转到登录页面。同时保留当前路径以便登录后跳回。
2025-08-28 18:01:36 +08:00
超级老白兔
e2ff3dd7c5 feat(ContentSelection): 添加搜索功能并优化分页逻辑
- 在PopupHeader组件中添加onSearch回调支持
- 将Input组件替换为Input.Search以支持搜索按钮
- 重构selectionPopup组件,移除useCallback并优化搜索和分页处理
- 减少每页显示数量从20改为10
- 简化空状态显示逻辑
2025-08-28 17:56:12 +08:00
超级老白兔
7430f4deb8 Merge branch 'yongpxu-dev' into yongpxu-dev2
# Conflicts:
#	Cunkebao/dist/.vite/manifest.json
#	Cunkebao/dist/index.html
2025-08-28 17:39:40 +08:00
超级老白兔
b89860bc93 refactor(ContentSelection): 将弹窗组件抽离为独立文件并优化逻辑
将内容选择弹窗组件抽离为独立的selectionPopup.tsx文件,优化代码结构
添加加载状态处理,改进搜索和分页逻辑,增强组件复用性
2025-08-28 17:38:35 +08:00
超级老白兔
87a1002aa5 fix(workspace/moments-sync): 修复群组选择key冲突并更新内容库显示逻辑
修复测试页面中群组选择tab的key冲突问题,将'groups'改为'groups2'
更新MomentsSync页面内容库显示逻辑,使用contentGroupsOptions中的name字段替代contentLibraryNames
2025-08-28 17:19:23 +08:00
超级老白兔
ca44dab358 fix(selection): 使用临时状态管理选中项避免直接修改props
在多个选择弹窗组件中引入临时状态变量tempSelectedOptions来管理选中项,避免直接修改props中的selectedOptions。当用户确认选择时才更新实际选中的选项,提供更一致的用户体验。
2025-08-28 17:14:50 +08:00
超级老白兔
53a78e6744 feat(ContentSelection): 添加临时选中选项以优化选择交互
在内容选择组件中引入临时选中选项(tempSelectedOptions),避免直接修改选中状态。用户确认时才更新实际选中选项,提供更流畅的选择体验并防止误操作。
2025-08-28 17:04:54 +08:00
wong
e050da41e5 验证修改优化 2025-08-28 16:35:10 +08:00
超级老白兔
0a6f892ba8 FEAT => 本次更新项目为:contentLibraries 2025-08-28 16:26:14 +08:00
超级老白兔
f7b28d876d fix(AccountListModal): 修改状态2的显示文本为"请求已发送待通过"
构建产物已更新,反映最新的代码变更
2025-08-28 16:09:58 +08:00
超级老白兔
b0496e309e FEAT => 本次更新项目为: 2025-08-28 16:06:39 +08:00
超级老白兔
31d75be68e fix(AccountListModal): 更新账号状态显示逻辑和颜色标识
修改账号状态显示逻辑,将字符串状态改为数字类型,并更新对应的颜色标识和状态文本。同时更新构建产物的文件引用路径。
2025-08-28 16:06:16 +08:00
超级老白兔
1053d4125f fix: 修正设备组字段名拼写错误及状态值错误
修复接口字段名拼写错误,将`devices`改为`deveiceGroups`以保持前后端一致
同时修正状态值映射错误,将启用状态对应的值从2改为0
2025-08-28 15:58:50 +08:00
超级老白兔
fdc6c15d88 feat(ckchat): 添加联系人分组功能并实现骨架屏优化
添加新的联系人分组状态 newContractList 和对应的异步设置方法 asyncNewContractList
实现页面加载时的骨架屏效果,优化用户体验
重构 SidebarMenu 组件样式,分离骨架屏逻辑
新增 PageSkeleton 组件用于统一管理骨架屏
2025-08-28 15:51:32 +08:00
超级老白兔
ee5d7fef31 Merge branch 'yongpxu-dev' into yongpxu-dev2
# Conflicts:
#	Cunkebao/src/pages/pc/ckbox/main.ts   resolved by yongpxu-dev version
2025-08-28 15:41:51 +08:00
超级老白兔
640daf2431 refactor(ckbox): 重构客服列表和联系人管理逻辑
- 将ctrlUserList重命名为kfUserList并优化相关接口
- 新增异步更新客服列表、联系人列表和会话列表的方法
- 重构联系人分组逻辑,支持按标签分组
- 优化会话列表排序规则,按最后更新时间降序排列
- 移除无用代码并简化组件逻辑
2025-08-28 15:18:18 +08:00
超级老白兔
703656d3c4 Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-08-27 17:51:38 +08:00
超级老白兔
3c1337b213 FEAT => 本次更新项目为: 2025-08-27 17:51:24 +08:00
超级老白兔
57f59b8827 Merge branch 'yongpxu-dev2' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into yongpxu-dev2 2025-08-27 17:48:18 +08:00
超级老白兔
3bf91cedc9 refactor(ui): 移除手动添加选项并重命名控制终端列表变量
- 删除两个页面中的"手动添加"标签页选项
- 将controlTerminalList重命名为kfUserList以提高可读性
- 更新构建文件引用路径
2025-08-27 17:48:15 +08:00
超级老白兔
93f4f1dc5a Merge branch 'yongpxu-dev' into yongpxu-dev2
# Conflicts:
#	Cunkebao/dist/.vite/manifest.json
#	Cunkebao/dist/index.html
2025-08-27 17:26:54 +08:00
超级老白兔
35c5cf60d4 refactor(群推任务): 统一API接口参数和响应处理
重构群推任务相关API接口,将参数改为对象形式并统一响应处理
更新任务状态切换接口,添加type参数区分任务类型
修改字段名maxPushPerDay为maxPerDay保持一致性
使用Toast组件替代window.alert进行提示
2025-08-27 17:26:00 +08:00
超级老白兔
0d5e34cfdd refactor(群推表单): 重构表单数据结构并整合京东联盟功能
- 将布尔类型字段改为数字枚举类型以增强可读性
- 移除独立的京东联盟组件,将其功能整合到基础设置中
- 简化表单步骤,从4步减少到3步
- 优化表单字段命名使其更语义化
2025-08-27 17:04:39 +08:00
超级老白兔
29e6bf8582 feat(群推任务): 添加更新群推任务接口并修改创建接口路径
修改创建群推任务的接口路径为/v1/workbench/create,并添加type参数
新增更新群推任务的接口/v1/workbench/update
2025-08-27 15:35:58 +08:00
超级老白兔
79ccd8580e refactor(流量池): 优化批量加入分组弹窗组件并清理无用代码
将Modal组件替换为Popup组件以改善移动端用户体验,移除调试日志和未使用的类型导入
2025-08-27 14:30:20 +08:00
超级老白兔
968480669e refactor(ckbox): 重构侧边栏组件结构并优化代码组织
feat(ckbox): 添加垂直用户列表组件用于终端用户展示
feat(ckbox): 实现控制终端用户数据管理功能

fix(ckbox): 修复联系人列表样式问题
fix(ckbox): 解决消息列表数据同步问题

style(ckbox): 调整侧边栏样式和布局
style(ckbox): 优化组件样式文件结构

chore(ckbox): 移除未使用的客户相关组件和样式
2025-08-27 14:29:37 +08:00
超级老白兔
1334942fc0 feat(聊天窗口): 优化消息时间显示格式并调整样式
重构消息时间格式化逻辑,提取为公共工具函数 formatWechatTime
移除聊天窗口中的分隔线样式,调整消息间距
更新构建产物文件引用路径
2025-08-27 10:22:51 +08:00
超级老白兔
08dc74cb67 feat(聊天窗口): 优化消息时间显示样式和格式化逻辑
- 在消息时间前后添加分割线并调整间距
- 根据消息时间范围显示不同格式的时间(当天、昨天、一周内、超过一周)
- 使用微信时间或创建时间作为消息时间基准
- 当相邻消息时间差超过5分钟时创建新时间组
2025-08-26 16:34:17 +08:00
超级老白兔
3645075473 fix(websocket): 修复WebSocket连接和视频消息处理问题
- 显式指定WebSocket URL确保连接到正确服务器
- 添加本地地址连接拦截逻辑防止错误连接
- 优化视频消息处理逻辑,包括加载状态管理和下载响应处理
- 使用时间戳作为唯一序列号避免重复
- 修复消息订阅逻辑,仅在有待处理视频请求时监听
2025-08-26 16:18:37 +08:00
超级老白兔
a26b465ff6 refactor(ckbox): 统一将contact重命名为contract
将代码中所有的contact变量和类名统一改为contract,包括API路径、组件属性、CSS类名等
更新相关路由配置和文档中的API接口说明
2025-08-26 14:19:19 +08:00
超级老白兔
c7b15896ac fix: 修复下载视频时friendMessageId的错误赋值逻辑 2025-08-26 11:50:26 +08:00
超级老白兔
f7a27c7c63 feat(ckbox): 实现聊天会话管理和多媒体消息支持
- 在ckchat store中添加聊天会话管理功能
- 支持多种类型消息渲染(图片、视频、音频、文件等)
- 优化聊天界面样式和时间分组显示
- 修复联系人数据结构命名问题
- 添加群聊列表获取功能
2025-08-26 11:48:10 +08:00
超级老白兔
c12633b2b1 refactor(ckbox): 重构聊天模块接口和类型定义
- 将HTTP方法从PUT改为GET以符合REST规范
- 移除测试用的硬编码凭证
- 新增ChatRecord接口并替换原有ChatSession
- 合并重复的样式文件并重命名
- 优化联系人点击处理逻辑
2025-08-25 14:05:32 +08:00
超级老白兔
83ae92b2b6 FEAT => 本次更新项目为: 2025-08-22 17:55:04 +08:00
超级老白兔
465c63ed1c feat(ckbox): 添加消息清除未读和获取功能
- 在api.ts中添加clearUnreadCount和getMessages接口
- 实现联系人点击时清除未读并获取消息
- 优化websocket重连逻辑,添加认证失败处理
- 修改登录凭证为karuo账户
- 添加递归获取所有联系人列表功能
2025-08-22 16:54:24 +08:00
超级老白兔
812cb97713 refactor(ckbox): 统一联系人数据结构并简化聊天会话处理
- 将ContactData接口添加索引签名以支持动态属性
- 移除ChatSession接口,直接使用ContactData作为聊天会话类型
- 简化组件间数据传递和状态管理
- 更新相关组件props类型定义
2025-08-22 15:17:47 +08:00
超级老白兔
f099bb5420 chore: 更新构建生成的资源文件引用路径
自动构建后更新manifest.json和index.html中的资源文件引用路径
2025-08-22 14:47:52 +08:00
超级老白兔
0b1872cc02 fix: 修复设备ID类型错误并优化选项处理
- 将设备ID从string类型改为number类型以匹配后端接口
- 为packageOptions添加默认空数组防止undefined错误
- 优化选项获取逻辑,正确处理API返回的数据结构
- 修复自动点赞任务创建时的friendsGroups校验条件
2025-08-22 14:47:39 +08:00
超级老白兔
b3cf0243dd 1 2025-08-22 14:24:52 +08:00
超级老白兔
b32e6cac0c Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-08-22 14:24:30 +08:00
超级老白兔
32ea075e90 FEAT => 本次更新项目为: 2025-08-22 14:21:35 +08:00
wong
5ce77e0adc 内容库兼容性优化 2025-08-22 11:32:01 +08:00
超级老白兔
647a053ccc fix(workspace): 修正自动点赞任务中设备组字段名称错误
修复自动点赞任务创建页面中设备组字段名称拼写错误,将`deveicegroups`统一改为`deveiceGroups`,并更新相关逻辑判断和组件属性传递
2025-08-22 10:36:33 +08:00
wong
d726be7d66 代码提交 2025-08-22 10:23:05 +08:00
超级老白兔
d0bd7d4cd7 feat(websocket): 重构WebSocket连接逻辑并添加初始化命令
- 重构WebSocket连接逻辑,使用新的URL参数构建方式
- 添加初始化连接时的CmdSignIn和CmdRequestWechatAccountsAliveStatus命令
- 修改WebSocket消息类型定义,简化消息结构
- 从CkChatStore获取accountId用于连接参数
- 移除Login页面中与WebSocket相关的冗余代码
2025-08-21 17:44:47 +08:00
超级老白兔
eadb7a8f3e refactor(auto-group): 将内容库选择器替换为流量池选择器
将原有的内容库选择组件(ContentSelector)替换为流量池选择组件(PoolSelector),并更新相关类型定义和表单逻辑
2025-08-21 16:58:12 +08:00
超级老白兔
cfb752509a refactor(PoolSelection): 重命名组件及相关接口从Group到Pool
将组件及相关接口从GroupSelection重命名为PoolSelection以更准确反映其用途
更新相关引用和类型定义
调整流量池选择弹窗的样式和交互
2025-08-21 16:47:55 +08:00
超级老白兔
8e89c5ba73 feat(ckbox): 实现聊天界面功能并集成WebSocket
添加联系人列表、聊天会话和消息窗口组件
集成WebSocket连接实现实时聊天功能
重构API调用以获取聊天记录和联系人信息
添加样式文件美化聊天界面
2025-08-21 10:51:23 +08:00
超级老白兔
61e1ffc6c4 style(组件): 优化流量池选择组件头像显示样式
将PoolSelection组件中的头像显示从Avatar组件改为显示名称首字母的div,提升视觉一致性
2025-08-20 18:44:54 +08:00
超级老白兔
ad5fc0bb52 feat(ckbox): 重构联系人列表获取逻辑并优化登录流程
- 将联系人列表从模拟数据改为从API获取
- 优化登录处理流程,分离存客宝和触客宝的token获取
- 添加messageApi统一处理消息提示
- 修复路径引用问题并移除未使用的导入
2025-08-20 17:38:59 +08:00
wong
0681c69d67 自动建群群提交 + 自动点赞数据优化 2025-08-20 17:35:34 +08:00
超级老白兔
3f4101a87d FEAT => 本次更新项目为: 2025-08-20 17:33:28 +08:00
超级老白兔
87aea20056 feat(流量池选择): 新增流量池包选择功能并保留原接口兼容
- 添加流量池包相关接口和类型定义
- 修改选择弹窗组件以支持流量池包选择
- 保留原有流量池接口和类型以兼容现有代码
- 更新UI展示以适应流量池包数据结构
2025-08-20 17:31:22 +08:00
超级老白兔
3d944770f1 feat(流量池选择): 新增流量池选择组件及相关API和类型定义
添加流量池选择组件,包含以下功能:
- 流量池列表获取API
- 流量池项和组件属性类型定义
- 选择弹窗组件实现
- 样式文件和测试页面集成
- 支持搜索、分页和多选功能
2025-08-20 16:08:49 +08:00
超级老白兔
1d93368eb0 feat(自动建群表单): 优化表单加载状态和输入组件样式
- 添加数据加载状态控制,编辑模式显示加载中提示
- 重构数字输入组件样式,使用antd的InputNumber组件
- 将StepIndicator移动到header区域优化布局
- 重命名defaultValues为initialValues以更准确表达用途
2025-08-20 15:45:55 +08:00
超级老白兔
fcc4805220 feat(自动建群): 添加内容库选择功能并重构表单为分步式
添加内容库选择组件及相关类型定义
重构自动建群表单为分步式操作流程
新增基础设置、设备选择和内容选择三个步骤组件
完善表单验证逻辑和步骤导航功能
2025-08-20 15:07:17 +08:00
超级老白兔
d93165bfac feat(自动建群): 添加删除自动建群任务功能
新增删除自动建群任务的API接口及前端交互逻辑,使用自定义确认弹窗替代原生confirm,提供更好的用户体验和错误处理
2025-08-20 11:19:53 +08:00
超级老白兔
640f6fca02 Merge branch 'yongpxu-dev' into yongpxu-dev4 2025-08-20 09:41:25 +08:00
超级老白兔
82c2300496 Merge branch 'yongpxu-dev' into yongxu-dev3 2025-08-20 09:41:20 +08:00
超级老白兔
d2560784a2 feat(test): 添加更新通知组件测试页面
添加用于
2025-08-19 15:20:00 +08:00
超级老白兔
16fae82c17 FEAT => 本次更新项目为: 2025-08-19 15:07:36 +08:00
超级老白兔
f9be0fad2d feat(UpdateNotification): 优化更新提示组件样式和功能
- 添加 forceShow 和 onClose 属性支持强制显示和关闭回调
- 重新设计 UI 样式为顶部通知栏布局
- 新增"稍后"按钮支持延迟更新
- 优化动画效果和交互体验
2025-08-19 14:57:25 +08:00
超级老白兔
50db041021 FEAT => 本次更新项目为: 2025-08-19 12:12:50 +08:00
超级老白兔
a6945d1dc9 FEAT => 本次更新项目为: 2025-08-19 11:58:01 +08:00
超级老白兔
9c2f275534 調整自動建群列表頁腳樣式,將原有的內聯樣式替換為 CSS 類名以提升可維護性。 2025-08-19 11:50:11 +08:00
超级老白兔
bd317d8fc0 FEAT => 本次更新项目为: 2025-08-19 11:02:35 +08:00
超级老白兔
eda7f00453 更新自動建群 API,調整獲取任務列表的路徑,並新增複製自動建群任務的功能,優化任務複製邏輯以提升用戶體驗。 2025-08-19 11:01:19 +08:00
超级老白兔
b6cef907f9 調整自動建群表單,移除群名稱模板的預設值,並優化表單項目以支持動態數據顯示。 2025-08-19 10:54:31 +08:00
超级老白兔
0a792a5406 更新自動建群表單,新增獲取群組詳情的 API 調用以回填表單數據,並調整表單結構以支持動態數據顯示。 2025-08-19 10:35:25 +08:00
超级老白兔
0468531655 FEAT => 本次更新项目为: 2025-08-18 18:21:31 +08:00
超级老白兔
dac3d51f88 更新自動建群功能,調整 API 接口以支持朋友圈同步任務,優化表單結構及樣式,並整合導航組件以提升用戶體驗。更新資源引用以確保正確加載。 2025-08-18 18:01:59 +08:00
超级老白兔
273ba8073c FEAT => 本次更新项目为: 2025-08-18 11:33:56 +08:00
超级老白兔
96e22b14aa Merge branch 'yongpxu-dev' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into yongpxu-dev 2025-08-18 11:32:57 +08:00
超级老白兔
1b1bd7536d 新增 VITE_API_WS_URL 環境變數,更新主程式以引入嚴格模式包裝器,並在登錄頁面中整合觸客寶用戶信息獲取功能,調整請求模組以動態獲取 token2。 2025-08-18 11:30:56 +08:00
超级老白兔
60e5a682bd FEAT => 本次更新项目为: 2025-08-16 17:59:36 +08:00
超级老白兔
8af3ad3549 新增流量分發記錄功能,更新樣式以改善搜索條和分發統計彈窗的顯示效果,並在列表中整合相應的功能邏輯。 2025-08-16 17:34:36 +08:00
超级老白兔
7dd20b0a9b 新增 VITE_API_BASE_URL2 環境變數,更新請求模組以移除 token2 支持,並在登錄頁面中整合新的 token 登錄功能,調整狀態管理以支持多個 token 的存儲。 2025-08-16 16:31:02 +08:00
超级老白兔
85f4ca9744 FEAT => 本次更新项目为:更新 index.html 和 manifest.json 中的資源引用,新增 token2 支持於請求模組,並調整 ChatWindow 組件的樣式以改善顯示效果。 2025-08-16 11:24:07 +08:00
超级老白兔
c7e90fc917 新增用戶信息統計功能,更新主頁面以顯示用戶相關數據,並調整狀態管理邏輯以提升性能。 2025-08-15 19:15:56 +08:00
wong
639d7d3cb6 1 2025-08-15 18:30:03 +08:00
超级老白兔
504220f630 新增用戶信息統計功能,更新主頁面以顯示用戶相關數據,並調整相應的狀態管理邏輯。 2025-08-15 17:59:22 +08:00
超级老白兔
94196b1fe3 头部信息 2025-08-15 17:53:42 +08:00
超级老白兔
9cd285223b 新增一條綠色平滑線圖數據,並調整相應的樣式屬性以提升可視化效果。 2025-08-15 17:42:52 +08:00
wong
25ae2458f0 流量池添加及场景获客统计数据 2025-08-15 15:37:00 +08:00
超级老白兔
a66f9b01eb 移除按鈕的上邊距樣式,以改善整體佈局。 2025-08-15 14:15:03 +08:00
超级老白兔
695ccdd177 FEAT => 本次更新项目为: 2025-08-14 18:13:35 +08:00
超级老白兔
d17ed6e5c3 FEAT => 本次更新项目为: 2025-08-14 18:12:03 +08:00
超级老白兔
5f58a1a2c4 FEAT => 本次更新项目为: 2025-08-14 17:05:36 +08:00
超级老白兔
7be8ff2e94 刪除帳號和設備列表彈窗組件,新增流量池列表彈窗組件,並更新樣式以支持新的彈窗功能,優化流量分配列表的顯示邏輯。 2025-08-14 16:53:16 +08:00
超级老白兔
27b9ef910c 更新流量分配列表,新增帳號和設備列表彈窗功能,調整樣式以提升用戶體驗,並優化設備選擇組件的顯示邏輯。 2025-08-14 15:13:29 +08:00
超级老白兔
10c78470b3 Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-08-14 15:04:27 +08:00
超级老白兔
566ca54fcf FEAT => 本次更新项目为: 2025-08-14 15:04:03 +08:00
超级老白兔
db4e2f9d43 FEAT => 本次更新项目为: 2025-08-14 10:46:22 +08:00
超级老白兔
0e71a6701d Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-08-14 10:13:24 +08:00
超级老白兔
b4384c4329 FEAT => 本次更新项目为: 2025-08-14 10:11:07 +08:00
超级老白兔
3c71a2be96 更新流量池列表相關邏輯,替換獲客場景和包選項的獲取方式,調整篩選條件的狀態管理,並優化篩選模態框的數據加載。 2025-08-13 17:34:38 +08:00
超级老白兔
907451df48 FEAT => 本次更新项目为: 2025-08-13 17:27:54 +08:00
超级老白兔
94df1469c0 更新流量池列表相關邏輯,替換獲客場景接口,調整用戶價值和狀態的篩選邏輯,並優化篩選模態框的狀態管理。 2025-08-13 17:07:51 +08:00
wong
e53f31487e 门店端优化 2025-08-13 17:03:25 +08:00
wong
f5dd5e8191 电话获客 2025-08-13 17:02:36 +08:00
超级老白兔
2c509d5aa1 更新設備選擇組件,新增獲客場景選項,調整篩選邏輯以支持多種篩選條件,並修正路由以符合新結構。 2025-08-13 16:16:05 +08:00
wong
5ef40510ce 设备管理新增删除及头像 2025-08-13 14:30:22 +08:00
超级老白兔
140c0ed7e0 FEAT => 本次更新项目为: 2025-08-13 14:28:21 +08:00
超级老白兔
e2dfd44874 更新設備選擇組件,新增設備頭像和好友數據顯示,調整樣式以提升用戶體驗,並更新相關數據結構以支持新屬性。 2025-08-13 12:18:43 +08:00
超级老白兔
f10af2741e Merge branch 'yongpxu-dev' into yongpxu-dev2
# Conflicts:
#	nkebao/postcss.config.js   resolved by yongpxu-dev version
2025-08-13 11:32:56 +08:00
超级老白兔
720686fb88 Merge branch 'yongpxu-dev' into yongxu-dev3
# Conflicts:
#	nkebao/postcss.config.js   resolved by yongpxu-dev version
2025-08-13 11:32:48 +08:00
超级老白兔
f0e06e4197 Merge branch 'yongpxu-dev' into yongpxu-dev4
# Conflicts:
#	nkebao/postcss.config.js   resolved by yongpxu-dev version
2025-08-13 11:32:40 +08:00
超级老白兔
22cc5755fa FEAT => 本次更新项目为: 2025-08-13 11:32:17 +08:00
超级老白兔
4a70635ae4 FEAT => 本次更新项目为: 2025-08-13 11:32:05 +08:00
超级老白兔
6380b8752b FEAT => 本次更新项目为: 2025-08-13 11:31:54 +08:00
wong
46ce2f7e8d 代码提交 2025-08-13 10:18:13 +08:00
wong
13577c302d 代码同步 2025-08-13 09:43:41 +08:00
超级老白兔
4e78eb6c5e 更新 index.html 和 manifest.json 中的資源引用版本,刪除不再使用的資源檔案,並調整設備詳情頁面的導航邏輯以使用 navigate 函數。 2025-08-12 18:28:19 +08:00
超级老白兔
628d940a3e FEAT => 本次更新项目为: 2025-08-12 18:26:08 +08:00
超级老白兔
949930f2cf FEAT => 本次更新项目为: 2025-08-12 17:07:42 +08:00
超级老白兔
aa583e6748 FEAT => 本次更新项目为: 2025-08-12 17:03:12 +08:00
超级老白兔
be9e07cfea 刪除 package-lock.json 和 yarn.lock 檔案,新增 antd-mobile-icons 依賴,並更新 package.json 中的腳本命令以使用 pnpm 進行構建和開發,調整 Vite 配置以允許從所有主機訪問。 2025-08-12 16:58:28 +08:00
wong
c847eff9fc 通话记录数据清洗 2025-08-12 16:58:00 +08:00
超级老白兔
746d3af943 FEAT => 本次更新项目为: 2025-08-12 16:15:55 +08:00
超级老白兔
2693e573f6 新增自定義標籤功能,更新表單數據結構以支持自定義標籤選項,並優化相關邏輯以提升用戶體驗。 2025-08-12 16:12:37 +08:00
超级老白兔
9170857d8a 優化好友備註區塊的樣式,調整提示框位置及外觀,並改善交互效果。更新好友備註選擇區的結構,提升可讀性與使用體驗。 2025-08-12 15:36:44 +08:00
超级老白兔
240c749806 更新 index.html 和 manifest.json 中的資源引用版本,將 index-PKCtfAad.js 更改為 index-C7IEJLdM.js,以保持一致性。 2025-08-12 15:32:43 +08:00
超级老白兔
88a007de7c 更新 index.html 和 manifest.json 中的資源引用版本,並在新計劃頁面中調整海報模板的屬性名稱,將 preview 改為 url,以統一資料結構。 2025-08-12 15:31:27 +08:00
wong
14c882ff08 获取电话底层优化 2025-08-12 15:02:30 +08:00
wong
67fac4b2f2 场景获客优化 2025-08-12 15:02:00 +08:00
超级老白兔
0baf472473 FEAT => 本次更新项目为: 2025-08-12 12:00:57 +08:00
超级老白兔
faf8a6e234 更新環境變數,將 API 基礎 URL 更改為 http://www.yishi.com,並調整前端資源引用的版本號。同時在新計劃頁面中新增自定義標籤功能,並優化相關邏輯 2025-08-12 11:33:55 +08:00
wong
f6b620383c Merge branch 'develop' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into develop 2025-08-12 10:05:41 +08:00
wong
d40710de3c 代码提交 2025-08-12 10:04:53 +08:00
超级老白兔
1750dc13eb Merge branch 'develop' into yongpxu-dev
# Conflicts:
#	nkebao/postcss.config.js   resolved by develop version
#	nkebao/技术栈.md   resolved by develop version
2025-08-12 09:33:39 +08:00
超级老白兔
19bce0bfa1 FEAT => 本次更新项目为: 2025-08-12 09:33:15 +08:00
超级老白兔
05333b4345 Merge branch 'develop' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into develop 2025-08-12 09:32:52 +08:00
超级老白兔
c62c06a072 FEAT => 本次更新项目为: 2025-08-12 09:32:45 +08:00
wong
1b8f64b4fb Merge branch 'develop' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into develop 2025-08-12 09:29:57 +08:00
wong
863a4fd31f 电话获客 2025-08-12 09:29:27 +08:00
超级老白兔
1b7f8bae6c Merge branch 'yongpxu-dev' into develop 2025-08-12 09:29:00 +08:00
wong
239e518676 工作台及场景获客字段同步 2025-08-12 09:28:57 +08:00
超级老白兔
60465774d4 FEAT => 本次更新项目为:
同步
2025-08-12 09:28:41 +08:00
超级老白兔
9c3bc5200a 同步代码 2025-08-12 09:27:50 +08:00
超级老白兔
dd4446fd8c FEAT => 本次更新项目为: 2025-08-12 09:24:26 +08:00
超级老白兔
ea4e36d759 FEAT => 本次更新项目为:重构上传组件的使用说明文档,统一代码风格,增加聊天文件上传组件的功能描述,并删除不再使用的样式和组件,以提升代码的可维护性和用户体验。 2025-08-11 18:06:27 +08:00
超级老白兔
4a5f9579b4 FEAT => 本次更新项目为:在个人中心页面新增“触客宝”功能模块,并在路由中添加相应的路径,以提升用户体验和功能完整性。 2025-08-11 15:26:20 +08:00
超级老白兔
69e2acd156 FEAT => 本次更新项目为:重构 PostCreateAddFriendPlanV1Controller 控制器,继承自 BaseController,修正设备参数名称为 'deveiceGroups',并优化用户信息获取逻辑,以提升代码的可读性和维护性。同时,更新开发环境变量配置。 2025-08-11 14:11:11 +08:00
超级老白兔
cdd5d88e2d Merge branch 'develop' into yongpxu-dev
# Conflicts:
#	nkebao/.env.development   resolved by yongpxu-dev version
2025-08-11 12:20:24 +08:00
超级老白兔
d8effe2dcb FEAT => 本次更新项目为:重构计划相关的 API 接口,更新计划的接口路径并移除不再使用的函数和类型定义,以简化代码结构并提升可维护性。同时,调整新建计划的逻辑以适应新的接口。 2025-08-11 12:18:22 +08:00
超级老白兔
bea245ced9 FEAT => 本次更新项目为:统一内容管理的导航路径,将所有相关路径前缀修改为 "/mine/content",以提升路由结构的一致性和用户体验。 2025-08-11 11:27:49 +08:00
超级老白兔
90f29da343 FEAT => 本次更新项目为:将 FriendSelection 组件的 selectedOptions 属性改为可选,并在相关组件中进行相应调整,以提升代码的灵活性和可读性。同时,更新导航路径以统一路由结构。 2025-08-11 11:18:02 +08:00
超级老白兔
1d5441b84a FEAT => 本次更新项目为:删除消费记录相关的 API、数据定义、样式和组件,以简化代码结构并移除不再使用的功能。 2025-08-11 11:11:13 +08:00
超级老白兔
8c9a660afd FEAT => 本次更新项目为:统一组件接口,将属性名称从 selectedGroups 更改为 selectedOptions,以提升代码一致性和可读性,并对相关组件进行了相应调整。 2025-08-11 10:49:02 +08:00
超级老白兔
0af7969fcc FEAT => 本次更新项目为: 2025-08-08 18:32:25 +08:00
超级老白兔
dc296336e0 FEAT => 本次更新项目为:新增获取京东社交媒体和推广站点列表的接口,并优化相关组件的状态管理与数据处理 2025-08-08 18:20:32 +08:00
超级老白兔
82a8c8b374 FEAT => 本次更新项目为: 2025-08-08 17:24:50 +08:00
超级老白兔
2432f7817c FEAT => 本次更新项目为:优化流量分配选项的文本格式,移除多余的空格以提升代码整洁性 2025-08-08 17:24:37 +08:00
wong
14b1230f72 场景获客及时获取群好友 2025-08-08 17:04:47 +08:00
超级老白兔
e0c0c59e73 FEAT => 本次更新项目为:将设备管理路径从 "/devices" 修改为 "/mine/devices",以统一路由结构并提升用户体验 2025-08-08 16:25:50 +08:00
超级老白兔
18c57904bb Merge branch 'yongpxu-dev2' into yongpxu-dev 2025-08-08 15:53:26 +08:00
超级老白兔
c0982905dd FEAT => 本次更新项目为: 2025-08-08 15:50:09 +08:00
超级老白兔
227ddbb776 FEAT => 本次更新项目为:新增帳號選擇組件,將選擇狀態類型從數字陣列更改為帳號項目陣列,以提升代碼的可讀性和一致性 2025-08-08 15:47:41 +08:00
超级老白兔
cef477dfee FEAT => 本次更新项目为:统一组件接口,将属性名称从 selectedContent 和 selectedGroups 更改为 selectedOptions,以提升代码一致性和可读性 2025-08-08 15:47:07 +08:00
超级老白兔
ee82732880 FEAT => 本次更新项目为: 2025-08-08 15:46:11 +08:00
超级老白兔
9dbd2d2a0c Merge branch 'yongpxu-dev' into yongpxu-dev2
# Conflicts:
#	nkebao/src/pages/mobile/test/select.tsx   resolved by yongpxu-dev version
2025-08-08 15:04:39 +08:00
超级老白兔
0abddfbfb3 FEAT => 本次更新项目为: 2025-08-08 15:03:45 +08:00
超级老白兔
8749a59957 FEAT => 本次更新项目为:重构帳號選擇組件,簡化狀態管理,並更新測試頁面以使用新接口 2025-08-08 14:59:59 +08:00
超级老白兔
b7d94b56f0 FEAT => 本次更新项目为:
设备选择构建完成
2025-08-08 14:58:26 +08:00
超级老白兔
d785f11aff FEAT => 本次更新项目为:
好友选择构建完成
2025-08-08 13:56:34 +08:00
超级老白兔
a06e691420 Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-08-08 11:36:33 +08:00
超级老白兔
98b3d0dff7 FEAT => 本次更新项目为:将设备选择组件中的属性名称从 selectedDevices 更改为 selectedOptions,以统一命名规范并提升可读性 2025-08-08 11:36:05 +08:00
超级老白兔
08d851ec81 FEAT => 本次更新项目为: 2025-08-08 10:37:43 +08:00
超级老白兔
a339dd898b FEAT => 本次更新项目为:移除選擇測試組件,更新環境變數,優化內容表單中的好友和群組選擇邏輯 2025-08-08 10:10:28 +08:00
超级老白兔
b06a0e16b0 FEAT => 本次更新项目为:
内容选择构建完成
2025-08-07 18:27:37 +08:00
超级老白兔
bb06d3e4f8 FEAT => 本次更新项目为:
内容库处理完成
2025-08-07 18:21:35 +08:00
超级老白兔
504e8ff9b6 FEAT => 本次更新项目为:
选择社群构建完成
2025-08-07 17:43:20 +08:00
超级老白兔
67c76d8b04 FEAT => 本次更新项目为:
存个版本
2025-08-07 16:15:45 +08:00
超级老白兔
662a192b81 FEAT => 本次更新项目为:
群组选择构建完成
2025-08-07 14:52:06 +08:00
wong
b33231b3b4 代码提交 2025-08-07 11:56:04 +08:00
超级老白兔
f592784c11 Merge branch 'develop' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into develop 2025-08-07 09:41:25 +08:00
超级老白兔
ab32093caf Merge branch 'develop' into yongpxu-dev 2025-08-07 09:41:06 +08:00
wong
f78cac645c 群推送代码提交 2025-08-07 09:22:39 +08:00
超级老白兔
cd88e44464 FEAT => 本次更新项目为:
同步
2025-08-06 18:29:12 +08:00
wong
94c484dbe4 工作台-消息群发 2025-08-06 18:02:03 +08:00
wong
7a2553940c 代码提交 2025-08-06 18:00:50 +08:00
wong
d4c10de30a 群同步 2025-08-06 17:57:24 +08:00
超级老白兔
7ebece0491 FEAT => 本次更新项目为:優化基本設置頁面,新增文件上傳組件,調整樣式及顯示邏輯 2025-08-06 17:54:41 +08:00
超级老白兔
f59ce8809d FEAT => 本次更新项目为:
优化了代码
2025-08-06 17:36:15 +08:00
超级老白兔
b53929cb22 FEAT => 本次更新项目为:
充值记录完成
2025-08-06 17:22:01 +08:00
超级老白兔
070d6b70d6 sj 2025-08-06 11:01:54 +08:00
超级老白兔
2deddf6fd4 FEAT => 本次更新项目为:检查更新 2025-08-05 18:12:32 +08:00
超级老白兔
a1ae5e9364 同步 2025-08-05 18:06:57 +08:00
超级老白兔
ed99243890 FEAT => 本次更新项目为:
版本更新问题去掉
2025-08-05 13:55:04 +08:00
超级老白兔
302670ed99 FEAT => 本次更新项目为:
用户信息保存
2025-08-05 13:42:31 +08:00
超级老白兔
ff801d2169 FEAT => 本次更新项目为:
改为正常版本
2025-08-05 12:02:05 +08:00
超级老白兔
dca72fac8c FEAT => 本次更新项目为:
新版本
2025-08-05 12:01:22 +08:00
超级老白兔
57a32e252d FEAT => 本次更新项目为:
更新模块完成
2025-08-05 11:59:18 +08:00
wong
982b2c024b 同步群列表 2025-08-05 10:28:28 +08:00
wong
ebff15fbed 编辑用户信息及修改密码 2025-08-05 10:26:55 +08:00
超级老白兔
9b1cfb6e03 Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-08-04 17:12:14 +08:00
超级老白兔
e63441c254 修正應用程式圖標路徑,將"/public/logo.png"更改為"/logo.png"以確保正確顯示。 2025-08-04 17:11:48 +08:00
超级老白兔
192c8c3c0a FEAT => 本次更新项目为:
样式同步了
2025-08-04 16:27:45 +08:00
超级老白兔
2ef2aff18f FEAT => 本次更新项目为:
上传封面主图构建完成
2025-08-04 16:03:39 +08:00
超级老白兔
a4be3d534f FEAT => 本次更新项目为:完善上传组件使用说明,增加数据回显功能示例,优化代码结构,移除冗余代码 2025-08-04 15:11:27 +08:00
超级老白兔
7621b91f15 FEAT => 本次更新项目为:移除上传组件相关文件,整合上传功能至主图上传组件,优化代码结构及样式 2025-08-04 14:11:36 +08:00
超级老白兔
38f5fcf360 FEAT => 本次更新项目为:
基础组件构建完成
2025-08-04 11:43:08 +08:00
超级老白兔
f2a10e249f FEAT => 本次更新项目为:
样式修正
2025-08-04 11:22:28 +08:00
超级老白兔
e36f602e6a FEAT => 本次更新项目为:
组件基础完成
2025-08-04 11:16:29 +08:00
超级老白兔
d838108025 Merge branch 'yongpxu-dev' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into yongpxu-dev 2025-08-04 10:38:09 +08:00
超级老白兔
02a42dd4b2 FEAT => 本次更新项目为:更新环境变量及标题为“存客宝”,新增应用模式标识,优化iOS设备安全区域高度计算 2025-08-04 10:35:35 +08:00
超级老白兔
6ed19f7a84 FEAT => 本次更新项目为:
store持久化
2025-08-04 10:34:59 +08:00
超级老白兔
7f5da492ee Merge branch 'yongpxu-dev' into yongpxu-dev4
# Conflicts:
#	nkebao/AI_ICON_GUIDE.md   resolved by yongpxu-dev version
#	nkebao/ICON_DETAILED_MAPPING.md   resolved by yongpxu-dev version
#	nkebao/ICON_MAPPING_GUIDE.md   resolved by yongpxu-dev version
#	nkebao/ICON_QUICK_REFERENCE.md   resolved by yongpxu-dev version
#	nkebao/index.html   resolved by yongpxu-dev version
2025-08-04 10:04:41 +08:00
超级老白兔
eedd687e47 1 2025-08-04 10:04:29 +08:00
超级老白兔
73407ceb8d Merge branch 'yongpxu-dev' into yongxu-dev3
# Conflicts:
#	nkebao/AI_ICON_GUIDE.md   resolved by yongpxu-dev version
#	nkebao/ICON_DETAILED_MAPPING.md   resolved by yongpxu-dev version
#	nkebao/ICON_MAPPING_GUIDE.md   resolved by yongpxu-dev version
#	nkebao/ICON_QUICK_REFERENCE.md   resolved by yongpxu-dev version
#	nkebao/index.html   resolved by yongpxu-dev version
2025-08-04 10:04:17 +08:00
超级老白兔
fe62708aa3 1 2025-08-04 10:04:01 +08:00
超级老白兔
9ef72679b3 Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-08-04 10:02:26 +08:00
超级老白兔
05783db2b1 FEAT => 本次更新项目为:
存了
2025-08-02 20:41:02 +08:00
超级老白兔
65f80c55b0 FEAT => 本次更新项目为:
基础配置传递过去了
2025-08-02 19:58:49 +08:00
超级老白兔
947f5c0f6b FEAT => 本次更新项目为:
联通测试完成
2025-08-02 19:52:12 +08:00
超级老白兔
4967f31791 store 恢复 2025-08-02 18:42:43 +08:00
超级老白兔
ae4cbd46dc FEAT => 本次更新项目为:
初始化状态链接功能
2025-08-02 17:10:41 +08:00
超级老白兔
56c9c24937 FEAT => 本次更新项目为: 2025-08-02 16:33:53 +08:00
超级老白兔
688e3faacd FEAT => 本次更新项目为:
App与H5交互构建完成
2025-08-02 16:31:59 +08:00
超级老白兔
91ddc42667 FEAT => 本次更新项目为:
交互完成,开始封装
2025-08-02 15:01:54 +08:00
超级老白兔
fca202fd76 FEAT => 本次更新项目为:
精简初步完成
2025-08-02 14:43:08 +08:00
超级老白兔
2e01757519 FEAT => 本次更新项目为:
定版本app通信
2025-08-02 11:32:04 +08:00
超级老白兔
fb543f99ba Merge branch 'yongpxu-dev' into yongpxu-dev2 2025-08-01 16:45:03 +08:00
超级老白兔
de0f8fafda 同步 2025-08-01 16:44:35 +08:00
超级老白兔
fddda27ca8 FEAT => 本次更新项目为:
基础通信搞定了
2025-08-01 16:29:34 +08:00
超级老白兔
325d53cad6 FEAT => 本次更新项目为:
新封装技术栈
2025-08-01 14:30:51 +08:00
超级老白兔
b59ddea3b6 FEAT => 本次更新项目为:
可以构建app了
2025-08-01 11:36:29 +08:00
超级老白兔
3cc48d33e6 FEAT => 本次更新项目为:提交 2025-08-01 11:14:33 +08:00
超级老白兔
165349172b FEAT => 本次更新项目为:
更新
2025-08-01 11:14:26 +08:00
超级老白兔
fa36c21672 FEAT => 本次更新项目为:
构建安卓app
2025-08-01 10:57:48 +08:00
超级老白兔
efec6d6857 FEAT => 更新测试页面URL,移除不再使用的postMessage测试组件,优化项目配置,添加Capacitor命令到package.json。 2025-07-31 18:44:45 +08:00
超级老白兔
ecddbf2226 FEAT => 本次更新项目为: 2025-07-31 18:26:23 +08:00
超级老白兔
031558c483 FEAT => 添加iframe返回键处理功能,优化返回键事件管理,更新样式以改善用户体验。 2025-07-31 18:25:26 +08:00
超级老白兔
02d0031079 FEAT => 本次更新项目为:
存一半
2025-07-31 18:18:01 +08:00
超级老白兔
101676cef3 REFactor => 移除安全区域相关功能,优化web-view组件样式,更新导航组件结构,简化代码逻辑。 2025-07-31 17:58:25 +08:00
超级老白兔
9193bda39f FEAT => 本次更新项目为:
初始化appbuild
2025-07-31 17:41:43 +08:00
超级老白兔
c5d4e80e7c REFactor => 移除安全区域相关功能和测试页面,简化导航组件,优化主入口文件结构。 2025-07-31 16:38:35 +08:00
超级老白兔
599d67141f FEAT => 更新项目配置和组件,添加安全区域支持,优化导航栏,移除不必要的组件测试页面。 2025-07-31 16:30:34 +08:00
超级老白兔
529e5d3b83 Merge branch 'yongpxu-dev' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into yongpxu-dev 2025-07-30 17:36:18 +08:00
超级老白兔
68fe9946d2 FEAT => 更新场景列表页面,添加分页功能并优化卡片点击事件处理,移除不必要的加载更多按钮。 2025-07-30 17:36:15 +08:00
超级老白兔
f4df55b9e1 Merge branch 'yongpxu-dev' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into yongpxu-dev 2025-07-30 17:31:58 +08:00
超级老白兔
3c4d0fcb5f FEAT => 本次更新项目为:
用户详情搞定
2025-07-30 17:29:15 +08:00
超级老白兔
1c154c6f48 FEAT => 本次更新项目为:设置中心构建完成 2025-07-30 17:08:31 +08:00
超级老白兔
c4b41daf6a FEAT => 本次更新项目为:
设置的构建完成
2025-07-30 17:01:21 +08:00
超级老白兔
2577fd81eb FEAT => 本次更新项目为:
设置页面构建完成
2025-07-30 16:58:29 +08:00
超级老白兔
75987baef8 FEAT => 本次更新项目为:
废弃流量池不需要的接口
2025-07-30 16:36:21 +08:00
超级老白兔
e3ccf61fd2 FEAT => 本次更新项目为:
bu修复
2025-07-30 15:37:13 +08:00
超级老白兔
f430718e5f Merge branch 'yongpxu-dev' into yongpxu-dev2
# Conflicts:
#	nkebao/src/pages/mobile/mine/devices/DeviceDetail.tsx   resolved by yongpxu-dev2 version
2025-07-30 15:29:22 +08:00
超级老白兔
9815134536 FEAT => 本次更新项目为:
存一下
2025-07-30 15:19:15 +08:00
超级老白兔
78e2105a62 FEAT => 更新设备绑定引导页逻辑,新增设备列表接口调用以优化设备数量获取,提升用户体验。 2025-07-30 15:19:02 +08:00
超级老白兔
c78134f05c FEAT => 更新设备绑定引导页逻辑,优化设备数量获取方式,使用用户状态中的设备总数替代API调用,提升性能和用户体验。 2025-07-30 14:57:03 +08:00
超级老白兔
0693f5de74 FEAT => 本次更新项目为:
存了
2025-07-30 14:42:22 +08:00
超级老白兔
f78af9e77c FEAT => 本次更新项目为:
设备绑定指引
2025-07-30 12:52:58 +08:00
超级老白兔
6638e24901 FEAT => 本次更新项目为:
-设备管理-设备详情-关联账户-微信号详情(微信号管理的详情)✔
2025-07-30 11:02:44 +08:00
超级老白兔
4a4e9a611f FEAT => 本次更新项目为:
存客宝设备绑定引导页
2025-07-30 10:40:15 +08:00
超级老白兔
51495a93ff Merge branch 'yongpxu-dev' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into yongpxu-dev
# Conflicts:
#	nkebao/src/pages/mobile/mine/devices/index.tsx   resolved by origin/yongpxu-dev(远端) version
2025-07-30 09:32:33 +08:00
超级老白兔
0059c3ba3b fix: Add missing commas in various TypeScript files to ensure proper syntax and prevent potential runtime errors. 2025-07-30 09:29:32 +08:00
超级老白兔
035c0602b5 FEAT => 本次更新项目为:设备类别构建完成 2025-07-29 19:51:56 +08:00
超级老白兔
e4b1cf4a15 FEAT => 本次更新项目为:
-设备扫描二维码弹窗优化✔
2025-07-29 19:49:09 +08:00
超级老白兔
0941124ab6 Merge branch 'yongpxu-dev' into yongpxu-dev4
# Conflicts:
#	nkebao/vite.config.ts   resolved by yongpxu-dev version
2025-07-29 19:31:35 +08:00
超级老白兔
89a7c12fa0 feat: 本次更新项目为: 2025-07-29 19:31:23 +08:00
超级老白兔
2da76dfcff Merge branch 'yongpxu-dev' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into yongpxu-dev 2025-07-29 19:29:28 +08:00
超级老白兔
62ceca1d34 Merge branch 'yongpxu-dev' into yongxu-dev3
# Conflicts:
#	nkebao/vite.config.ts   resolved by yongpxu-dev version
2025-07-29 19:29:20 +08:00
超级老白兔
dd2db73e9d FEAT => 本次更新项目为:
2025-07-29 19:29:07 +08:00
超级老白兔
2824e6582c Merge branch 'yongpxu-dev2' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into yongpxu-dev2
# Conflicts:
#	nkebao/vite.config.ts   resolved by origin/yongpxu-dev2(远端) version
2025-07-29 19:28:00 +08:00
超级老白兔
d2d9e53655 FEAT => 本次更新项目为:
村里
2025-07-29 19:27:48 +08:00
超级老白兔
54b84f8bf6 FEAT => 本次更新项目为:
存了
2025-07-29 19:27:24 +08:00
超级老白兔
e45344a1c3 Revert " feat: 本次更新项目为:"
This reverts commit 3995ef55e1.
2025-07-29 19:26:39 +08:00
超级老白兔
2b978997c7 Merge branch 'yongpxu-dev2' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into yongpxu-dev2
# Conflicts:
#	nkebao/.env.development   resolved by yongpxu-dev2 version
#	nkebao/src/pages/mobile/mine/traffic-pool/detail/data.ts   resolved by yongpxu-dev2 version
2025-07-29 19:25:12 +08:00
超级老白兔
834ccea786 Merge branch 'yongpxu-dev' into yongpxu-dev2
# Conflicts:
#	nkebao/src/pages/mobile/mine/traffic-pool/detail/api.ts   resolved by yongpxu-dev version
#	nkebao/src/pages/mobile/mine/traffic-pool/detail/data.ts   resolved by yongpxu-dev version
#	nkebao/src/pages/mobile/mine/traffic-pool/detail/index.tsx   resolved by yongpxu-dev version
2025-07-29 19:24:44 +08:00
超级老白兔
13a940b8c2 Merge branch 'yongpxu-dev' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into yongpxu-dev 2025-07-29 19:23:03 +08:00
超级老白兔
a4f63f0d70 Merge branch 'yongpxu-dev' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into yongpxu-dev 2025-07-29 19:22:55 +08:00
超级老白兔
3995ef55e1 feat: 本次更新项目为:
同步
2025-07-29 19:22:49 +08:00
超级老白兔
92fa2975c2 FEAT => 本次更新项目为:
同步
2025-07-29 19:22:42 +08:00
超级老白兔
ea2336fe1e FEAT => 本次更新项目为:
同步
2025-07-29 19:22:32 +08:00
超级老白兔
d9f2e134b1 存了 2025-07-29 19:20:45 +08:00
超级老白兔
922d1357fa FEAT => 本次更新项目为:
临时存了
2025-07-29 19:19:28 +08:00
超级老白兔
c1cc74c2f3 FEAT => 本次更新项目为: 2025-07-29 19:14:56 +08:00
超级老白兔
dce6910371 FEAT => 本次更新项目为:
用户详情
2025-07-29 19:08:50 +08:00
超级老白兔
10dd9869d4 feat: 添加设备绑定引导页面及相关逻辑,更新路由配置 2025-07-29 18:42:04 +08:00
超级老白兔
600e2a52ad feat: 更新构建脚本,添加类型检查命令并优化Vite配置,调整chunk大小限制和文件命名格式 2025-07-29 17:29:39 +08:00
超级老白兔
dcb1c95215 FEAT => 本次更新项目为:语法校验即可 2025-07-29 17:06:18 +08:00
超级老白兔
1c1062b235 FEAT => 本次更新项目为:
素材列表、编辑、新增全部完成
2025-07-29 16:38:48 +08:00
超级老白兔
d1d8391e1d feat: 添加素材列表卡片标题显示 2025-07-29 16:13:12 +08:00
超级老白兔
63df76bf01 feat: 更新素材表单组件,优化状态管理和数据获取逻辑,移除不必要的状态,使用useCallback提升性能 2025-07-29 16:11:17 +08:00
超级老白兔
d277cd6518 feat: 本次更新项目为:
上传组件封装完成
2025-07-29 16:06:37 +08:00
超级老白兔
8a06e6504d feat: 本次更新项目为:
样式做的很好
2025-07-29 14:59:10 +08:00
超级老白兔
f6beb38502 feat: 本次更新项目为:
扫码获取二维码添加设备
2025-07-29 14:26:52 +08:00
超级老白兔
da11868d48 2025-07-29 11:40:47 +08:00
超级老白兔
7e9b8e2341 Merge branch 'yongpxu-dev' into yongpxu-dev2
# Conflicts:
#	nkebao/src/pages/mobile/mine/traffic-pool/detail/api.ts   resolved by yongpxu-dev2 version
#	nkebao/src/pages/mobile/mine/traffic-pool/detail/data.ts   resolved by yongpxu-dev2 version
#	nkebao/src/pages/mobile/mine/traffic-pool/detail/index.tsx   resolved by yongpxu-dev2 version
2025-07-29 11:40:31 +08:00
超级老白兔
8d2dcad9ca Merge branch 'yongpxu-dev' into yongxu-dev3
# Conflicts:
#	nkebao/src/pages/mobile/mine/wechat-accounts/list/api.ts   resolved by yongxu-dev3 version
2025-07-29 11:40:23 +08:00
超级老白兔
6d768f3a97 feat: 本次更新项目为:
语法格式化
2025-07-29 11:35:04 +08:00
笔记本里的永平
3a444a29b9 存进度 2025-07-29 10:07:09 +08:00
笔记本里的永平
232d4834be feat: 本次提交更新内容如下
存储进度
2025-07-29 10:06:40 +08:00
笔记本里的永平
e1ed0e5dff feat: 本次提交更新内容如下
eslint规则校验
2025-07-28 17:18:03 +08:00
笔记本里的永平
a9306bb8ba feat: 本次提交更新内容如下
格式更新一下
2025-07-28 16:53:18 +08:00
笔记本里的永平
9facfe2ff8 feat: 本次提交更新内容如下
接口对接成功
2025-07-28 14:43:49 +08:00
笔记本里的永平
c8742de888 feat: 本次提交更新内容如下
-流量分发新建\编辑✔
-内容库新建\编辑✔
-隐藏Ai策略优化✔
-隐藏Ai销售预测✔
2025-07-28 11:42:54 +08:00
笔记本里的永平
14e397ab26 feat: 本次提交更新内容如下
1、在线微信号传参构建完成
2025-07-28 11:13:57 +08:00
笔记本里的永平
cbc8c4a501 feat: 本次提交更新内容如下
项目分包优化
2025-07-25 18:08:16 +08:00
笔记本里的永平
ed5ff02b91 feat: 本次提交更新内容如下 2025-07-25 18:04:45 +08:00
笔记本里的永平
4608e23b60 feat: 本次提交更新内容如下
自动化部署脚本构建完成
2025-07-25 18:04:40 +08:00
笔记本里的永平
0cee80e6d7 feat: 本次提交更新内容如下
优化存一版
2025-07-25 18:03:00 +08:00
笔记本里的永平
2234fdf172 feat: 本次提交更新内容如下
脚本构建完成了
2025-07-25 16:57:19 +08:00
笔记本里的永平
a243e66746 feat: 本次提交更新内容如下
layout兼容问题
2025-07-25 16:15:52 +08:00
cea4a01a4a Initial Commit 2025-04-14 16:40:39 +08:00
4181 changed files with 858301 additions and 223540 deletions

View File

@@ -1,9 +0,0 @@
Server/runtime/
*.png
*.jpg
*.jpeg
*.gif
*.bmp
*.webp
*.ico
*.svg

16
.codegraph/.gitignore vendored Normal file
View File

@@ -0,0 +1,16 @@
# CodeGraph data files
# These are local to each machine and should not be committed
# Database
*.db
*.db-wal
*.db-shm
# Cache
cache/
# Logs
*.log
# Hook markers
.dirty

6
.codegraph/daemon.pid Normal file
View File

@@ -0,0 +1,6 @@
{
"pid": 74205,
"version": "0.9.9",
"socketPath": "/Users/karuo/Documents/开发/2、私域银行/cunkebao_v3/.codegraph/daemon.sock",
"startedAt": 1780473089935
}

View File

@@ -0,0 +1,21 @@
---
description: CodeGraph F21a — 经卡若AI 全局 MCP 解析代码,不依赖本仓科室配置
alwaysApply: true
---
<!-- CODEGRAPH_START · 卡若AI F21a 引用 -->
## CodeGraph全局 · 卡若AI F21a
结构读/写代码 **只走卡若AI F21a**MCP 已在 `~/.cursor/mcp.json` 由 `codegraph_mcp_serve.sh` 启动。
**真源**`/Users/karuo/Documents/个人/卡若AI/04_卡火/火种_知识模型/CodeGraph_MCP语义代码图谱/SKILL.md`
| 场景 | 工具 |
|:---|:---|
| 任务上下文 | `codegraph_context` → `codegraph_explore` |
| 找符号 | `codegraph_search` |
| 调用链 | `codegraph_callers` / `codegraph_callees` |
| 改 API 影响 | `codegraph_impact` |
无 `.codegraph/``bash …/CodeGraph_MCP语义代码图谱/脚本/codegraph_init_repo.sh "$(pwd)"`
**勿**在本仓新增 `.cursor/mcp.json` 的 codegraph 块。
<!-- CODEGRAPH_END -->

View File

@@ -0,0 +1,40 @@
---
description: 存客宝 v3 部署——Agent 自主执行、自主修错、验收至首页可访问(覆盖「部署失败再问用户」)
alwaysApply: true
---
# 存客宝 v3 · 部署自主处理
> **生效**2026-05-26。用户说「部署」时**不得**只跑脚本报成功就结束;必须验收到**首页有内容**。
## 强制流程
1. **执行**`bash 开发文档/8、部署/scripts/deploy_local_docker.sh --build`(改前端/首次/用户抱怨空白页时必须 `--build`)。
2. **脚本失败时 Agent 自行排查修复**,不要停下来问用户,常见顺序:
- Docker 未响应 → `open -a Docker`,等就绪后重试
- 全量 `--build` 中 `server` 镜像失败composer/php 拉取)→ 改为 `docker compose build cunkebao-web touchkebao-web superadmin-web && docker compose up -d`
- `vendor` 缺失 → 重建 `server` 或 `docker compose up -d --build server`
- MySQL 未 healthy → `COMPOSE_PROFILES=embedded-mysql docker compose up -d mysql`,确认 3308
- 3100 HTTP 200 但首页空白 → 查 `pageerror`(如 `homeIcons` 缺 import、`PermissionRoute` hydration、index.html 缓存;修代码后 **必须 rebuild cunkebao-web**
3. **验收(全绿才算部署成功)**
- HTTP3100 / 3101 = 200
- API登录 code 200经 `/api` 与 8082 任一)
- **首页 smoke**`node 开发文档/8、部署/scripts/verify_home_smoke.mjs` → 页面含「设备数量」「场景获客」,`pageerror` 为空
- `index.html` 响应头含 `Cache-Control: no-cache`
4. **失败则循环**:修 → rebuild 前端 → 再 deploy → 再 smoke直到通过或明确环境级阻塞仅此时向用户说明
## 禁止
- 仅 `curl` 首页 200 就宣称部署成功
- 部署失败后只给用户命令让用户自己跑
- 未 rebuild 就声称「已更新前端」
## 脚本与文档
- 本地一键:`开发文档/8、部署/scripts/deploy_local_docker.sh`
- 首页 smoke`开发文档/8、部署/scripts/verify_home_smoke.mjs`
- 腾讯云:`开发文档/8、部署/scripts/deploy_tencent_baota.sh`(用户明确要求线上时)
## 与「仅本地不 push」关系
部署修错可改本地代码与 `开发文档/`**不自动 git push**(见 `cunkebao-local-only-no-github.mdc`),除非用户本轮明确说提交/推送。

View File

@@ -0,0 +1,28 @@
---
description: 四端只 HTTP 对接、不串仓硬性铁律。改触客宝/AI数智员工/存客宝/超管任意一端时强制遵守。
globs:
- "**/*"
alwaysApply: true
---
# 四端对接铁律 · 只 HTTP · 不串仓
> 真源:`开发文档/2、架构/四端对接铁律_只HTTP不串仓.md`
## 强制
1. **各端各自仓库、各自职责** — 存客宝(3100) · 触客宝(3101) · AI数智员工(3104) · SuperAdmin(3103) **不互相改业务代码**。
2. **协作只走 HTTP API** — JWT `/v1/*` · Open `X-API-KEY` · 禁止跨项目 import 源码、禁止直连 MySQL/Mongo。
3. **数据范围** — 请求必须带当前项目 `companyId`;禁止跨项目读写。
4. **新能力** — 先更 `开发文档/5、接口/` 索引,再改 `route.php`,最后写 UI。
5. **工作手机/神射手** — 只经存客宝 BFF 或 documented HTTPAI数智员工 **禁止** `/v1/kefu/*`。
## 禁止
- 在触客宝/AI数智员工仓修改 `Server/application/cunkebao`(除非用户本轮明确「改存客宝 Server」
- copy 兄弟项目 React 业务组件进本仓(设计 token 可对齐)
- 触客宝直连 `traffic_pool` 表
## 接口索引
`开发文档/5、接口/SuperAdmin_四端对接接口索引.md` · `02-存客宝对接/触客宝PC接口对接索引.md`

View File

@@ -0,0 +1,89 @@
---
description: 存客宝 v3 · 卡若AI 对话强制规则(复盘五块 + 本轮文档可打开路径)
alwaysApply: true
---
# 存客宝 v3 · 卡若AI 对话强制规则
> **生效**2026-05-31 · 优先级:与本仓 `cunkebao-v3-devdoc-first.mdc`、`cunkebao-local-only-no-github.mdc` 并列。
## 一、身份与启动(每轮)
1. **读卡若AI**`/Users/karuo/Documents/个人/卡若AI/BOOTSTRAP.md` → `SKILL_REGISTRY.md`(热技能命中则读对应 `SKILL.md`)。
2. **回廊洗字**`运营中枢/参考资料/卡若闽南口音_ASR纠错库.json`(卡路/卡洛/卡罗拉→**卡若**)。
3. **执行前公示**(默认):正文最前 **理解 ≤200 字** + **本次要做** 有序列表;用户说「直接做」可跳过列表,**复盘仍必须**。
4. **存客宝开发**:叠加 `开发文档/开发铁律_先读文档再编码.md` Phase 0。
## 二、对话结束 · 强制复盘(卡若 v5/v6
**每条回复的最后一块**必须是完整 **[卡若复盘]YYYY-MM-DD HH:mm**,真源:
`卡若AI/运营中枢/参考资料/卡若复盘格式_固定规则.md` · 速查 `04_卡火/火炬_全栈消息/卡若复盘格式/SKILL.md`F01d
| 顺序 | 块 |
|:---:|:---|
| 1 | **🎯 目标·结果·达成率**(单行 ≤50 字,含 **0100%** |
| 2 | **📌 过程**1、2、3 分点,可验收数据) |
| 3 | **💡 反思**13 点) |
| 4 | **📝 总结** |
| 5 | **▶ 下一步执行** |
复盘块内**禁止表格**;禁止省略复盘或写「见上文」。
## 三、复盘之后 · 本轮文档Cursor 可点 · 强制)
复盘五块写完后、回复结束前,追加 **📎 本轮文档****只列本轮对话新建/修改的文件**,通常 13 个;**禁止**贴历史轮次、索引 README、无关专文
### 3.1 Cursor 聊天内(默认 · 必用)
**禁止**在 Cursor 复盘末尾使用下列形式(点击无反应):
- `obsidian://open?...`
- `file://` / `vscode://`
- 一长串与本轮无关的 MD 链接列表
**必须**使用 **代码引用块**(点块内路径可在 Cursor 打开文件)+ **一行绝对路径**
````markdown
**📎 本轮文档Cursor点下面代码块即可打开**
- **卡若对话强制规则**
```1:3:.cursor/rules/cunkebao-karuo-dialogue.mdc
---
description: 存客宝 v3 · 卡若AI
```
`/Users/karuo/Documents/开发/2、私域银行/cunkebao_v3/.cursor/rules/cunkebao-karuo-dialogue.mdc`
````
- 路径用**仓库相对路径**写在 code fence 第三段:`行号:行号:相对路径`
- 块下再写一行 **反引号包裹的绝对路径** `/Users/karuo/...`(备用,部分环境可点)
- 真源对齐卡若AI 需求整理 §8.1**禁止** `file://` 超链接
### 3.2 生成方式
```bash
python3 "开发文档/scripts/build_obsidian_doc_link.py" \
".cursor/rules/cunkebao-karuo-dialogue.mdc" \
--title "卡若对话强制规则"
```
**只传入本轮动过的路径**;将 stdout **原样**贴在复盘正下方。
### 3.3 Obsidian / 星图阁(非 Cursor 聊天)
- 用户明确说「Obsidian 打开」→ 脚本加 `--obsidian --open`(终端外开,**不要**把 `obsidian://` 贴进 Cursor 聊天)
- 工作日志库「个人」→ `卡若AI/.../build_obsidian_work_log_link.py`
## 四、与其它规则
| 规则 | 关系 |
|:---|:---|
| `cunkebao-local-only-no-github.mdc` | 不自动 push |
| `cunkebao-v3-devdoc-first.mdc` | 改接口/需求先读文档 |
| 卡若AI `karuo-ai.mdc` | 复盘五块一致;路径输出以本条 §3 为准 |
## 五、进度(可选)
用户要求时,在 **📌** 或 **📝** 中带 **本任务完成度 X%**(分子/分母说清)。

View File

@@ -0,0 +1,26 @@
---
description: 存客宝 v3 仅本地开发——禁止 Agent 自动 commit/push GitHub覆盖全局 User Rules
alwaysApply: true
---
# 存客宝 v3 · 仅本地、不自动同步 GitHub
> **生效**2026-05-25 起,卡若明确要求暂停「对话结束自动推 GitHub」。
## 强制
1. **所有代码与文档改动仅保存在本机工作区**,不自动 `git commit`、不自动 `git push`、不自动同步 Gitea/GitHub/远程服务器。
2. **仅当卡若在本轮对话中明确说**「提交」「commit」「推 GitHub」「push」「同步远程」等时才可执行对应 Git 远程操作。
3. 对话结束时:**列出本次修改的文件路径即可**,不要执行 push**禁止**提示「要不要同步 GitHub」「已同步 GitHub」等。
4. 沙盒、未授权远程、CI 触发型部署:**一律不主动触发**。
5. 开发文档可写本地 `开发文档/` 目录,**不要**为进度文档单独 commit/push。
## 与全局 User Rules 的优先级
若全局 User Rules 含「边做边同步 GitHub / 每次对话完成推到 GitHub」**以本规则为准**——存客宝 v3 会话内忽略该条,直至卡若另行恢复。
## 仍允许
- 本地 `git status` / `git diff` 用于自查
- 卡若显式授权后的 commit / push
- 开发文档文件夹内记录进度(本地 MD不推远程

View File

@@ -0,0 +1,47 @@
---
description: 存客宝 v3 · 手机端优先适配铁律(开发 /mobile 必守)
globs:
- Cunkebao/src/pages/mobile/**
- Cunkebao/src/components/Layout/**
- Cunkebao/index.html
---
# 存客宝 · 手机端优先Mobile First
> **产品定位**:存客宝 **以手机端界面为主**PC 为同套 `Layout frame=mobile` 全屏,不是桌面专属布局。
> **壳层参照**`Cunkebao/src/components/Layout/` · `Cunkebao/index.html` · 触客宝 `.cursor/skills/触客宝手机端前端壳/SKILL.md`(键盘/100dvh 同源)
## 一、结构(强制)
| 层级 | 规则 |
|:---|:---|
| 页面壳 | 用 `Layout``header` 顶 · `main` **唯一纵向滚动** · `footer` 底 |
| 多步表单 | **`embedded`**:底栏内嵌在 flex 底,**禁止**只靠 Portal 让用户上拉找「下一步」 |
| 内容区 | **禁止** `min-height: 100vh` / `padding-bottom: 100px` 撑高;滚动只发生在 `Layout.main` |
## 二、选项与排版(禁止挤压)
| 场景 | 做法 |
|:---|:---|
| ≤3 个短选项 | `radioGroupSegment` 等分横排 |
| ≥4 个或文案较长 | `radioGroupWrap` **两列换行**,禁止 `radioGroupHorizontal` 塞 5 项导致**竖排单字** |
| 长说明 | 副文案单独一行(`radioDesc`),不挤在 Radio 同一窄列 |
| 触控 | 可点区域高度 **≥ 44px**;字号 **≥ 14px**(输入 **≥ 16px** 防 iOS 放大) |
## 三、底栏按钮(下一步 / 提交)
- 固定在视口**最下**`embedded` 的 `inlineFooter` 或 `TabBarPortal`
- 主按钮 **block / flex:1**,与「上一步」并排时 `footer-btn-group`
- 主体 `main` 预留 `padding-bottom` ≥ 底栏高 + safe-area`Layout` 已处理 `embedded` 时勿重复加大留白)
## 四、新页 / 改页 Checklist
- [ ] 已读本条 + Layout 源码
- [ ] 无 `100vh` 撑高 body
- [ ] 多选项未竖排挤字
- [ ] 底栏无需上拉即可点「下一步」
- [ ] 真机或 375px 模拟器目视
## 五、需求落盘
手机适配类需求写入 `开发文档/1、需求/修改/存客宝_进行中_{YYYYMMDD}.md`REQ-CKB-11x验收**375px 宽无挤字、底栏固定可见**。

View File

@@ -0,0 +1,43 @@
---
description: 存客宝 v3 · 读需求表必先看图(强制)
globs:
- "开发文档/1、需求/**/*.md"
alwaysApply: false
---
# 存客宝 v3 · 读需求表必先看图
> **生效**2026-05-26 起。卡若明确要求:每次读 `开发文档/1、需求/修改/存-*.md` 或类似需求 MD 时,**必须**确认能看到 MD 引用的所有图片,再开始开发。
## 强制流程(开发前 Phase 0
1. **先读需求 MD**`Read` 工具读完整文件)。
2. **列出所有 `![](images/xxx.png)` 引用**(用 `Grep` 模式 `../../10、项目管理/images/[^)]+\.png`)。
3. **`ls images/`** 确认本地文件均存在;若缺图必须在对话里**明确报告缺失清单**,不要假装能看到。
4. **逐张 `Read` 关键截图**(至少所有 P0/P1 模块对应的截图与「参考样式」「标注」类图片。Read 工具支持 PNG/JPG/GIF/WebP会作为图像加载到上下文。
5. **复述**截图中看到的元素(按钮文字、颜色、布局、标注红框),与 MD 文字描述对照,若有冲突以**截图为准**并在回复中标注。
6. **图片缺失或读取失败时**:先用 `GenerateImage` 不可、改用 `WebFetch` 也不可。立刻向用户报告,**不**继续编码。
## 禁止
- 仅凭 MD 文字描述就开始编码,跳过截图。
- 用 `cat`/`head` 读 PNG无效
- 复述 MD 时把截图当作文字一并描述但实际未 Read伪装看图
## 适用范围
- `开发文档/1、需求/修改/*.md`
- `开发文档/1、需求/草稿/*.md`
- 任何外部传入的 PDF 需求(先 `Read` PDF再 `Read` 拆出的 PNG 页)
## 复盘要求
每次按本规则开发后,在复盘里说明:
- 本轮 Read 了哪些 PNG路径
- 截图中验证/纠正了哪些 MD 文字描述
- 是否有图片缺失或读取失败
## 与其他规则关系
- 与 `cunkebao-v3-devdoc-first.mdc` 协同:先读 1、需求图 → 再读 5、接口/2、架构 → 再编码。
- 与 `cunkebao-local-only-no-github.mdc` 不冲突:仍只本地,不自动推。

View File

@@ -0,0 +1,54 @@
---
description: 存客宝 v3 开发铁律——先读开发文档再编码,迭代结束回写文档(含 5、接口
alwaysApply: true
---
# 存客宝 v3 · 开发文档先行
本项目代码变更**必须**遵循 `开发文档/开发铁律_先读文档再编码.md`。
## 动手前Phase 0
1. 读 `开发文档/开发铁律_先读文档再编码.md` 的 Phase 0 表,按任务类型读完对应 MD
2. **目录/需求维护** 必读 `开发文档/开发文档目录结构铁律.md`(十大目录 · modify 收口 · 已完成进 10、项目管理
2. **接口/BFF/对接** 必读:
- `开发文档/5、接口/README.md`
- `开发文档/5、接口/07-接口分类总览Obsidian配色.md`
- `开发文档/5、接口/存客宝API全量索引.md`
- 涉及 workphone 时再读 `5、接口/02-存客宝与工作手机对接/` 下 05/06/03
3. **架构/新模块** 必读 `开发文档/2、架构/README.md` → `系统架构.md`
4. 对话中先列出「本次已读文档路径」≤5 条),再改代码
## 开发中Phase 1
- 路由真源:`Server/application/*/config/route.php`
- 不改原有业务 URL设备写操作走 `/v1/workphone/*`
- 对照 `5、接口/02-存客宝与工作手机对接/06-业务接口与工作手机一对一映射总表.md`
- **边开发边标注**:只改 `开发文档/1、需求/修改/{端}_进行中_{YYYYMMDD}.md`(⬜→🟡→✅);**禁止**同日另开 `{端}_{主题}_{date}.md`;关单节迁入 `开发文档/10、项目管理/已完成/`
- **需求落盘 Skill**`.cursor/skills/需求协调与归档/SKILL.md` §九(一项目一文件)
### ⛳ 强制打勾铁律(卡若 2026-05-31 · 最高优先)
> **每完成一项,立即在开发文档对应行打 ✅** —— 不攒批、不等收尾、不口头代替。
1. **触发即更新**:任一需求/子项验收通过的**当下**,立刻把进行中 MD 对应行 `⬜/🟡 → ✅`,并在验收列补**可验收证据**HTTP 码 / 条数 / 脚本 exit / 截图路径)。
2. **粒度到子项**:表格每行、清单每条 `- [ ] → - [x]`;一个功能含多子项的,完成几项打几个勾。
3. **顺序固定**`改代码 → 验证 → 立即打勾 ✅ + 证据 → 再继续下一项`;严禁"全部做完再统一打勾"。
4. **复盘对齐**:复盘 📌 引用的完成项,必须与 MD 中已 ✅ 的行一致;不一致以 MD 为准并补勾。
5. **跨文档同步**:接口变更同步打勾 `开发进度_接口文档.md`;其它相关文档同样"完成一项打一勾"。
6. **未完成保留**:⬜/🟡/🔒 原样留下,不删行;整节全 ✅ 才迁 `10、项目管理/已完成/` 并留索引行。
## 收尾Phase 2
- 路由变更 → 同步 `5、接口/存客宝API全量索引.md`
- BFF/SDK 变更 → 同步 02-对接目录 + `开发文档/开发进度_接口文档.md`
- 新增 MD → 运行 `开发文档/scripts/generate_doc_header_banners.py`
- 复盘须写:读了哪些文档、更新了哪些文档
## Skill
| Skill | 路径 |
|:---|:---|
| 卡若AI 开发文档先行 | `01_卡资/金盾_数据安全/存客宝/开发文档先行/SKILL.md` |
| 需求整理 W05b | `.cursor/skills/开发需求文档整理/SKILL.md` |
| 需求协调 W05c | `.cursor/skills/需求协调与归档/SKILL.md` |

View File

@@ -0,0 +1,122 @@
---
name: cunkebao-architecture-five-diagrams
description: >-
存客宝 v3 项目架构五图:对话内 GenerateImage 生成高级架构/流程图,写入
开发文档/2、架构/images/architecture-overview/,并维护 08-项目架构五图读懂指南.md。
触发:架构五图、五图读懂、项目架构图、生成架构图、一图读懂存客宝、
PM架构图、不用Python生成图片、架构 infographic。禁止用 Pillow/Python 画架构总览图。
---
# 存客宝 · 架构五图(对话内 AI 出图)
> **铁律**:架构总览 / 业务流程 / 板块关系图 → **只用 Cursor `GenerateImage`** 在对话里生成;**禁止**用 `generate_doc_header_banners.py` 或 Pillow 替代。
> **文档说明小头图**(单篇 MD 顶栏)仍走 F09a「开发文档说明头图」Skill。
---
## 一、何时触发
- 用户要「几张图看懂整个项目」「给 PM 的架构图」「五图读懂」
- 存客宝大版本架构变更后需更新总览图
- 用户明确说「不要用 Python 生成图片」
---
## 二、五图标准套装(固定 5 张)
| 序号 | 文件名 | 标题(中文) | 内容要点 |
|:----:|--------|-------------|----------|
| 1 | `01-系统四层架构总览.png` | 存客宝 v3 · 系统四层架构总览 | 客户端层 → Nginx → Server/Moncter/WebSocket → MongoDB/MySQL/Redis + 工作手机 SDK |
| 2 | `02-核心业务数据流.png` | 存客宝 · 核心业务数据流 | 工作手机 ↔ SDK ↔ ThinkPHP ↔ 三库WebSocket ↔ React 前端 |
| 3 | `03-六大业务板块关系.png` | 存客宝 · 六大业务板块 | 场景获客/工作台/设备/微信号/流量池/内容库 → 中心 BFFroute.php 真源 |
| 4 | `04-场景获客与好友添加流程.png` | 场景获客 + 好友添加 · 完整流程 | 建计划→task_customer→Workerman→健康分→SDK→状态0~5→Webhook |
| 5 | `05-定时任务与自动化调度.png` | 存客宝 · 定时任务与自动化调度 | task_scheduler 高/中/低频任务Command→Queue→Job |
**输出目录**(项目内):
```
开发文档/2、架构/images/architecture-overview/
```
**配套文档**(必须同步更新或新建):
```
开发文档/2、架构/08-项目架构五图读懂指南.md
```
---
## 三、GenerateImage 提示词规范
### 视觉风格(统一)
- 比例:**16:9 横版**
- 风格:**dark glassmorphism**,深蓝/紫渐变底,毛玻璃卡片,细线箭头
- 字体:**中文为主**大号标题PM 可读
- 质量Keynote / 企业 SaaS 发布会级别,**不要**代码截图、不要 ASCII
### 每张图调用模板
```
Professional enterprise architecture infographic, 16:9 landscape,
premium dark glassmorphism, subtle blue-purple gradient.
Chinese title: "{标题}".
{具体内容 bullet英文描述布局即可}
Clean sans-serif Chinese typography, Apple keynote quality, footer: CKB v3
```
### 并行生成
5 张图 **同一轮对话并行** 调用 5 次 `GenerateImage`,不要串行等待。
### 落盘
GenerateImage 默认写到 Cursor assets**必须 cp 到**
```bash
开发文档/2、架构/images/architecture-overview/
```
(用 Shell 复制,不要用 Python 画图。)
---
## 四、文档维护 checklist
生成图片后按序执行:
1. [ ] 5 张 PNG 已在 `architecture-overview/`
2. [ ] `08-项目架构五图读懂指南.md` 内 5 处 `![...](images/architecture-overview/...)`
3. [ ] `2、架构/README.md` 阅读顺序增加「08-五图读懂」
4. [ ] `5、接口/开发进度_接口文档.md` 或架构变更记录加一行
5. [ ] **不**运行 `generate_doc_header_banners.py` 为这五张图生成 doc-header
---
## 五、内容真源(生成前必读)
| 主题 | 文档 / 代码 |
|------|-------------|
| 四层架构 | `开发文档/2、架构/系统架构.md` |
| 数据流 | `开发文档/2、架构/核心业务链路.md` |
| 六板块 | `开发文档/5、接口/03-业务板块接口/README.md` |
| 获客加友 | `开发文档/2、架构/07-业务板块核心算法.md` |
| 定时任务 | `开发文档/2、架构/自动化工作流.md` · `Server/config/task_scheduler.php` |
---
## 六、与 F09a 头图 Skill 的分工
| 类型 | 工具 | Skill |
|------|------|-------|
| 单篇 MD 顶栏小 banner | GenerateImage首选 | F09a 开发文档说明头图 |
| **项目级架构五图** | GenerateImage强制 | **本 Skill** |
| 离线无 AI 的小色条 | Pillow fallback | F09a 仅 fallback不用于五图 |
---
## 七、变更记录
| 版本 | 日期 | 说明 |
|------|------|------|
| 1.0 | 2026-05-25 | 首版;替代 Python 批量画架构总览 |

View File

@@ -0,0 +1,193 @@
---
name: 存客宝官网开发
description: 存客宝对外官网(/官网/需求、SEO/GEO、产品矩阵截图、留资单接口、超管接口文档第9类。触发存客宝官网、官网开发、官网SEO、产品矩阵、官网留资、对外接口、官网需求、GEO、私域官网。
triggers: 存客宝官网,官网开发,官网SEO,产品矩阵,官网留资,对外接口,GEO,私域官网,触客宝官网
owner: 火炬
group: 火
version: "1.0"
updated: "2026-05-31"
---
# 存客宝官网开发 SkillF01e · 项目内)
> **代码**`/Users/karuo/Documents/开发/2、私域银行/cunkebao_v3/官网/`(静态站 · 零构建)
> **当日需求真源(唯一)**`开发文档/1、需求/修改/官网_进行中_{YYYYMMDD}.md` — **新需求只追加此文件章节**,禁止再开 `官网_{主题}_*.md` 于 `修改/`
> **鉴定/索引**`开发文档/1、需求/官网/0013` 为已整理专文,**非**当日追加口(见 [需求协调与归档](../需求协调与归档/SKILL.md) §九)
> **对话规则**`.cursor/rules/cunkebao-karuo-dialogue.mdc`(卡若复盘 + 本轮文档代码块)
---
## 一、何时启用
| 触发 | 动作 |
|:---|:---|
| 改官网页面/样式/留资 | 读 **`官网_进行中_{date}.md`**REQ/E 序)· 专文仅作参考 → 改 `官网/`**回勾进行中(⬜→🟡→✅)**`03-研发迭代日志` |
| 运营后台/访客/Webhook | 读 `_进行中` **§ REQ-WEB-60** → `scripts/analytics-store.mjs` + `admin/` → 子项打勾 |
| **AI 客服对外(新)** | **先读 14 专文 §十一** · **勿抢先写代码** · Server `official-ai/chat` → 再官网挂件 |
| 留资/对接 API | 只读 [07-官网统一留资接口](../../开发文档/1、需求/官网/03-接口与对接/07-官网统一留资接口_20260531.md)**禁止再造第二 URL** |
| SEO/关键词 | 读 [09](../../开发文档/1、需求/官网/04-SEO与增长/09-官网SEO与GEO全站优化_20260531.md);要做后台读 [12](../../开发文档/1、需求/官网/04-SEO与增长/12-官网SEO运营后台与自动挂链_20260531.md) |
| 产品矩阵配图 | 读 [08-产品矩阵界面与截图](../../开发文档/1、需求/官网/02-页面与视觉/08-产品矩阵界面与截图_20260531.md) |
| 超管接口汇总 | 同步 `SuperAdmin/lib/api-docs-index.ts` + `5、接口/SuperAdmin_四端对接接口索引.md` |
---
## 二、文档地图(按编号)
| 编号 | 文件 | 用途 |
|:---|:---|:---|
| **进行中** | `修改/官网_进行中_20260531.md` | **REQ-WEB 总表 · 待改文件 · E1E7** |
| 00 | 00-官网需求执行总表_20260531.md | 进度 % · 速查 |
| 01 | 01-需求与验收_v2.md | IA、价格、验收 smoke |
| 06 | 06-首页Hero三版_20260531.md | Hero 配色/真截图 |
| 07 | 07-官网统一留资接口_20260531.md | **鉴定版** POST /v1/api/scenarios |
| 08 | 08-产品矩阵界面与截图_20260531.md | 三端截图规范 |
| 09 | 09-官网SEO与GEO全站优化_20260531.md | 关键词矩阵 + Schema + 工具 |
| 10 | 10-官网对外接口文档_20260531.md | 改造官网索引 |
| 11 | 11-超管统一接口文档中心_20260531.md | 超管 9 类汇总 |
| 12 | 12-官网SEO运营后台与自动挂链_20260531.md | **⬜** SEO 运营中心需求(挂链/发帖/收录) |
| 13 | 13-官网AI数智员工单设备硬件专题_20260531.md | 单手机 mockup · 流程图 · `../../10、项目管理/images/官网_20260531/` |
| **14** | **14-官网AI客服获客场景_20260531.md** | **P0 · 仅文档 · 开发按 §十一Server→官网** |
| **15** | **15-官网后台用户旅程与Webhook推送_20260531.md** | **P1 · 有手机优先 · 一键推 Webhook** |
---
## 三、常用命令
```bash
# 本地预览 + smoke
bash 开发文档/8、部署/scripts/deploy_official_site.sh --smoke
# → http://127.0.0.1:8090/
# 复盘末尾生成本轮文档链接Cursor 可点)
python3 开发文档/scripts/build_obsidian_doc_link.py \
"开发文档/1、需求/官网/02-页面与视觉/08-产品矩阵界面与截图_20260531.md" \
--title "产品矩阵规范"
```
---
## 四、留资铁律(踩坑合集)
| 坑 | 正确做法 |
|:---|:---|
| 再造 `/v1/open/*` 或第二个留资 URL | **唯一** `POST /v1/api/scenarios` + apiKey/sign |
| 只传 `portrait.tags` | 必须传顶层 **`tags` + `siteTags`** 逗号串(`lead.js` 已补) |
| `portrait` 参与签名 | 签名时 **删除** portrait、apiKey、sign |
| 页脚明文微信/电话 | 只留 **data-lead** 弹窗 |
| planId 写 URL | 用 **apiKey** 绑 plan **779** |
**配置**`官网/assets/js/site-config.js` · **提交**`lead.js` · **轨迹**`track.js`
### 4.1 访客追踪 + WebhookREQ-WEB-60
| 项 | 说明 |
|:---|:---|
| 追踪 API | `POST /api/track`(公开)· 数据 `data/analytics.json` |
| 留资推送 | `POST /api/lead-local`(有手机号才推飞书)· **勿**浏览器直连飞书 |
| 飞书格式 | `{ msg_type:"text", content:{ text:"…" } }` · 成功 `code===0` |
| 合并键 | `phone` > `fingerprint`canvas+UA非真 IMEI/MAC> `visitorId` |
| 后台 | 概览/获客/旅程 · 右上 ⏰ `GET /admin/api/alerts` |
| 边做边勾 | 完成子项即改 `_进行中` REQ-WEB-60 / **REQ-WEB-61** 表状态 ✅ |
| 开发顺序 | **REQ-CKB-66 阶段1Server→ 阶段2计划UI→ REQ-WEB-61 阶段3官网** |
---
## 五、SEO / GEO 要点2026
1. **SEO 地基优先**76% Google AI 摘要 URL 在自然 Top10 — 先 meta、sitemap、Schema、内链。
2. **五类词**:核心词 / 场景词连锁·AI客服/ 长尾 FAQ / 蒸馏词 / 品牌词 → 见 09 §三。
3. **Schema**index 已有 SoftwareApplication + ItemList(产品矩阵)pricing FAQPage。
4. **工具**Screaming Frog 全站爬、Google Rich Results Test、百度搜索资源平台。
5. **GEO**E-E-A-T + 真实案例数字 + 站外知乎/百家号同实体 NAP。
6. **勿迷信单独 GEO 外包**:与 SEO 同一套内容工程。
---
## 六、产品矩阵 UI
| 端 | 截图路径 | 替换方式 |
|:---|:---|:---|
| 存客宝 | `官网/assets/img/matrix-cunkebao.png` | 3100 真机截图覆盖 |
| 触客宝 | `官网/assets/img/matrix-touchkebao.png` | 3101 真机截图覆盖 |
| AI数智员工 | `官网/assets/img/matrix-aistaff.png` | 3104 真机截图覆盖 |
- HTML`index.html#matrix` · class `matrix-shot`
- 标题:**三端入口** + **存客宝产品矩阵**(对齐设计稿)
---
## 七、超管「接口文档」同步(第 9 类)
```
改留资接口说明
→ 07/10 官网 MD
→ SuperAdmin_四端对接接口索引.md §九
→ SuperAdmin/lib/api-docs-index.ts id: official-site
→ 超管 /dashboard/ai-center?tab=docs 自测
```
**注意**Tab 挂在 AI 中心下,**内容是全平台 9 类**,文案勿写「仅 AI 接口」。
---
## 八、复盘与链接Cursor 专用 · 必记)
| 错误 | 正确 |
|:---|:---|
| 复盘贴 `obsidian://` | **点不开**;用 **代码引用块** ` ```1:3:相对路径 ` + 绝对路径一行 |
| 贴 6 个历史 MD 链接 | **只列本轮**新建/修改 13 个文件 |
| 用 `file://` 超链接 | 卡若需求整理 §8.1 **禁止** |
脚本:`开发文档/scripts/build_obsidian_doc_link.py`(默认 Cursor 块)
---
## 九、与兄弟 Skill 关系
| Skill | 关系 |
|:---|:---|
| [开发需求文档整理](../开发需求文档整理/SKILL.md) | 四端需求 MD官网专文走 **本 Skill** |
| [需求协调与归档](../需求协调与归档/SKILL.md) | 跨端 COORD |
| [存客宝宝塔部署](../存客宝宝塔部署/SKILL.md) | 公网部署官网时 |
| 卡若AI F01d 复盘格式 | 每轮收尾五块 |
---
## 十、验收清单(每轮)
- [ ] `node 官网/smoke.mjs` 全绿
- [ ] 矩阵三图不裂、alt 有意义
- [ ] 留资弹窗可提交(本地需 apiKey
- [ ] 文档 03 迭代日志 + 相关专文变更记录
- [ ] 若动接口:超管 api-docs-index 已同步
---
## 十一、SEO 运营后台(需求 · 12 号文档)
| 要点 | 说明 |
|:---|:---|
| 放哪 | **SuperAdmin 平级「SEO 运营」**,不要塞 AI 中心 |
| 自动挂链 | **站内**内链脚本 + **矩阵站**互链表;**站外**发稿任务单(知乎等多为草稿+人工) |
| 申请 | P1 接百度站长、GSC、IndexNow API |
| 禁止 | 小旋风类黑帽站群、泛目录、买链轮链 |
| 开源参考 | GitHub `SEO-Interlinking-Tool``schema-sentry` |
| 本轮 | **只写需求**,不改 `官网/` |
---
## 十二、经验沉淀evolution
| 日期 | 经验 |
|:---|:---|
| 2026-05-31 | Cursor 聊天内 obsidian:// 无效 → 复盘用代码引用块 |
| 2026-05-31 | 官网留资已有单接口,文档 07 鉴定即可 |
| 2026-05-31 | 超管接口文档 = 9 类全平台,官网为第 9 类 |
| 2026-05-31 | 产品矩阵需 UI 截图,非纯文字卡 |
| 2026-05-31 | SEO+GEO关键词矩阵在 09ItemList Schema 上 index |
| 2026-05-31 | SEO 后台要自建;站群 SaaS 黑帽不做;发帖 API 稀缺→任务单 |
| 2026-05-31 | 本机素材:`9、手册/使用手册/截图/AI数智员工-登录页.png`;海报参考在 `个人/4、工作日志/会议纪要/参考/` |
| 2026-05-31 | 单设备叙事≠海报获客;硬件专题见 13 号 REQ-WEB-5054 |
| 2026-05-31 | **当日官网需求只写 `官网_进行中`**`官网/0x` 专文=鉴定索引,勿在 `modify/` 再开主题 MD |
| 2026-05-31 | 官网 AI systemPrompt**Server 七层合并**(存客宝 §6· 默认循序渐进留资见 REQ-WEB-63 |
| 2026-05-31 | REQ-WEB-60访客 analytics + 飞书 Webhook + 后台 Karuo UI开发完须在 `_进行中` 打勾 |

View File

@@ -0,0 +1,120 @@
---
name: 存客宝宝塔部署
description: 存客宝 v3 部署到腾讯云宝塔 42.194.245.239 的标准流程、验收与踩坑防治。触发词存客宝部署、宝塔部署、kr-op部署、ckbapi部署、腾讯云部署、部署踩坑、Query.php、场景获客失败、工作台500、二维码不显示。
---
# 存客宝宝塔部署 Skill
> 生产kr-op / kr-kf / ckbapi · 服务器 42.194.245.239:22022
> 详细踩坑:`开发文档/8、部署/宝塔腾讯云部署踩坑清单.md`
## 一、部署前必读(防治清单)
| 序号 | 风险 | 防治 |
|------|------|------|
| 1 | 登录 405 | 构建 `VITE_API_BASE_URL=https://ckbapi.quwanzhi.com` + kr-op `/api` 反代 |
| 2 | 登录 10501 / where 全挂 | **禁止**覆盖 `thinkphp/library/think/db/Query.php` 为 TP5.1 精简版;脚本 2b 强制同步仓库版 |
| 3 | JWT 500 | 控制器用 `JwtAuthTrait::authUser()`,勿读 `$request->userInfo` |
| 4 | 工作台 500 | `setAttr('config')` 勿用 `$item->config` / `$item->data[]` |
| 5 | 流量池分页 1 条 | count 用 `(clone $query)->count()` |
| 6 | 场景设置保存 500 | 先跑 `20260525_acquisition_global_settings.sql` |
| 7 | 场景列表空白 / 401 | 前端 token 每次请求从 store 读401 跳登录 |
| 8 | **海报二维码 img 404** | URL **勿**带 `.png` 后缀Nginx 当静态文件 404`/v1/frontend/poster/qrcode/{id}` |
| 9 | **海报弹窗误显 H5 码** | 主二维码 = **小程序码**`poster_qrcode_mode=miniprogram`H5 表单仅下方链接区,两者隔离 |
| 10 | **小程序码进表单/错 AppId** | 落地页 **`pages/poster/index2`**(非 `input2`AppId **`wx789850448e26c91d`**(存客宝);`.env` 勿重复 `[weChat]` 段 |
| 11 | **授权手机号后无入池** | `decryptphone` 勿用 EasyWeChat `phone_number`;用 `MiniProgramQrCodeService::getUserPhoneNumber` curl 直连微信 |
| 12 | rsync 勿删 `.env` / `vendor` / `runtime` | 脚本已 exclude |
## 二、标准命令(项目根 cunkebao_v3
```bash
# 1. 数据库迁移(有 migrations 时先跑)
bash 开发文档/8、部署/scripts/run_db_migrations_tencent.sh
# 2. 全量部署
bash 开发文档/8、部署/scripts/deploy_tencent_baota.sh
# 3. 仅前端
bash 开发文档/8、部署/scripts/deploy_tencent_baota.sh --skip-server
# 4. 验收(必须 17/17扩展含 AI/支付/海报;线上全链路)
bash 开发文档/8、部署/scripts/acceptance_tencent_prod.sh
bash 开发文档/8、部署/scripts/acceptance_tencent_extended.sh
bash 开发文档/8、部署/scripts/acceptance_tencent_online_full.sh
# 5. 仅同步支付 overlay + 证书
bash 开发文档/8、部署/scripts/sync_payment_certs_tencent.sh
```
## 三、上线顺序
**迁移 SQL → rsync 后端(含 Query.php 2b→ 构建前端 → 发布 dist → 清 cache → 验收**
## 四、快速诊断
```bash
# 登录
curl -X POST https://ckbapi.quwanzhi.com/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"account":"karuo","password":"***","typeId":1}'
# 场景(需 Bearer
curl -H "Authorization: Bearer $TOKEN" \
'https://ckbapi.quwanzhi.com/v1/plan/scenes?page=1&limit=5'
# 海报二维码(应 200 image/png勿测 .png 后缀)
curl -I 'https://ckbapi.quwanzhi.com/v1/frontend/poster/qrcode/709'
# Query.php 版本(应为 375f1093… 一类newQuery 须复制 name/table
ssh -p 22022 root@42.194.245.239 \
"md5sum /www/wwwroot/ckbapi.quwanzhi.com/thinkphp/library/think/db/Query.php"
```
## 五、路径速查
| 组件 | 路径 |
|------|------|
| API | `/www/wwwroot/ckbapi.quwanzhi.com` |
| 存客宝 dist | `/www/wwwroot/auto-devlop/ckb-operation/dist` |
| 触客宝 dist | `/www/wwwroot/auto-devlop/tkb-wechat/dist` |
| monorepo | `/www/wwwroot/mckb_quwanzhi_com` |
| MySQL | 10.0.0.4:3306 / cunkebao_v3 / ck_ |
## 六、复盘归档
每次部署后更新:`开发文档/8、部署/04-部署复盘/`
---
## 七、超管 SuperAdmin本地 Docker vs 宝塔)
| 项 | 本地 Docker | 宝塔生产 |
|----|-------------|----------|
| 访问 | http://localhost:**3103** | **kr-admin.quwanzhi.com**Node/PM2 单独部署,未进 ckb dist |
| API 反代 | Next `rewrites``cunkebao-server``/api/v1/admin` | 需 Nginx 反代到 **ckbapi.quwanzhi.com/v1/admin** 或同域 `/api` |
| 构建 | `docker compose build superadmin-web`build-arg `SUPERADMIN_API_PROXY_TARGET=http://cunkebao-server` | `npm run build` + `next start``NEXT_PUBLIC_API_BASE_URL=/api/v1/admin` |
| 设备二维码 | 依赖 `api.guid` + `api.deviceSocketHost``.env.docker` | 同左,生产 `.env` 必须配置;缺则添加设备报「环境配置不完整」 |
| S2 同步设备 | 需本机/内网能访问 **rpc.API_BASE_URL**(工作手机 SDK | 宝塔 server 需能连 S2本地 Docker 常连 `workphone-sdk:8899` 或线上 kf |
| 客户池列表 | 37 万+ 数据,首屏约 **35s**V2 表);勿用旧 `traffic_source_v1` JOIN | 同架构;生产库更大时需加索引/缓存 |
| 端口冲突 | 登记 **3103**(勿与 website 3102 混用) | 宝塔单独 Node 项目,不占 3100/3101 |
### 超管常见报错
| 现象 | 原因 | 处理 |
|------|------|------|
| 添加设备网络错误 | 前端打到错误路径 `/v1/api/device/add` | 用 `POST /v1/admin/devices/qrcode` |
| 二维码环境不完整 | 缺 guid / deviceSocketHost | 补 `Server/.env` `[api]``config/device_bind.php` |
| 客户池一直 loading | 旧 trafficPool 全表 JOIN 超时 | 已改 V2确认部署含新 `GetPoolListController` |
| 转移设备失败 | 目标项目无 s2_accountId | 项目编辑页确认触客宝账号已创建 |
| 本地 3105/3103 混用 | 多个 next dev 实例 | 统一 Docker **3103** 或登记端口 |
### 超管验收(本地)
```bash
bash 开发文档/8、部署/scripts/acceptance_superadmin_docker.sh
# 扩展overview + devices
TOKEN=$(curl -s -X POST http://localhost:8082/v1/admin/auth/login -H 'Content-Type: application/json' \
-d '{"account":"admin","password":"e10adc3949ba59abbe56e057f20f883e"}' | python3 -c "import sys,json;print(json.load(sys.stdin)['data']['token'])")
curl -s -H "Authorization: Bearer $TOKEN" 'http://localhost:8082/v1/admin/dashboard/overview?limit=5' | python3 -m json.tool | head -30
curl -s -H "Authorization: Bearer $TOKEN" 'http://localhost:8082/v1/admin/devices/list?limit=5' | python3 -m json.tool | head -20
```

View File

@@ -0,0 +1,180 @@
---
name: 开发需求文档整理
description: 将录屏转写、标注截图、口述草稿合并为存客宝标准需求 MD。命名 项目_功能主题_日期modify 扁平无子目录;四端分文件。触发词:需求文档、录屏需求、截图需求、需求拆解、整理需求、开发需求文档、存客宝需求、触客宝需求、超管需求。
---
# 开发需求文档整理 Skill存客宝 v3 · W05b 项目内)
> **卡若AI 母 Skill**`/Users/karuo/Documents/个人/卡若AI/02_卡人/水泉_规划拆解/需求整理/SKILL.md`(通用)
> **本 Skill**:存客宝 **录屏全量包** + **模块拆解**;落盘纪律以本文为准。
> **协调归档**:配合 [需求协调与归档](../需求协调与归档/SKILL.md)W05c 项目内)
---
## 一、目录与命名(强制 · **2026-05-31 修订**
> **当日进行中 = 一项目一文件**。主题需求写在文件 **内章节**,不另开 `{项目}_{主题}.md`。
> 纪律真源:[需求协调与归档/SKILL.md](../需求协调与归档/SKILL.md) §二、§九
```
开发文档/1、需求/
├── 索引.md
├── 修改/
│ ├── YYYYMMDD_需求总览.md ← 唯一横表(待办+协同+审计)
│ ├── 存客宝_进行中_{YYYYMMDD}.md ← 存客宝当日唯一真源(多节)
│ ├── 触客宝_进行中_{YYYYMMDD}.md
│ ├── 官网_进行中_{YYYYMMDD}.md
│ ├── AI数智员工_进行中_{YYYYMMDD}.md
│ ├── images/
│ └── (无 images/ · 无 已完成/
开发文档/10、项目管理/
├── images/ ← 需求配图
└── 已完成/ ← 关单批次
```
| 规则 | 说明 |
|:---|:---|
| **进行中文件名** | **仅** `{端}_进行中_{YYYYMMDD}.md` |
| **禁止** | 根目录 `存客宝_设置与我的页_*``触客宝_手机_*``SuperAdmin_*_{主题}_*` 等与 `_进行中` 并存 |
| **超管/协同** | SuperAdmin、触客宝登录/BFF 等 **写入 `存客宝_进行中_{YYYYMMDD}.md` 新 §**,不另开 `SuperAdmin_*.md` |
| **文内结构** | `## §X · {功能主题}`:问题 / 根因 / 规则 / 接口 / REQ / 验收 |
| **端名称** | **触客宝**、**存客宝**、**SuperAdmin**、**AI数智员工** |
| **禁止误称** | 不写「初客宝 / 初刻宝 / 纯客宝」 |
**当日示例20260531**
| 端 | 唯一文件 | 节示例 |
|:---|:---|:---|
| 存客宝 | `存客宝_进行中_20260531.md` | §三 设置/我的 · §一 总表 dedupe |
| 触客宝 | `触客宝_进行中_20260531.md` | 功能中心二版 |
| 官网 | `官网_进行中_20260531.md` | REQ-WEB · E1E9 |
---
## 二、完成目标优先
**标题下第一句**必须是:
```markdown
完成目标:【一句话:交付什么、谁受益、如何验收】。
```
再写来源、时间轴、模块、排期。禁止先堆口述原文。
---
## 三、何时启用
| 素材 | 动作 |
|:---|:---|
| 录屏 TXT + 截图 + 草稿 | **追加**对应端 `{项目}_进行中_{日期}.md` 新 §,不新建主题 MD |
| 口述「整理需求」 | 读本文 + 项目经验 `卡若AI/.../项目经验/存客宝.md` |
| 跨端需求 | 拆到四端各一篇 + 总表COORD 写入各端「跨项目协调」节 |
---
## 四、执行流程
```
素材收集 → 完成目标 → 识别端(四端之一) → 图片入 10、项目管理/images/ → 模块拆解 → 落盘 → 更新索引
```
### Step 1 · 确定端与主题
| 端 | 端口 | 主题示例 |
|:---|:---|:---|
| 存客宝 | 3100 H5 | 微信号、场景获客、迁移、设备 |
| 触客宝 | 3101 PC | AI获客、工作台、Webhook |
| SuperAdmin | 3103 | 超管后台、客户池、项目 |
| AI数智员工 | 3104 | 扫码、飞书、协同对接 |
### Step 2 · 图片
- 进行中:`![](images/xxx.png)`(相对 `modify/*.md`
- 命名建议:`YYYYMMDD-模块-序号.png`
- Obsidian`attachmentFolderPath` = `./images`
### Step 3 · 模块拆解(每端 MD 内)
| 块 | 内容 |
|:---|:---|
| 需求总表 | 优先级 + 功能块 + 状态 |
| 分模块章节 | 说明 / 表 / 验收 / 代码路径 |
| **跨项目协调(本端 · W05c** | 见 [需求协调与归档](../需求协调与归档/SKILL.md) |
### Step 4 · 录屏全量包(可选)
大录屏可另存 `10、项目管理/已完成/` 或保留详细节;**当日进行中**仍只维护该端 **一篇** 主题 MD。
---
## 五、标准章节
见 [template.md](template.md)。必含:完成目标、需求总表、模块章节、开发计划、验收清单、变更记录。
---
## 六、与卡若AI / 项目规范
| 环节 | 动作 |
|:---|:---|
| W05b 通用 | 卡若AI `需求整理/SKILL.md`;存客宝以 **本节目录纪律** 为准 |
| W05c 协调 | 共享 `/Users/karuo/Documents/开发/_共享/跨项目协调/索引.md` |
| COORD 分配 | 001/005/006→存客宝001/003→触客宝001/002/003→SuperAdmin001/002/004→AI数智员工 |
| 开发前 | `开发文档/开发铁律_先读文档再编码.md` Phase 0 |
| Git | `cunkebao-local-only-no-github.mdc`,不自动 push |
---
## 七、质量门禁
- [ ] 当日需求已写入 `{端}_进行中_{日期}.md`(无第二份同端 MD
- [ ] 文内含 `完成目标` 或 § 内问题/根因/验收表
- [ ] 第一句 `完成目标:...`
- [ ] 无「初刻宝」等误称
- [ ] 图片 `../../10、项目管理/images/` 存在
- [ ] 本端 COORD 表已写入(有则写,无则注「当前无」)
- [ ] `1、需求/索引.md` 与当日总表已更新
---
## 八、验收后归档
1. 迁入 `10、项目管理/已完成/{项目}_{功能主题}_{日期}/` + `../../10、项目管理/images/`
2. 运行 `python3 开发文档/1、需求/scripts/flatten_requirements.py`(可选)
3. 更新 `YYYYMMDD_需求总览.md`
---
## 九、存客宝官网(独立 Skill
对外品牌站 **不走四端 `{项目}_{主题}_{日期}.md`**,走专题目录:
| 项 | 路径 |
|:---|:---|
| Skill | [.cursor/skills/存客宝官网开发/SKILL.md](../存客宝官网开发/SKILL.md) |
| **进行中真源** | [`修改/官网_进行中_20260531.md`](../修改/官网_进行中_20260531.md) |
| 文档 | `开发文档/1、需求/官网/`0012 编号专文) |
| 代码 | `官网/` 静态站 |
触发词官网需求、官网SEO、产品矩阵、官网留资 → **先读官网 Skill**,再落盘 `官网/``1、需求/官网/*.md`
**复盘链接**:官网任务同样遵守 `cunkebao-karuo-dialogue.mdc`(代码块路径,禁止 obsidian:// 贴聊天)。
---
## 十、经验沉淀
| 日期 | 说明 |
|:---|:---|
| 2026-05-31 | §一 修订:**进行中仅 `{端}_进行中_{date}.md`**;与 W05c 需求协调 Skill 对齐 |
---
## 十一、延伸阅读
- [template.md](template.md) · [examples.md](examples.md)
- [需求协调与归档/SKILL.md](../需求协调与归档/SKILL.md)
- [存客宝官网开发/SKILL.md](../存客宝官网开发/SKILL.md)
- 卡若AI`02_卡人/水泉_规划拆解/需求整理/references/项目经验/存客宝.md`

View File

@@ -0,0 +1,37 @@
# 标杆示例 · 2026-05-26 全量需求
## 文档路径
`开发文档/1、需求/修改/存-20260526_超级后台设备客服与流量池全量需求.md`
## 素材来源对照
| 类型 | 原始路径 | 处理方式 |
|------|----------|----------|
| 录屏 | `/Users/karuo/Downloads/存客宝开发 20260526.txt` | → 第一章时间轴 14 行 |
| 截图夹 | `/Users/karuo/Downloads/20260526/*.png` | → 复制为 `../../10、项目管理/images/20260526-*.png` |
| 草稿 | `修改/存-20260526.md` | → 并入模块 D微信号/流量池) |
## 结构亮点(可复制)
1. **时间轴先行**:开发先懂「口述顺序」,再按端看章节。
2. **mermaid 产品关系**:多系统(超管/存客宝/触客宝/工作手机)必读。
3. **每节固定块**:需求 + 图 + 表 + 验收 + 代码路径。
4. **P0 扫码绑 IMEI**:步骤表 + 链到 20260525 设备管理文档。
5. **无接口原则**:设备列表「无接口不展示」写进表。
6. **Phase 0 六条**:可直接开 sprint。
7. **验收 20+ checkbox**:测试无需再问产品。
## 图片命名对照(节选)
| 标准名 | 原文件名 |
|--------|----------|
| `20260526-superadmin-01.png` | 超级管理后台标注_20260526204146.png |
| `20260526-device-02.png` | 存客宝-我的-设备管理标注_20260526205229.png |
| `20260526-kefu-module-02.png` | 存客宝-我的-新增客户版块标注_20260526210031.png |
## 触发本 Skill 的用户话术示例
- 「把录屏和截图整理成需求文档,放修改目录」
- 「按上次 0526 的格式再做一份需求」
- 「需求要能直接在 Obsidian 看图,开发能照着做」

View File

@@ -0,0 +1,185 @@
# 需求文档标准模板(复制后替换占位符)
> 进行中文件名:`{项目名称}_{功能主题}_{YYYYMMDD}.md`(例 `存客宝_微信号场景迁移与设备_20260529.md`
> 位置:`开发文档/1、需求/修改/`(扁平,无子目录)
> 图片:`开发文档/10、项目管理/images/` · 已完成:`10、项目管理/已完成/{项目}_{主题}_{日期}/images/`
---
```markdown
# {项目名称}_{功能主题}_{YYYYMMDD}
完成目标:【一句话写清本轮最终交付、边界和验收结果。例:把某账号/项目/功能链路整理到可开发、可测试、可验收的稳定状态。】
> **来源**:录屏口述 `【录屏文件名】.txt`(【时长】)+ 标注截图 `【文件夹或说明】` + 草稿 `【草稿文件名,无则删】`
> **日期**YYYY-MM-DD
> **状态**:待开发
> **适用端**:【例:超级管理后台 3103 · 存客宝 H5 3100 · 触客宝】
---
## 一、录屏时间轴(与口述一一对应)
| 时间点 | 模块 | 口述要点 |
|--------|------|----------|
| MM:SS | 【模块名】 | 【一句话需求,**加粗**关键动作】 |
| MM:SSMM:SS | 【模块名】 | 【跨段合并要点】 |
| — | 草稿补充 | 【仅来自草稿、录屏未提的项】 |
---
## 二、目标拆解(先定目标,再拆需求)
| 目标层级 | 完成目标 | 验收标准 |
|----------|----------|----------|
| 总目标 | 【一句话总目标】 | 【最终验收标准】 |
| 子目标 1 | 【模块/数据/页面目标】 | 【可测试结果】 |
| 子目标 2 | 【模块/数据/页面目标】 | 【可测试结果】 |
---
## 三、产品关系(开发必读)
```mermaid
flowchart TB
subgraph A["【系统A】"]
A1[模块]
end
subgraph B["【系统B】"]
B1[模块]
end
A1 --> B1
```
- **【产品甲】**:【与产品乙的关系一句】
- **【关键链路】**:【例:扫码 → IMEI → 入库 → 超管可见】
---
## 四、模块 A · 【端名称 · 如超级管理后台】
### A1. 【功能名】P0/P1
**完成目标**:【本小节完成后达到什么状态】
**需求**:【一句话】
![【图说】](images/YYYYMMDD-模块-01.png)
| 项 | 说明 |
|----|------|
| 业务规则 | 【规则】 |
| 验收 | 【可测试句子】 |
| 建议改动 | 【组件/文件,无则写待勘察】 |
| 接口 | 【有则链 5、接口无则「无接口不展示」】 |
---
### A2. 【功能名】P0
【重复 A1 结构:说明 + 图 + 表 + 验收 + 代码入口】
---
## 五、模块 B · 【端名称】
### B1. 【功能名】P0
**完成目标**:【本小节完成后达到什么状态】
![【图说】](images/YYYYMMDD-module-02.png)
| 功能点 | 详细说明 | 优先级 |
|--------|----------|--------|
| 【点1】 | 【说明】 | P0 |
**代码入口**`【仓库相对路径】`
---
## 六、模块 C · 【可选】
【无则写:本期录屏未涉及,略。】
---
## 七、开发计划(建议排期)
### Phase 0 · 本周必做
| 编号 | 任务 | 端 | 估时 |
|------|------|-----|------|
| P0-1 | 【任务】 | 【SuperAdmin/Cunkebao/Server】 | 【0.5d】 |
### Phase 1 · 下周
| 编号 | 任务 | 端 |
|------|------|-----|
| P1-1 | 【任务】 | 【端】 |
### Phase 2 · 迭代
| 编号 | 任务 |
|------|------|
| P2-1 | 【任务】 |
---
## 八、验收清单(测试可直接勾选)
### 【端名称】
- [ ] 【验收项1】
- [ ] 【验收项2】
---
## 九、代码与文档索引(开发定位)
| 模块 | 路径 |
|------|------|
| 【模块】 | `【文件路径】` |
| 接口真源 | `开发文档/5、接口/存客宝API全量索引.md` |
---
## 十、素材清单(本目录 images
| 文件名 | 用途 |
|--------|------|
| `YYYYMMDD-xxx-01.png` | 【对应章节】 |
> **Obsidian**`![](images/xxx.png)` 与本文同级 `../../10、项目管理/images/` 目录。
---
## 十一、进度
| 维度 | 状态 |
|------|------|
| 需求拆解 | ✅ 已完成 |
| UI 素材归档 | ✅ / ⏳ |
| 开发 | ⏳ 待排期 |
| 接口文档回写 | ⏳ 开发后 |
**维护**:变更写在下方表。
| 日期 | 变更 |
|------|------|
| YYYY-MM-DD | 初版:录屏 + 截图 + 草稿合并 |
```
---
## 模块章节编号约定
| 章 | 内容 |
|----|------|
| 二 | 目标拆解(总目标与子目标) |
| 三 | 产品关系 |
| 四 | 模块 A通常超管 |
| 五 | 模块 B通常存客宝主功能 |
| 六 | 模块 C我的/客服等) |
| 七~十一 | 固定:计划、验收、索引、素材、进度 |
模块超过四个时继续「六、模块 E」**不要**打乱七~十一的固定编号。

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@@ -0,0 +1,177 @@
---
name: 触客宝手机端前端壳
description: 触客宝 Touchkebao 手机 H5 壳层开发铁律:顶底固定、禁缩放、键盘不乱动、仅中间滚动。对齐存客宝 index.html+Layout 与 AI数智员工 mobile-terminal。触发词触客宝手机端、mobile shell、viewport、键盘、顶栏固定、底栏Tab、user-scalable、visualViewport、100dvh、乱动、壳固定。
---
# 触客宝 · 手机端前端壳 Skill
> **需求真源**[触客宝_进行中 §98 / TKB-M30](../../开发文档/1、需求/修改/触客宝_进行中_20260602.md)(§十五~§十九 见 [0530归档](../../开发文档/10、项目管理/已完成/触客宝_0530进行中全文归档_20260530/)
> **参照实现**
> - 存客宝 `Cunkebao/index.html` · `Cunkebao/src/components/Layout/`
> - AI数智员工 `AI数智员工/app/layout.tsx` · `globals.css` · `mobile-app-shell.tsx`
> **路由范围**`/mobile/**` · 组件根 `Touchkebao/src/pages/mobile/`
---
## 一、何时启用
| 场景 | 必须读本 Skill |
|:---|:---|
| 新建/改 `/mobile/` 任意页面 | ✅ |
| 改 `Layout.tsx` · TabBar · 顶栏 | ✅ |
| 改聊天输入区 · 键盘交互 · 发送/AI 不遮挡 | ✅ §十八 |
| 改顶栏 · 同步态 · 算力 | ✅ §十六 |
| 改进房预加载 · Dexie 缓存 | ✅ §十七 · **PC+手机双端同一套** |
| 改 ErrorBoundary / 登录跳转 | ✅ §十九 |
| 改 `Touchkebao/index.html` | ✅ |
| 仅改 PC `/pc/` | ❌ 不必 |
---
## 二、壳三层(固定结构)
```
html.tkb-mobile锁滚
└─ #root
└─ .shellfixed · 100dvh · overflow hidden · max-width 420px
├─ .topBar ← flex-shrink:0 · z-50 · safe-area-top
├─ .body ← flex:1 · min-height:0 · 唯一纵向滚动
└─ .tabBar ← flex-shrink:0 · z-50 · safe-area-bottom
```
**全屏子页**(聊天室):`.room` 覆盖 Tab · 结构 `topBar + record(scroll) + enter(fixed bottom)`
---
## 三、index.html 清单(强制)
```html
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover, shrink-to-fit=no" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="format-detection" content="telephone=no" />
```
**内联 `--real-vh` 脚本**(与存客宝一致):
```javascript
function syncVh() {
var vv = window.visualViewport;
var h = (vv && vv.height) || window.innerHeight || document.documentElement.clientHeight;
document.documentElement.style.setProperty("--real-vh", h * 0.01 + "px");
}
syncVh();
window.addEventListener("resize", syncVh);
window.addEventListener("orientationchange", syncVh);
if (window.visualViewport) {
window.visualViewport.addEventListener("resize", syncVh);
}
```
**触控锁滚**(存客宝同款):
```css
@media (hover: none) and (pointer: coarse) {
html, body { position: fixed; width: 100%; height: 100%; overflow: hidden; touch-action: manipulation; }
#root { width: 100%; height: 100%; overflow: hidden; }
}
```
---
## 四、CSS 变量与高度(强制)
```scss
.shell {
position: fixed;
inset: 0;
max-width: 420px;
width: 100%;
margin: 0 auto;
display: flex;
flex-direction: column;
overflow: hidden;
height: 100dvh;
height: calc(var(--real-vh, 1vh) * 100);
max-height: calc(var(--real-vh, 1vh) * 100);
}
.body {
flex: 1;
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
overscroll-behavior-y: contain;
-webkit-overflow-scrolling: touch;
touch-action: pan-y;
}
```
---
## 五、键盘 / 输入(强制)
| 规则 | 说明 |
|:---|:---|
| 输入字号 **≥ 16px** | 防 iOS focus 自动 zoom |
| **不**滚 `body` | 只滚 `.body``.record` |
| `visualViewport` 只更新 `--real-vh` | 禁止改 body top 造成跳变 |
| 输入区 `touch-action: auto` | 壳层 `touch-action: manipulation` |
| 获焦不 unmount 父 Layout | 避免 Tab 栏闪烁 |
---
## 六、禁止清单
-`user-scalable=yes` 或缺少 `maximum-scale=1`
- ❌ 整页 `html/body { overflow-y: auto }`
- ❌ 只用 `100vh` 不用 `--real-vh` / `100dvh`
- ❌ 顶栏 `position: sticky` 代替 flex 壳
- ❌ 多个并列 `position:fixed` 全屏滚动区
- ❌ 键盘弹出时隐藏 TabBar 再显示(闪屏)
---
## 七、新页面开发 Checklist
开发任意 `/mobile/` 页面前 **逐项打勾**
- [ ] 内容在 `.body` 内 · 不在 shell 外再包一层可滚 `min-h-screen`
- [ ] 未新增 `100vh` 撑高 body
- [ ] 弹层 Drawer/Modal 用 `embedded` 或 Portal · 不破坏 shell overflow
- [ ] 输入框字号 ≥ 16px
- [ ] 长列表在 `.body` 内滚 · 不绑 `window` scroll
- [ ] iPhone 真机或 390 smoke捏合不缩放 · 键盘不整页乱跳 · **发送+AI 可见**(§十八)
- [ ] 顶栏:**圆形头像** · 同步时头像闪 · **「· 聊天」保留**(§十六)
- [ ] 需求变更已写入 `触客宝_进行中_20260530.md`(一项目一文件)
---
## 八、验收命令
```bash
node 开发文档/8、部署/scripts/verify_touchkebao_mobile.mjs
# 扩展后应含viewport user-scalable=no · sessionRows · 无横向溢出
```
---
## 九、与需求协调 Skill 关系
| Skill | 职责 |
|:---|:---|
| [需求协调与归档](../需求协调与归档/SKILL.md) | 需求落盘 · 一项目一 `_进行中` |
| **本文** | 手机 H5 **壳层与键盘** 编码铁律 |
| [开发需求文档整理](../开发需求文档整理/SKILL.md) | 录屏→需求 MD |
---
## 十、参照文件速查
| 项目 | 文件 |
|:---|:---|
| 存客宝 viewport | `cunkebao_v3/Cunkebao/index.html` |
| 存客宝 Layout | `Cunkebao/src/components/Layout/Layout.tsx` · `layout.module.scss` |
| AI 终端 globals | `AI数智员工/app/globals.css``.mobile-terminal` · `.mobile-app-frame` |
| AI Shell 组件 | `AI数智员工/app/components/mobile-app-shell.tsx` |
| 触客宝工作台 | `Touchkebao/src/pages/mobile/workbench/Layout.module.scss` |
| 触客宝聊天室 | `Touchkebao/src/pages/mobile/workbench/ChatRoom/index.module.scss` |

View File

@@ -0,0 +1,197 @@
---
name: 需求协调与归档
description: 存客宝四端需求维护与文档收敛一项目一进行中MD、已完成只进10、项目管理/已完成/、关单分拣、COORD、0530维护经验。触发词需求协调、需求归档、四端汇总、已完成、进行中、修改目录整理、一项目一文件、关单、需求维护、项目管理。
---
# 需求协调与归档 Skill存客宝 v3 · 0602 维护版)
> **🚫 不再写需求总览(卡若 0602 定)**:需求**按项目、按当天**直接写进 `{项目}_进行中_{YYYYMMDD}.md`,谁的子项目就写谁的文件,简单记录;**禁止**再建/维护 `YYYYMMDD_需求总览.md` 横表(旧文件留作历史索引)。
> **🚫 不再写未完成项审计(卡若 0603 定)**:禁止 `YYYYMMDD_未完成项审计*.md`**完成度表 + §待卡若验收 + §P0 开发顺序** 写在各端当日 `_进行中`。非当天未完 **迁入当天文件** 后 **删除** 旧 `_进行中``modify/` 只留当天,详文备份在 `10、项目管理/已完成/`)。
> **端真源(唯一)**`开发文档/1、需求/修改/{项目}_进行中_{YYYYMMDD}.md`**每端最多 1 个**
> **已完成总汇**`开发文档/10、项目管理/已完成/YYYYMMDD_四端已完成与全量检测总汇/`**不在根目录、不在 modify/**
> **跨端协同**:写在**发起方**项目 `_进行中`「跨项目协调」节(标 `协同 XXX`),不再单列总览。
> **术语铁律**:统一「知识库」「流量池」;**无「流量词」概念**ASR 误识,纠错库 `卡若AI/运营中枢/参考资料/卡若闽南口音_ASR纠错库.json` 已收 流量词→流量池)。
> **卡若AI 母 Skill**`/Users/karuo/Documents/个人/卡若AI/02_卡人/水泉_规划拆解/需求协调/SKILL.md`
> **单篇整理**[开发需求文档整理/SKILL.md](../开发需求文档整理/SKILL.md)
---
## 一、何时启用
| 场景 | 动作 |
|:---|:---|
| 新 P0/P1 需求(**当日/本轮** | **只追加** `修改/{端}_进行中_{YYYYMMDD}.md` 新 § 节 · **禁止**根目录另开 `{端}_{主题}_{date}.md` |
| 需求写完仅文档 | 在 `_进行中` 内写 **固定规则+验收** · 状态标 ⬜ |
| **官网边开发边勾** | 子项完成即改 `官网_进行中` **⬜→🟡→✅**REQ-WEB-60/61**AI 客服**按 [14 专文 §十一](../../开发文档/1、需求/官网/05-AI与硬件/14-官网AI客服获客场景_20260531.md) 阶段打勾 |
| **存客宝 AI 客服** | 同步 `存客宝_进行中` **REQ-CKB-66** · Server 先于官网 |
| 验收通过 | 迁入 `10、项目管理/已完成/{项目}_{主题}_{date}/` · 更新 **已完成总汇** §索引 |
| 端 100% 关单 | **删除** 根目录 `{端}_进行中` · 总汇保留链接 |
| 日终 / 分拣 | 各端 `_进行中` 内更新状态 · 已完成项 **移出** 根目录(**不维护总览** |
| 跨端依赖 | COORD 写入 **各端 `_进行中`**「跨项目协调」小节 |
---
## 二、目录纪律2026-06-02 · 已完成迁入项目管理)
```
开发文档/1、需求/修改/
├── README.md
├── 存客宝_进行中_YYYYMMDD.md ← 需求只写各端进行中(不写总览)
├── 触客宝_进行中_YYYYMMDD.md
├── 官网_进行中_YYYYMMDD.md
└── AI数智员工_进行中_YYYYMMDD.md (有未完才建)
# YYYYMMDD_需求总览.md ← 已停用 · 仅历史索引,勿再维护
开发文档/10、项目管理/
├── README.md
├── images/ ← 需求截图(禁止 modify/images
├── 已完成/ ← 关单批次唯一落点
│ ├── README.md
│ └── {项目}_{功能主题}_{YYYYMMDD}/
├── 开发进度_*.md
└── scripts/
```
### 禁止(本轮维护教训)
| 禁止 | 正确做法 |
|:---|:---|
| `modify/已完成/` · `modify/images/` | `10、项目管理/已完成/` · `10、项目管理/images/` |
| 写「需求总览/横表」 | **不写总览**;需求按项目当天写进 `{项目}_进行中_{YYYYMMDD}.md` |
| 文档里写「流量词」 | 统一「**流量池**」ASR 误识,无流量词概念) |
| 根目录 `四端已完成*.md` | 只放 `10、项目管理/已完成/…/` |
| 新建 `10、使用手册/` | 只写 `9、手册/使用手册/…` |
| 同项目两个 MD`_进行中` + 专文) | P0 全文 **并入** `_进行中` 新章节 · 删/归档专文 |
| 根目录 `SuperAdmin_{主题}_{date}.md` | 写入 **`存客宝_进行中_{date}.md` §** |
| 文件名 初刻宝/初客宝/触刻版 | 统一 **触客宝** |
| COORD 只写横表 | **必须**写入各端 `_进行中` |
---
## 三、0530 维护期经验(固定写法)
### 3.1 需求文档结构P0 块)
每个 P0 在 `_进行中` 内必须含:
1. **问题表**(卡若原话 / 截图路径)
2. **根因**(代码文件 · 现状 vs 期望)
3. **固定规则**(归一化、合并键、禁止项 · **不可写抽象**
4. **接口清单**(不变 URL
5. **代码锚点**(待建/已有路径)
6. **验收表**(可数字 · V1Vn
### 3.2 只写文档不开发
- 状态标 **「P0 · 仅文档 · 待开发」**
- 同步 `开发文档/5、接口/开发进度_接口文档.md` 顶部一节
- **不**为专文单独占根目录文件名
### 3.3 已完成 vs 进行中边界
| 状态 | 放哪 |
|:---|:---|
| 代码 ✅ · 待目视 🟡 | 进行中留一行 · 细节进总汇或 `10、项目管理/已完成/` 批次 |
| 代码 ✅ · 验收 ✅ | 移入 `10、项目管理/已完成/` · 从 `_进行中` **删**该行 |
| 运营 P2签字/Key 轮换) | 写横表 · **不**占 P0 开发 slot |
| 全端 100% | 无 `_进行中` · 链总汇 §二 |
### 3.4 截图与 images
- 进行中:`10、项目管理/images/{项目}_{现象}_{YYYYMMDD}.png`
- 关单迁 `10、项目管理/已完成/{批次}/images/`**改相对路径**
---
## 四、COORD 分配(写入各端 `_进行中`
| COORD | 简述 | 写入文档 |
|:---|:---|:---|
| 001 | SuperAdmin 开放 API | 存客宝、SuperAdmin、触客宝、AI数智员工 |
| 006 | 设备/微信项目同源 | **存客宝、触客宝** |
(其余见历史表 · 各端「跨项目协调」小节)
---
## 五、关单流程(逐步)
```
1. 开发/文档完成 → 更新 {端}_进行中 状态 ✅
2. 全文批次 → 10、项目管理/已完成/{项目}_{主题}_{date}/同名.md
3. 更新 已完成/…/四端已完成总汇.md §索引
4. 从 _进行中 删除已完成行;端无未完 → 删除 _进行中 文件
5. 更新 YYYYMMDD_需求总览.md
6. 更新 5、接口/开发进度_接口文档.md
7. 更新 10、项目管理/已完成/README.md 表格一行
8. 禁止 git push除非卡若本轮明确说提交
```
### 脚本(可选)
```bash
python3 开发文档/1、需求/scripts/flatten_requirements.py
python3 开发文档/1、需求/scripts/reorganize_requirement_docs.py
```
---
## 六、开发前检查
1. 读待开发横表 + **本端唯一** `_进行中`
2.**跨项目协调**
3. 对话列出:已读文档 ≤5 条
4. 确认 **没有** 第二份同端 MD
5. 关单目标路径为 **`10、项目管理/已完成/`**
---
## 七、质量门禁
- [ ] 根目录无 `*已完成*` MD总汇只在 `10、项目管理/已完成/`
- [ ] 每端 **≤1** 个 `_进行中``modify/`
- [ ] **禁止**`10、项目管理/已完成/` 新建批次(仅跳转 README
- [ ] P0 块含:问题 / 根因 / 固定规则 / 验收
- [ ] 已完成批次在 `10、项目管理/已完成/{项目}_{主题}_{date}/` 且图片不断链
- [ ] `10、项目管理/README.md``已完成/README.md` 已更新
---
## 八、与卡若AI 分工
| Skill | 职责 |
|:---|:---|
| **本文** | 一项目一文件 · **已完成进 10、项目管理** · 关单分拣 |
| 开发需求文档整理 | 录屏→全量 MD · 落盘纪律以本文为准 |
---
## 九、当日需求落盘Agent 必背)
```
卡若提新需求
→ 打开 modify/{端}_进行中_{YYYYMMDD}.md 新 §
→ 更新 待开发清单 一行
→ 截图 → 10、项目管理/images/
→ 禁止 新建 modify/{端}_{主题}_{YYYYMMDD}.md
关单后
→ 迁入 10、项目管理/已完成/{项目}_{主题}_{date}/
```
---
## 十、经验沉淀
| 日期 | 教训 |
|:---|:---|
| 2026-06-02 | 十大目录收口:已完成+配图→`10、项目管理` · 三表→`需求总览` · `10、使用手册``9、手册/使用手册` |
| 2026-06-02 | CKB-90流量分发多子账号轮询 + 客服选择 UI 与好友分配一致 |
---
## 十一、延伸阅读
- [修改/README.md](../../开发文档/1、需求/修改/README.md)
- [10、项目管理/README.md](../../开发文档/10、项目管理/README.md)
- [已完成/README.md](../../开发文档/10、项目管理/已完成/README.md)
- [四端对接铁律](../../开发文档/2、架构/四端对接铁律_只HTTP不串仓.md)

View File

@@ -1,51 +0,0 @@
name: Sync from Coding
on:
schedule:
- cron: '0 */2 * * *' # 每2小时执行一次
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout GitHub develop 分支
uses: actions/checkout@v4
with:
ref: develop
- name: 配置 Git 用户信息
run: |
git config user.name "zhiqun@qq.com"
git config user.email "zhiqun@qq.com"
- name: 添加 coding-origin 并获取最新代码
run: |
git remote add coding-origin https://${{ secrets.CODING_USERNAME }}:${{ secrets.CODING_TOKEN }}@e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3.git
git fetch coding-origin develop
- name: 使用远程分支强制合并(保留 theirs
run: |
# 尝试合并,如果失败就自动解决冲突
git merge --no-edit --allow-unrelated-histories coding-origin/develop || true
# 检查是否有冲突文件
if [ -n "$(git ls-files -u)" ]; then
echo "检测到冲突,自动使用远程分支内容解决"
# 对所有冲突文件强制使用远程分支版本theirs
for file in $(git diff --name-only --diff-filter=U); do
git checkout --theirs "$file"
git add "$file"
done
# 提交自动合并结果
git commit -m "Auto-merged coding-origin/develop with theirs resolution"
fi
- name: 推送回 GitHub develop 分支
run: |
git push origin develop

28
.gitignore vendored
View File

@@ -1,22 +1,20 @@
Store_vue/unpackage/
*.js
Store_vue/node_modules/
Server/.specstory/
Cunkebao/.next/
Cunkebao/.specstory/
.idea/
Cunkebao/.next/
Store_vue/node_modules/
*.zip
Cunkebao/.specstory/
*.cursorindexingignore
Server/.specstory/
Server/thinkphp/
Store_vue/.specstory/
Store_vue/unpackage/
Store_vue/.vscode/
SuperAdmin/.specstory/
node_modules
node_modules/
.next/
out/
dist/
Cunkebao/dist
Touchkebao/.specstory/
Serverruntime/
Moncter/提示词/
*.log
.DS_Store
android/app/build/
android/.gradle/
android/local.properties
*.code-workspace
项目整体分析报告.md
Server/docs/traffic_pool_design.md

13
.obsidian/app.json vendored Normal file
View File

@@ -0,0 +1,13 @@
{
"pdfExportSettings": {
"pageSize": "Letter",
"landscape": false,
"margin": "0",
"downscalePercent": 100
},
"promptDelete": false,
"attachmentFolderPath": "./images",
"useMarkdownLinks": true,
"newLinkFormat": "relative",
"alwaysUpdateLinks": true
}

5
.obsidian/appearance.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"enabledCssSnippets": [
"cunkebao-devdoc"
]
}

33
.obsidian/core-plugins.json vendored Normal file
View File

@@ -0,0 +1,33 @@
{
"file-explorer": true,
"global-search": true,
"switcher": true,
"graph": true,
"backlink": true,
"canvas": true,
"outgoing-link": true,
"tag-pane": true,
"footnotes": false,
"properties": true,
"page-preview": true,
"daily-notes": true,
"templates": true,
"note-composer": true,
"command-palette": true,
"slash-command": false,
"editor-status": true,
"bookmarks": true,
"markdown-importer": false,
"zk-prefixer": false,
"random-note": false,
"outline": true,
"word-count": true,
"slides": false,
"audio-recorder": false,
"workspaces": false,
"file-recovery": true,
"publish": false,
"sync": true,
"bases": true,
"webviewer": false
}

93
.obsidian/graph.json vendored Normal file
View File

@@ -0,0 +1,93 @@
{
"collapse-filter": false,
"search": "path:开发文档",
"showTags": true,
"showAttachments": false,
"hideUnresolved": false,
"showOrphans": false,
"collapse-color-groups": false,
"colorGroups": [
{
"query": "path:开发文档/2、架构",
"color": {
"a": 1,
"rgb": 14010688
}
},
{
"query": "path:开发文档/1、需求",
"color": {
"a": 1,
"rgb": 6178001
}
},
{
"query": "path:开发文档/3、原型",
"color": {
"a": 1,
"rgb": 6178001
}
},
{
"query": "path:开发文档/4、前端",
"color": {
"a": 1,
"rgb": 162529
}
},
{
"query": "path:开发文档/5、接口",
"color": {
"a": 1,
"rgb": 162529
}
},
{
"query": "path:开发文档/6、后端",
"color": {
"a": 1,
"rgb": 162529
}
},
{
"query": "path:开发文档/7、数据库",
"color": {
"a": 1,
"rgb": 5606192
}
},
{
"query": "path:开发文档/8、部署",
"color": {
"a": 1,
"rgb": 5606192
}
},
{
"query": "path:开发文档/9、手册",
"color": {
"a": 1,
"rgb": 14010688
}
},
{
"query": "path:开发文档/10、使用手册",
"color": {
"a": 1,
"rgb": 12976168
}
}
],
"collapse-display": true,
"showArrow": true,
"textFadeMultiplier": 0,
"nodeSizeMultiplier": 1,
"lineSizeMultiplier": 1,
"collapse-forces": true,
"centerStrength": 0.5,
"repelStrength": 10,
"linkStrength": 1,
"linkDistance": 200,
"scale": 0.17544920373570672,
"close": true
}

124
.obsidian/snippets/cunkebao-devdoc.css vendored Normal file
View File

@@ -0,0 +1,124 @@
/* 存客宝 v3 · 开发文档 Obsidian 目录配色
* 启用Settings → Appearance → CSS snippets → cunkebao-devdoc
* 依赖Obsidian 文件资源管理器 nav-folder-title data-path 属性
* 规范:与工作手机 workphone-devdoc.css 四类分类色对齐
*/
/* ── 一、需求与架构 ── */
.nav-folder-title[data-path="开发文档/1、需求"] > .nav-folder-title-content {
color: #5E35B1 !important;
font-weight: 600;
}
.nav-folder-title[data-path="开发文档/2、架构"] > .nav-folder-title-content {
color: #1565C0 !important;
font-weight: 600;
}
.nav-folder-title[data-path="开发文档/3、原型"] > .nav-folder-title-content {
color: #5E35B1 !important;
font-weight: 600;
}
/* ── 二、开发与接口 ── */
.nav-folder-title[data-path="开发文档/4、前端"] > .nav-folder-title-content {
color: #0277BD !important;
font-weight: 600;
}
.nav-folder-title[data-path="开发文档/4、前端/UI"] > .nav-folder-title-content {
color: #0288D1 !important;
}
.nav-folder-title[data-path="开发文档/5、接口"] > .nav-folder-title-content {
color: #0277BD !important;
font-weight: 600;
}
.nav-folder-title[data-path^="开发文档/5、接口/0"] > .nav-folder-title-content {
color: #0288D1 !important;
}
.nav-folder-title[data-path="开发文档/5、接口/images"] > .nav-folder-title-content {
color: #78909C !important;
}
.nav-folder-title[data-path="开发文档/5、接口/02-存客宝与工作手机对接"] > .nav-folder-title-content {
color: #FFB300 !important;
font-weight: 600;
}
.nav-folder-title[data-path^="开发文档/5、接口/02-存客宝与工作手机对接/04-CodeGraph"] > .nav-folder-title-content {
color: #78909C !important;
}
.nav-folder-title[data-path="开发文档/6、后端"] > .nav-folder-title-content {
color: #0277BD !important;
font-weight: 600;
}
/* ── 三、部署与数据 ── */
.nav-folder-title[data-path="开发文档/7、数据库"] > .nav-folder-title-content {
color: #558B2F !important;
font-weight: 600;
}
.nav-folder-title[data-path="开发文档/8、部署"] > .nav-folder-title-content {
color: #558B2F !important;
font-weight: 600;
}
.nav-folder-title[data-path="开发文档/8、部署/scripts"] > .nav-folder-title-content {
color: #689F38 !important;
}
.nav-folder-title[data-path="开发文档/8、部署/01-本地环境"] > .nav-folder-title-content {
color: #689F38 !important;
}
.nav-folder-title[data-path="开发文档/8、部署/02-数据库同步"] > .nav-folder-title-content {
color: #558B2F !important;
}
.nav-folder-title[data-path="开发文档/8、部署/03-运维与CI"] > .nav-folder-title-content {
color: #558B2F !important;
}
.nav-folder-title[data-path="开发文档/8、部署/04-部署复盘"] > .nav-folder-title-content {
color: #689F38 !important;
}
.nav-folder-title[data-path="开发文档/8、部署/05-测试验收"] > .nav-folder-title-content {
color: #F9A825 !important;
font-weight: 600;
}
.nav-folder-title[data-path^="开发文档/8、部署/05-测试验收/"] > .nav-folder-title-content {
color: #FFB300 !important;
}
/* ── 四、手册与使用 ── */
.nav-folder-title[data-path="开发文档/9、手册"] > .nav-folder-title-content {
color: #1565C0 !important;
font-weight: 600;
}
.nav-folder-title[data-path="开发文档/9、手册/提示词"] > .nav-folder-title-content {
color: #0288D1 !important;
}
.nav-folder-title[data-path="开发文档/10、使用手册"] > .nav-folder-title-content {
color: #C62828 !important;
font-weight: 600;
}
.nav-folder-title[data-path="开发文档/10、使用手册/存客宝"] > .nav-folder-title-content {
color: #E65100 !important;
font-weight: 600;
}
.nav-folder-title[data-path="开发文档/10、使用手册/存客宝/工作台"] > .nav-folder-title-content {
color: #FF6F00 !important;
}
.nav-folder-title[data-path="开发文档/10、使用手册/截图"] > .nav-folder-title-content {
color: #78909C !important;
}
/* ── 辅助目录 ── */
.nav-folder-title[data-path="开发文档/工作手机对接"] > .nav-folder-title-content {
color: #90A4AE !important;
}
.nav-folder-title[data-path="开发文档/scripts"] > .nav-folder-title-content {
color: #546E7A !important;
}
/* 文档说明头图 */
.markdown-preview-view img[alt="文档说明"],
.markdown-source-view img[alt="文档说明"] {
display: block;
width: 100%;
max-width: 920px;
margin: 0.5em 0 1.2em;
border-radius: 10px;
box-shadow: 0 2px 12px rgba(15, 23, 42, 0.08);
border: 1px solid rgba(148, 163, 184, 0.35);
}

235
.obsidian/workspace.json vendored Normal file
View File

@@ -0,0 +1,235 @@
{
"main": {
"id": "413f86c90d0909cc",
"type": "split",
"children": [
{
"id": "984914435f6a9181",
"type": "tabs",
"children": [
{
"id": "4301052b31068085",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "开发文档/1、需求/修改/存客宝_进行中_20260603.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "存客宝_进行中_20260603"
}
}
]
}
],
"direction": "vertical"
},
"left": {
"id": "28b5f5be79ea5491",
"type": "split",
"children": [
{
"id": "03d49e20527f98d9",
"type": "tabs",
"children": [
{
"id": "b4bd2ff5d3f2ec66",
"type": "leaf",
"state": {
"type": "file-explorer",
"state": {
"sortOrder": "alphabetical",
"autoReveal": false
},
"icon": "lucide-folder-closed",
"title": "文件列表"
}
},
{
"id": "a8ab05904e453c81",
"type": "leaf",
"state": {
"type": "search",
"state": {
"query": "tag:#存客宝",
"matchingCase": false,
"explainSearch": false,
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical"
},
"icon": "lucide-search",
"title": "搜索"
}
},
{
"id": "1b4c70b94399d9a5",
"type": "leaf",
"state": {
"type": "bookmarks",
"state": {},
"icon": "lucide-bookmark",
"title": "书签"
}
}
]
}
],
"direction": "horizontal",
"width": 276.5004348754883
},
"right": {
"id": "213cf3c58fffb77e",
"type": "split",
"children": [
{
"id": "b9b3f94b382e2801",
"type": "tabs",
"children": [
{
"id": "7fb9573cc08dd33f",
"type": "leaf",
"state": {
"type": "backlink",
"state": {
"file": "开发文档/1、需求/修改/20260530_四端已完成与全量检测总汇.md",
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical",
"showSearch": false,
"searchQuery": "",
"backlinkCollapsed": false,
"unlinkedCollapsed": true
},
"icon": "links-coming-in",
"title": "20260530_四端已完成与全量检测总汇 的反向链接列表"
}
},
{
"id": "6e4f013221e7c63d",
"type": "leaf",
"state": {
"type": "outgoing-link",
"state": {
"file": "开发文档/项目文档生成器_核心提示词.md",
"linksCollapsed": false,
"unlinkedCollapsed": true
},
"icon": "links-going-out",
"title": "项目文档生成器_核心提示词 的出链列表"
}
},
{
"id": "7b4a28550b01a9ca",
"type": "leaf",
"state": {
"type": "tag",
"state": {
"sortOrder": "frequency",
"useHierarchy": true,
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-tags",
"title": "标签"
}
},
{
"id": "b9587677fcf19822",
"type": "leaf",
"state": {
"type": "all-properties",
"state": {
"sortOrder": "frequency",
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-archive",
"title": "添加笔记属性"
}
},
{
"id": "9a63f6e4237e033b",
"type": "leaf",
"state": {
"type": "outline",
"state": {
"file": "开发文档/项目文档生成器_核心提示词.md",
"followCursor": false,
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-list",
"title": "项目文档生成器_核心提示词 的大纲"
}
}
]
}
],
"direction": "horizontal",
"width": 300,
"collapsed": true
},
"left-ribbon": {
"hiddenItems": {
"switcher:打开快速切换": false,
"graph:查看关系图谱": false,
"canvas:新建白板": false,
"daily-notes:打开/创建今天的日记": false,
"templates:插入模板": false,
"command-palette:打开命令面板": false,
"bases:新建数据库": false
}
},
"active": "4301052b31068085",
"lastOpenFiles": [
"Touchkebao/dist/xiaobao-ai.png",
"Touchkebao/dist/websdk.js",
"Touchkebao/dist/manifest.json",
"Touchkebao/dist/logo.png",
"Touchkebao/dist/index.html",
"Touchkebao/dist/assets/vendor-fZy-8e4I.js",
"Touchkebao/dist/assets/utils-Bc0vVQOI.js",
"Touchkebao/dist/assets/ui-DsBy32W5.js",
"Touchkebao/dist/assets/ui-DmZQDU-f.css",
"Touchkebao/dist/assets/other/wither.png",
"Touchkebao/dist/assets/other/sun.png",
"Touchkebao/dist/assets/other/rose.png",
"Touchkebao/dist/assets/other/poop.png",
"Touchkebao/dist/assets/other/moon.png",
"Touchkebao/dist/assets/other/lips.png",
"Touchkebao/dist/assets/other/knife.png",
"Touchkebao/dist/assets/other/heart.png",
"Touchkebao/dist/assets/other/coffee.png",
"Touchkebao/dist/assets/other",
"Touchkebao/dist/assets/index-DfxITDxP.css",
"Touchkebao/dist/assets/index-DHDH-NpT.js",
"开发文档/8、部署/开发进度_五端全量验收_20260603.md",
"开发文档/1、需求/修改/官网_进行中_20260603.md",
"开发文档/1、需求/修改/未完成项与跨仓验收汇总.md",
"开发文档/10、项目管理/已完成/触客宝_0603日终复盘_20260603.md",
"Server/public/uploads/knowledge/914/ingest/20260603_203119_10_转化话术C_3.md",
"Server/public/uploads/knowledge/914/ingest/20260603_203119_10_引流玩法B_2.md",
"Server/public/uploads/knowledge/914/ingest/20260603_203119_10_测试套餐A_1.md",
"开发文档/6、后端/核心算法清单.md",
"开发文档/6、后端/核心算法图解手册.md",
"开发文档/10、项目管理/账号密码与登录环境一览.md",
"开发文档/10、项目管理/开发进度.md",
"Server/public/uploads/knowledge/914/ingest/20260603_184809_10_转化话术C_3.md",
"Server/public/uploads/knowledge/914/ingest/20260603_184809_10_引流玩法B_2.md",
"Server/public/uploads/knowledge/914/ingest/20260603_184809_10_测试套餐A_1.md",
"Server/public/uploads/knowledge/914/ingest/20260603_184041_10_转化话术C_3.md",
"Server/public/uploads/knowledge/914/ingest/20260603_184041_10_引流玩法B_2.md",
"Server/public/uploads/knowledge/914/ingest/20260603_184041_10_测试套餐A_1.md",
"开发文档/1、需求/修改/AI数智员工_进行中_20260603.md",
"Server/public/uploads/knowledge/914/ingest/20260603_183530_10_转化话术C_3.md",
"Server/public/uploads/knowledge/914/ingest/20260603_183530_10_引流玩法B_2.md",
"Server/public/uploads/knowledge/914/ingest/20260603_183530_10_测试套餐A_1.md",
"开发文档/1、需求/修改/存客宝_进行中_20260603.md",
"开发文档/1、需求/修改/触客宝_进行中_20260603.md",
"Server/public/uploads/knowledge/914/ingest/20260603_172928_13_转化话术C_3.md",
"Server/public/uploads/knowledge/914/ingest/20260603_172928_13_测试套餐A_1.md"
]
}

View File

@@ -1,4 +0,0 @@
{
"presets": ["next/babel"]
}

9
Cunkebao/.dockerignore Normal file
View File

@@ -0,0 +1,9 @@
node_modules
dist
.git
.env
.env.*
*.log
.DS_Store
coverage
.vite

12
Cunkebao/.env.development Normal file
View File

@@ -0,0 +1,12 @@
# 开发环境直接使用线上 API腾讯云数据库
# 如需使用本地后端,改为 VITE_API_BASE_URL=/api 并确保 vite.config.ts 代理配置正确
VITE_API_BASE_URL=https://ckbapi.quwanzhi.com
VITE_API_BASE_URL2=https://kf.quwanzhi.com:9991
VITE_API_WS_URL=wss://kf.quwanzhi.com:9993
VITE_APP_TITLE=存客宝
# 开发环境自动登录配置(仅开发环境使用,生产环境禁止使用)
# WARNING: 此配置仅用于开发调试,正式环境部署时必须删除或禁用
VITE_DEV_AUTO_LOGIN=true
VITE_DEV_ACCOUNT=admin
VITE_DEV_PASSWORD=k123456

1
Cunkebao/.env.local Normal file
View File

@@ -0,0 +1 @@
NEXT_PUBLIC_API_BASE_URL= http://yishi.com

6
Cunkebao/.env.production Normal file
View File

@@ -0,0 +1,6 @@
# 基础环境变量示例
VITE_API_BASE_URL=https://ckbapi.quwanzhi.com
VITE_API_BASE_URL2=https://kf.quwanzhi.com:9991
VITE_API_WS_URL=wss://kf.quwanzhi.com:9993
# VITE_API_BASE_URL=http://www.yishi.com
VITE_APP_TITLE=存客宝

64
Cunkebao/.eslintrc.js Normal file
View File

@@ -0,0 +1,64 @@
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended", // 这个配置会自动处理大部分冲突
],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 12,
sourceType: "module",
},
plugins: ["react", "react-hooks", "@typescript-eslint", "prettier"],
rules: {
"prettier/prettier": "error",
"react/react-in-jsx-scope": "off",
"@typescript-eslint/no-unused-vars": "warn",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unnecessary-type-constraint": "warn",
"react/prop-types": "off",
"linebreak-style": "off",
"eol-last": "off",
"no-empty": "warn",
"prefer-const": "warn",
// 确保与 Prettier 完全兼容
"comma-dangle": "off",
"comma-spacing": "off",
"comma-style": "off",
"object-curly-spacing": "off",
"array-bracket-spacing": "off",
indent: "off",
quotes: "off",
semi: "off",
"arrow-parens": "off",
"no-multiple-empty-lines": "off",
"max-len": "off",
"space-before-function-paren": "off",
"space-before-blocks": "off",
"keyword-spacing": "off",
"space-infix-ops": "off",
"space-in-parens": "off",
"space-in-brackets": "off",
"object-property-newline": "off",
"array-element-newline": "off",
"function-paren-newline": "off",
"object-curly-newline": "off",
"array-bracket-newline": "off",
},
settings: {
react: {
version: "detect",
},
},
};

27
Cunkebao/.gitattributes vendored Normal file
View File

@@ -0,0 +1,27 @@
# 设置默认行为如果core.autocrlf没有设置Git会自动处理行尾符
* text=auto
# 明确指定文本文件使用LF
*.js text eol=lf
*.jsx text eol=lf
*.ts text eol=lf
*.tsx text eol=lf
*.json text eol=lf
*.css text eol=lf
*.scss text eol=lf
*.html text eol=lf
*.md text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
# 二进制文件
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.svg binary
*.woff binary
*.woff2 binary
*.ttf binary
*.eot binary

32
Cunkebao/.gitignore vendored
View File

@@ -1,23 +1,11 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
node_modules/
dist/
build/
yarn.lock
.env
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
dist/*
.cursorindexingignore
*.zip
.idea/
.next/

View File

@@ -1 +0,0 @@
# 请将伪静态规则或自定义Apache配置填写到此处

13
Cunkebao/.prettierrc Normal file
View File

@@ -0,0 +1,13 @@
{
"semi": true,
"trailingComma": "all",
"singleQuote": false,
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"endOfLine": "lf",
"bracketSpacing": true,
"arrowParens": "avoid",
"jsxSingleQuote": false,
"quoteProps": "as-needed"
}

11
Cunkebao/.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"recommendations": [
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"bradlc.vscode-tailwindcss",
"ms-vscode.vscode-typescript-next",
"formulahendry.auto-rename-tag",
"christian-kohler.path-intellisense",
"ms-vscode.vscode-json"
]
}

45
Cunkebao/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,45 @@
{
"files.eol": "\n",
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
},
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"eslint.format.enable": false,
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"typescript.preferences.importModuleSpecifier": "relative",
"typescript.suggest.autoImports": true,
"editor.tabSize": 2,
"editor.insertSpaces": true,
"editor.detectIndentation": false
}

View File

@@ -1 +0,0 @@
nKNDFrnOs9-cf7FZzW7Cg4MpIxhtE8pZ1etiT_7NPWA.2wKeISBpPoGcCt72TdgVrBMxtnnieXof2OZkRdHKCJI

23
Cunkebao/Dockerfile Normal file
View File

@@ -0,0 +1,23 @@
# 存客宝前端 - React + Vite
FROM node:20-alpine AS builder
WORKDIR /app
RUN npm install -g pnpm
ARG VITE_API_BASE_URL=
ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
FROM nginx:alpine
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -1,266 +0,0 @@
# 内客宝 - 智能获客管理平台
## 📋 项目简介
内客宝是一个专业的微信获客和流量管理平台,基于 React 技术栈构建。平台提供智能化的客户获取、管理和运营解决方案,集成了多种自动化工具,帮助企业高效管理微信营销活动。
## 🚀 技术栈详解
### 核心框架
- **React 18.2.0** - 现代化的用户界面库
- **TypeScript 4.9.5** - 类型安全的 JavaScript 超集
- **Create React App (CRA) 5.0.1** - React 应用脚手架
- **React Router DOM 6.20.0** - 客户端路由管理
### 构建工具
- **CRACO 7.1.0** - Create React App Configuration Override
- 支持自定义 webpack 配置
- 路径别名配置
- 构建优化
### UI 组件库
- **Radix UI** - 无样式的可访问组件库
- 完整的组件生态系统30+ 组件)
- 优秀的无障碍访问支持
- 高度可定制
- **Tailwind CSS 3.4.17** - 实用优先的 CSS 框架
- 响应式设计支持
- 自定义主题配置
- 原子化 CSS 类
### 图标和样式
- **Lucide React 0.454.0** - 精美的图标库
- **Tailwind CSS Animate** - CSS 动画库
- **Class Variance Authority** - 组件变体管理
- **Tailwind Merge** - Tailwind 类名合并工具
### 状态管理和表单
- **React Hook Form 7.54.1** - 高性能表单库
- **Zod 3.24.1** - TypeScript 优先的模式验证
- **@hookform/resolvers 3.9.1** - 表单验证解析器
### 数据可视化
- **Recharts** - 基于 React 的图表库
- **Chart.js 4.5.0** - 灵活的图表库
- **@ant-design/plots** - Ant Design 图表组件
### HTTP 请求和数据处理
- **Axios 1.6.0** - HTTP 客户端
- **Crypto-js 4.2.0** - 加密库
- **Date-fns** - 日期处理库
- **XLSX 0.18.5** - Excel 文件处理
### 通知和反馈
- **React Hot Toast 2.5.2** - 轻量级通知库
- **Sonner 1.7.4** - 现代化 Toast 组件
### 高级组件
- **@tanstack/react-table** - 功能强大的表格组件
- **Embla Carousel React 8.5.1** - 轮播组件
- **React Resizable Panels 2.1.7** - 可调整大小的面板
- **Vaul 0.9.6** - 抽屉组件
- **Input OTP 1.4.1** - OTP 输入组件
- **React Day Picker** - 日期选择器
### 开发工具
- **PostCSS 8** - CSS 后处理器
- **Autoprefixer 10.4.20** - CSS 前缀自动添加
- **ESLint** - 代码质量检查
- **Jest** - 单元测试框架
- **Testing Library** - React 测试工具
## 📁 项目结构
```
nkebao/
├── public/ # 静态资源
├── src/ # 源代码
│ ├── api/ # API 接口封装
│ ├── components/ # 全局组件
│ │ ├── ui/ # UI 基础组件
│ │ └── icons/ # 图标组件
│ ├── config/ # 配置文件
│ ├── contexts/ # React Context
│ ├── hooks/ # 自定义 Hooks
│ ├── pages/ # 页面组件
│ │ ├── workspace/ # 工作台模块
│ │ │ ├── auto-like/ # 自动点赞
│ │ │ ├── auto-group/ # 自动建群
│ │ │ ├── group-push/ # 群消息推送
│ │ │ ├── moments-sync/ # 朋友圈同步
│ │ │ ├── ai-assistant/ # AI 对话助手
│ │ │ └── traffic-distribution/ # 流量分发
│ │ ├── devices/ # 设备管理
│ │ ├── scenarios/ # 场景管理
│ │ ├── content/ # 内容管理
│ │ └── ...
│ ├── types/ # TypeScript 类型定义
│ ├── utils/ # 工具函数
│ ├── App.tsx # 应用根组件
│ └── index.tsx # 应用入口
├── craco.config.js # CRACO 配置
├── tailwind.config.js # Tailwind CSS 配置
├── tsconfig.json # TypeScript 配置
└── package.json # 项目依赖
```
## 🎯 核心功能模块
### 工作台 (Workspace)
- **自动点赞** - 智能点赞管理和配置
- **自动建群** - 群组自动化创建和管理
- **群消息推送** - 群组消息批量发送
- **朋友圈同步** - 内容同步和发布
- **AI 对话助手** - 智能客服和对话管理
- **流量分发** - 流量分配和策略管理
### 设备管理 (Devices)
- 设备状态监控和配置
- 设备性能分析
- 设备权限管理
### 场景管理 (Scenarios)
- 营销场景配置
- 自动化流程设计
- 场景效果分析
### 内容管理 (Content)
- 内容创建与编辑
- 内容模板管理
- 内容发布调度
### 其他模块
- 用户管理 (Users)
- 订单管理 (Orders)
- 流量池管理 (Traffic Pool)
- 联系人导入 (Contact Import)
## 🛠️ 开发指南
### 环境要求
- **Node.js** 16+
- **npm** 或 **yarn**
### 安装依赖
```bash
# 使用 npm
npm install
# 使用 yarn
yarn install
```
### 开发环境启动
```bash
# 使用 npm
npm start
# 使用 yarn
yarn start
```
### 构建生产版本
```bash
# 使用 npm
npm run build
# 使用 yarn
yarn build
```
### 运行测试
```bash
# 使用 npm
npm test
# 使用 yarn
yarn test
```
## 🔧 配置说明
### 路径别名配置
项目使用 CRACO 配置了路径别名:
```javascript
'@': path.resolve(__dirname, 'src'),
'@/components': path.resolve(__dirname, 'src/components'),
'@/api': path.resolve(__dirname, 'src/api'),
'@/types': path.resolve(__dirname, 'src/types'),
'@/hooks': path.resolve(__dirname, 'src/hooks'),
'@/utils': path.resolve(__dirname, 'src/utils'),
'@/styles': path.resolve(__dirname, 'src/styles'),
'@/pages': path.resolve(__dirname, 'src/pages'),
```
### Tailwind CSS 配置
- 自定义字体大小和间距
- 响应式断点配置
- 主题颜色系统
### TypeScript 配置
- 严格模式启用
- 路径映射配置
- JSX 支持
## 📱 响应式设计
项目采用移动优先的响应式设计:
- 支持桌面端、平板端、移动端
- 自适应布局组件
- 触摸友好的交互设计
## 🎨 UI 设计系统
### 设计原则
- 简洁现代的设计风格
- 一致的用户体验
- 无障碍访问支持
### 组件库特点
- 基于 Radix UI 的高质量组件
- 完整的表单组件系统
- 数据展示组件
- 导航和布局组件
## 🔒 安全特性
- 身份验证和授权
- API 请求拦截
- 数据验证和清理
- 加密功能支持
## 📊 性能优化
- 代码分割和懒加载
- 组件优化
- 缓存策略
- 包大小优化
## 🧪 测试策略
- 单元测试 (Jest + Testing Library)
- 组件测试
- 集成测试支持
## 🤝 贡献指南
1. Fork 项目
2. 创建功能分支 (`git checkout -b feature/AmazingFeature`)
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`)
5. 创建 Pull Request
## 📄 许可证
本项目采用 MIT 许可证。
## 📞 联系方式
如有问题或建议,请联系开发团队。
---
**项目名称**: 内客宝 (nkebao2)
**版本**: 0.1.0
**技术栈**: React + TypeScript + CRA + Tailwind CSS
**最后更新**: 2024年12月

View File

@@ -1,192 +0,0 @@
import { api } from "@/lib/api";
import type {
ApiResponse,
Device,
DeviceStats,
DeviceTaskRecord,
PaginatedResponse,
QueryDeviceParams,
CreateDeviceParams,
UpdateDeviceParams,
DeviceStatus,
ServerDevice,
ServerDevicesResponse
} from "@/types/device"
const API_BASE = "/api/devices"
// 获取设备列表 - 连接到服务器/v1/devices接口
export const fetchDeviceList = async (page: number = 1, limit: number = 20, keyword?: string): Promise<ServerDevicesResponse> => {
const params = new URLSearchParams();
params.append('page', page.toString());
params.append('limit', limit.toString());
if (keyword) {
params.append('keyword', keyword);
}
return api.get<ServerDevicesResponse>(`/v1/devices?${params.toString()}`);
};
// 获取设备详情 - 连接到服务器/v1/devices/:id接口
export const fetchDeviceDetail = async (id: string | number): Promise<ApiResponse<any>> => {
return api.get<ApiResponse<any>>(`/v1/devices/${id}`);
};
// 获取设备关联的微信账号
export const fetchDeviceRelatedAccounts = async (id: string | number): Promise<ApiResponse<any>> => {
return api.get<ApiResponse<any>>(`/v1/wechats/related-device/${id}`);
};
// 获取设备操作记录
export const fetchDeviceHandleLogs = async (id: string | number, page: number = 1, limit: number = 10): Promise<ApiResponse<any>> => {
return api.get<ApiResponse<any>>(`/v1/devices/${id}/handle-logs?page=${page}&limit=${limit}`);
};
// 更新设备任务配置
export const updateDeviceTaskConfig = async (
config: {
deviceId: string | number;
autoAddFriend?: boolean;
autoReply?: boolean;
momentsSync?: boolean;
aiChat?: boolean;
}
): Promise<ApiResponse<any>> => {
return api.post<ApiResponse<any>>(`/v1/devices/task-config`, {
...config
});
};
// 删除设备
export const deleteDevice = async (id: number): Promise<ApiResponse<any>> => {
return api.delete<ApiResponse<any>>(`/v1/devices/${id}`);
};
// 设备管理API
export const deviceApi = {
// 创建设备
async create(params: CreateDeviceParams): Promise<ApiResponse<Device>> {
const response = await fetch(`${API_BASE}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(params),
})
return response.json()
},
// 更新设备
async update(params: UpdateDeviceParams): Promise<ApiResponse<Device>> {
const response = await fetch(`${API_BASE}/${params.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(params),
})
return response.json()
},
// 获取设备详情
async getById(id: string): Promise<ApiResponse<Device>> {
const response = await fetch(`${API_BASE}/${id}`)
return response.json()
},
// 查询设备列表
async query(params: QueryDeviceParams): Promise<ApiResponse<PaginatedResponse<Device>>> {
// 创建一个新对象用于构建URLSearchParams
const queryParams: Record<string, string> = {};
// 按需将params中的属性添加到queryParams
if (params.keyword) queryParams.keyword = params.keyword;
if (params.status) queryParams.status = params.status;
if (params.type) queryParams.type = params.type;
if (params.page) queryParams.page = params.page.toString();
if (params.pageSize) queryParams.pageSize = params.pageSize.toString();
// 特殊处理需要JSON序列化的属性
if (params.tags) queryParams.tags = JSON.stringify(params.tags);
if (params.dateRange) queryParams.dateRange = JSON.stringify(params.dateRange);
// 构建查询字符串
const queryString = new URLSearchParams(queryParams).toString();
const response = await fetch(`${API_BASE}?${queryString}`)
return response.json()
},
// 删除设备
async delete(id: string): Promise<ApiResponse<void>> {
const response = await fetch(`${API_BASE}/${id}`, {
method: "DELETE",
})
return response.json()
},
// 重启设备
async restart(id: string): Promise<ApiResponse<void>> {
const response = await fetch(`${API_BASE}/${id}/restart`, {
method: "POST",
})
return response.json()
},
// 解绑设备
async unbind(id: string): Promise<ApiResponse<void>> {
const response = await fetch(`${API_BASE}/${id}/unbind`, {
method: "POST",
})
return response.json()
},
// 获取设备统计数据
async getStats(id: string): Promise<ApiResponse<DeviceStats>> {
const response = await fetch(`${API_BASE}/${id}/stats`)
return response.json()
},
// 获取设备任务记录
async getTaskRecords(id: string, page = 1, pageSize = 20): Promise<ApiResponse<PaginatedResponse<DeviceTaskRecord>>> {
const response = await fetch(`${API_BASE}/${id}/tasks?page=${page}&pageSize=${pageSize}`)
return response.json()
},
// 批量更新设备标签
async updateTags(ids: string[], tags: string[]): Promise<ApiResponse<void>> {
const response = await fetch(`${API_BASE}/tags`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ deviceIds: ids, tags }),
})
return response.json()
},
// 批量导出设备数据
async exportDevices(ids: string[]): Promise<Blob> {
const response = await fetch(`${API_BASE}/export`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ deviceIds: ids }),
})
return response.blob()
},
// 检查设备在线状态
async checkStatus(ids: string[]): Promise<ApiResponse<Record<string, DeviceStatus>>> {
const response = await fetch(`${API_BASE}/status`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ deviceIds: ids }),
})
return response.json()
},
}

View File

@@ -1,74 +0,0 @@
import { NextResponse } from "next/server"
import type { CreateScenarioParams, QueryScenarioParams, ScenarioBase, ApiResponse } from "@/types/scenario"
// 获客场景路由处理
export async function POST(request: Request) {
try {
const body: CreateScenarioParams = await request.json()
// TODO: 实现创建场景的具体逻辑
const scenario: ScenarioBase = {
id: "generated-id",
...body,
status: "draft",
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
creator: "current-user-id",
}
const response: ApiResponse<ScenarioBase> = {
code: 0,
message: "创建成功",
data: scenario,
}
return NextResponse.json(response)
} catch (error) {
return NextResponse.json(
{
code: 500,
message: "创建失败",
data: null,
},
{ status: 500 },
)
}
}
export async function GET(request: Request) {
try {
const { searchParams } = new URL(request.url)
const params: QueryScenarioParams = {
type: searchParams.get("type") as any,
status: searchParams.get("status") as any,
keyword: searchParams.get("keyword") || undefined,
dateRange: searchParams.get("dateRange") ? JSON.parse(searchParams.get("dateRange")!) : undefined,
page: Number(searchParams.get("page")) || 1,
pageSize: Number(searchParams.get("pageSize")) || 20,
}
// TODO: 实现查询场景列表的具体逻辑
return NextResponse.json({
code: 0,
message: "查询成功",
data: {
items: [],
total: 0,
page: params.page,
pageSize: params.pageSize,
totalPages: 0,
},
})
} catch (error) {
return NextResponse.json(
{
code: 500,
message: "查询失败",
data: null,
},
{ status: 500 },
)
}
}

View File

@@ -1,126 +0,0 @@
import { request } from "@/lib/api"
export interface ApiResponse<T = any> {
code: number;
msg: string;
data: T | null;
}
// 服务器返回的场景数据类型
export interface SceneItem {
id: number;
name: string;
image: string;
status: number;
createTime: number;
updateTime: number | null;
deleteTime: number | null;
}
// 服务器返回的场景列表响应类型
export interface ScenesResponse {
code: number;
msg: string;
data: SceneItem[];
}
// 前端使用的场景数据类型
export interface Channel {
id: string;
name: string;
icon: string;
stats: {
daily: number;
growth: number;
};
link?: string;
plans?: Plan[];
}
// 计划类型
export interface Plan {
id: string;
name: string;
isNew?: boolean;
status: "active" | "paused" | "completed";
acquisitionCount: number;
}
/**
* 获取获客场景列表
*
* @param params 查询参数
* @returns 获客场景列表
*/
export const fetchScenes = async (params: {
page?: number;
limit?: number;
keyword?: string;
} = {}): Promise<ScenesResponse> => {
const { page = 1, limit = 10, keyword = "" } = params;
const queryParams = new URLSearchParams();
queryParams.append("page", String(page));
queryParams.append("limit", String(limit));
if (keyword) {
queryParams.append("keyword", keyword);
}
try {
return await request<ScenesResponse>(`/v1/plan/scenes?${queryParams.toString()}`);
} catch (error) {
console.error("Error fetching scenes:", error);
// 返回一个错误响应
return {
code: 500,
msg: "获取场景列表失败",
data: []
};
}
};
/**
* 将服务器返回的场景数据转换为前端展示需要的格式
*
* @param item 服务器返回的场景数据
* @returns 前端展示的场景数据
*/
export const transformSceneItem = (item: SceneItem): Channel => {
// 为每个场景生成随机的"今日"数据和"增长百分比"
const dailyCount = Math.floor(Math.random() * 100);
const growthPercent = Math.floor(Math.random() * 40) - 10; // -10% 到 30% 的随机值
// 默认图标(如果服务器没有返回)
const defaultIcon = "/assets/icons/poster-icon.svg";
return {
id: String(item.id),
name: item.name,
icon: item.image || defaultIcon,
stats: {
daily: dailyCount,
growth: growthPercent
}
};
};
/**
* 获取场景详情
*
* @param id 场景ID
* @returns 场景详情
*/
export const fetchSceneDetail = async (id: string | number): Promise<ApiResponse<SceneItem>> => {
try {
return await request<ApiResponse<SceneItem>>(`/v1/plan/scenes/${id}`);
} catch (error) {
console.error("Error fetching scene detail:", error);
return {
code: 500,
msg: "获取场景详情失败",
data: null
};
}
};

View File

@@ -1,203 +0,0 @@
import { api } from "@/lib/api";
import {
ServerWechatAccountsResponse,
QueryWechatAccountParams,
} from "@/types/wechat-account";
// 添加接口返回数据类型定义
interface WechatAccountSummary {
accountAge: string;
activityLevel: {
allTimes: number;
dayTimes: number;
};
accountWeight: {
scope: number;
ageWeight: number;
activityWeigth: number;
restrictWeight: number;
realNameWeight: number;
};
statistics: {
todayAdded: number;
addLimit: number;
};
restrictions: {
id: number;
level: string;
reason: string;
date: string;
}[];
}
interface WechatAccountSummaryResponse {
code: number;
msg: string;
data: WechatAccountSummary;
}
/**
* 获取微信账号列表
* @param params 查询参数
* @returns 微信账号列表响应
*/
export const fetchWechatAccountList = async (params: QueryWechatAccountParams = {}): Promise<ServerWechatAccountsResponse> => {
const queryParams = new URLSearchParams();
// 添加查询参数
if (params.page) queryParams.append('page', params.page.toString());
if (params.limit) queryParams.append('limit', params.limit.toString());
if (params.keyword) queryParams.append('nickname', params.keyword); // 使用nickname作为关键词搜索参数
if (params.sort) queryParams.append('sort', params.sort);
if (params.order) queryParams.append('order', params.order);
// 发起API请求
return api.get<ServerWechatAccountsResponse>(`/v1/wechats?${queryParams.toString()}`);
};
/**
* 刷新微信账号状态
* @returns 刷新结果
*/
export const refreshWechatAccounts = async (): Promise<{ code: number; msg: string; data: any }> => {
return api.put<{ code: number; msg: string; data: any }>('/v1/wechats/refresh', {});
};
/**
* 执行微信好友转移
* @param sourceId 源微信账号ID
* @param targetId 目标微信账号ID
* @returns 转移结果
*/
export const transferWechatFriends = async (sourceId: string | number, targetId: string | number): Promise<{ code: number; msg: string; data: any }> => {
return api.post<{ code: number; msg: string; data: any }>('/v1/wechats/transfer-friends', {
source_id: sourceId,
target_id: targetId
});
};
/**
* 将服务器返回的微信账号数据转换为前端使用的格式
* @param serverAccount 服务器返回的微信账号数据
* @returns 前端使用的微信账号数据
*/
export const transformWechatAccount = (serverAccount: any): import("@/types/wechat-account").WechatAccount => {
// 从deviceInfo中提取设备信息
let deviceId = '';
let deviceName = '';
if (serverAccount.deviceInfo) {
// 尝试解析设备信息字符串
const deviceInfo = serverAccount.deviceInfo.split(' ');
if (deviceInfo.length > 0) {
// 提取数字部分作为设备ID确保是整数
const possibleId = deviceInfo[0].trim();
// 验证是否为数字
deviceId = /^\d+$/.test(possibleId) ? possibleId : '';
// 提取设备名称
if (deviceInfo.length > 1) {
deviceName = deviceInfo[1] ? deviceInfo[1].replace(/[()]/g, '').trim() : '';
}
}
}
// 如果从deviceInfo无法获取有效的设备ID使用imei作为备选
if (!deviceId && serverAccount.imei) {
deviceId = serverAccount.imei;
}
// 如果仍然没有设备ID使用微信账号的ID作为最后的备选
if (!deviceId && serverAccount.id) {
deviceId = serverAccount.id.toString();
}
// 如果没有设备名称,使用备用名称
if (!deviceName) {
deviceName = serverAccount.deviceMemo || '未命名设备';
}
// 假设每天最多可添加20个好友
const maxDailyAdds = 20;
const todayAdded = serverAccount.todayNewFriendCount || 0;
return {
id: serverAccount.id.toString(),
avatar: serverAccount.avatar || '',
nickname: serverAccount.nickname || serverAccount.accountNickname || '未命名',
wechatId: serverAccount.wechatId || '',
deviceId,
deviceName,
friendCount: serverAccount.totalFriend || 0,
todayAdded,
remainingAdds: serverAccount.canAddFriendCount || (maxDailyAdds - todayAdded),
maxDailyAdds,
status: serverAccount.wechatAlive === 1 ? "normal" : "abnormal" as "normal" | "abnormal",
lastActive: new Date().toLocaleString() // 服务端未提供,使用当前时间
};
};
/**
* 获取微信好友列表
* @param wechatId 微信账号ID
* @param page 页码
* @param pageSize 每页数量
* @param searchQuery 搜索关键词
* @returns 好友列表数据
*/
export const fetchWechatFriends = async (wechatId: string, page: number = 1, pageSize: number = 20, searchQuery: string = '') => {
try {
return api.get(`/v1/wechats/${wechatId}/friends?page=${page}&limit=${pageSize}${searchQuery ? `&search=${searchQuery}` : ''}`, true);
} catch (error) {
console.error("获取好友列表失败:", error);
throw error;
}
};
/**
* 获取微信账号概览信息
* @param id 微信账号ID
* @returns 微信账号概览信息
*/
export const fetchWechatAccountSummary = async (wechatIdid: string): Promise<WechatAccountSummaryResponse> => {
try {
return api.get<WechatAccountSummaryResponse>(`/v1/wechats/${wechatIdid}/summary`);
} catch (error) {
console.error("获取账号概览失败:", error);
throw error;
}
};
/**
* 获取好友详情信息
* @param wechatId 微信账号ID
* @param friendId 好友ID
* @returns 好友详情信息
*/
export interface WechatFriendDetail {
id: number;
avatar: string;
nickname: string;
region: string;
wechatId: string;
addDate: string;
tags: string[];
playDate: string;
memo: string;
source: string;
}
interface WechatFriendDetailResponse {
code: number;
msg: string;
data: WechatFriendDetail;
}
export const fetchWechatFriendDetail = async (wechatId: string): Promise<WechatFriendDetailResponse> => {
try {
return api.get<WechatFriendDetailResponse>(`/v1/wechats/${wechatId}`);
} catch (error) {
console.error("获取好友详情失败:", error);
throw error;
}
};

View File

@@ -1,20 +0,0 @@
import { NextResponse } from "next/server"
import type { OrderFormData } from "@/types/acquisition"
export async function POST(request: Request, { params }: { params: { planId: string } }) {
try {
const data: OrderFormData = await request.json()
// 这里应该添加实际的数据库存储逻辑
console.log("Received order:", data, "for plan:", params.planId)
// 模拟成功响应
return NextResponse.json({
success: true,
message: "订单已成功提交",
})
} catch (error) {
return NextResponse.json({ success: false, message: "订单提交失败" }, { status: 500 })
}
}

View File

@@ -1,69 +0,0 @@
// API请求工具函数
import { toast } from "@/components/ui/use-toast"
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "https://api.example.com"
// 带有认证的请求函数
export async function authFetch(url: string, options: RequestInit = {}) {
const token = localStorage.getItem("token")
// 合并headers
let headers = { ...options.headers }
// 如果有token添加到请求头
if (token) {
headers = {
...headers,
Token: `${token}`,
}
}
try {
const response = await fetch(`${API_BASE_URL}${url}`, {
...options,
headers,
})
const data = await response.json()
// 检查token是否过期仅当有token时
if (token && (data.code === 401 || data.code === 403)) {
// 清除token
localStorage.removeItem("token")
// 暂时不重定向到登录页
// if (typeof window !== "undefined") {
// window.location.href = "/login"
// }
console.warn("登录已过期")
}
return data
} catch (error) {
console.error("API请求错误:", error)
toast({
variant: "destructive",
title: "请求失败",
description: error instanceof Error ? error.message : "网络错误,请稍后重试",
})
throw error
}
}
// 不需要认证的请求函数
export async function publicFetch(url: string, options: RequestInit = {}) {
try {
const response = await fetch(`${API_BASE_URL}${url}`, options)
return await response.json()
} catch (error) {
console.error("API请求错误:", error)
toast({
variant: "destructive",
title: "请求失败",
description: error instanceof Error ? error.message : "网络错误,请稍后重试",
})
throw error
}
}

View File

@@ -1,82 +0,0 @@
import { NextResponse } from "next/server"
import type {
CreateDeviceParams,
QueryDeviceParams,
Device,
ApiResponse,
DeviceStatus,
DeviceType,
} from "@/types/device"
// 设备管理路由处理
export async function POST(request: Request) {
try {
const body: CreateDeviceParams = await request.json()
// TODO: 实现创建设备的具体逻辑
const device: Device = {
id: "generated-id",
...body,
status: DeviceStatus.OFFLINE,
lastOnlineTime: new Date().toISOString(),
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
}
const response: ApiResponse<Device> = {
code: 0,
message: "创建成功",
data: device,
}
return NextResponse.json(response)
} catch (error) {
return NextResponse.json(
{
code: 500,
message: "创建失败",
data: null,
},
{ status: 500 },
)
}
}
export async function GET(request: Request) {
try {
const { searchParams } = new URL(request.url)
const params: QueryDeviceParams = {
keyword: searchParams.get("keyword") || undefined,
status: (searchParams.get("status") as DeviceStatus) || undefined,
type: (searchParams.get("type") as DeviceType) || undefined,
tags: searchParams.get("tags") ? JSON.parse(searchParams.get("tags")!) : undefined,
dateRange: searchParams.get("dateRange") ? JSON.parse(searchParams.get("dateRange")!) : undefined,
page: Number(searchParams.get("page")) || 1,
pageSize: Number(searchParams.get("pageSize")) || 20,
}
// TODO: 实现查询设备列表的具体逻辑
return NextResponse.json({
code: 0,
message: "查询成功",
data: {
items: [],
total: 0,
page: params.page,
pageSize: params.pageSize,
totalPages: 0,
},
})
} catch (error) {
return NextResponse.json(
{
code: 500,
message: "查询失败",
data: null,
},
{ status: 500 },
)
}
}

View File

@@ -1,405 +0,0 @@
"use client"
import { useState } from "react"
import { ChevronLeft, Copy, Check, Info } from "lucide-react"
import { Button } from "@/components/ui/button"
import { useRouter } from "next/navigation"
import { useToast } from "@/components/ui/use-toast"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { getApiGuideForScenario } from "@/docs/api-guide"
import { Badge } from "@/components/ui/badge"
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'https://yishi.com'
export default function ApiDocPage({ params }: { params: { channel: string; id: string } }) {
const router = useRouter()
const { toast } = useToast()
const [copiedExample, setCopiedExample] = useState<string | null>(null)
const apiGuide = getApiGuideForScenario(params.id, params.channel)
// 假设 fullUrl 和 apiKey 可通过 props 或接口获取,这里用演示值
const [apiKey] = useState("naxf1-82h2f-vdwcm-rrhpm-q9hd1")
const [fullUrl] = useState("/v1/api/scenarios")
const testUrl = fullUrl.startsWith("http") ? fullUrl : `${API_BASE_URL}${fullUrl}`
const copyToClipboard = (text: string, exampleId: string) => {
navigator.clipboard.writeText(text)
setCopiedExample(exampleId)
toast({
title: "已复制代码",
description: "代码示例已复制到剪贴板",
})
setTimeout(() => {
setCopiedExample(null)
}, 2000)
}
return (
<div className="min-h-screen bg-gray-50">
<header className="sticky top-0 z-10 bg-white border-b">
<div className="flex items-center justify-between h-14 px-4">
<div className="flex items-center">
<Button variant="ghost" size="icon" onClick={() => router.back()}>
<ChevronLeft className="h-5 w-5" />
</Button>
<h1 className="ml-2 text-lg font-medium"></h1>
</div>
</div>
</header>
<div className="container mx-auto py-6 px-4 max-w-4xl">
<Card className="mb-6">
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>使 <b>apiKey</b> </CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex items-center gap-2">
<Info className="h-4 w-4 text-blue-500" />
<p className="text-sm text-gray-700">
</p>
</div>
<div className="rounded-md bg-amber-50 p-4 border border-amber-200">
<p className="text-sm text-amber-800">
<strong></strong> API密钥使
</p>
</div>
</div>
</CardContent>
</Card>
<Card className="mb-6">
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2 text-sm text-gray-700">
<div>
<b></b> <span className="font-mono">sign</span>
</div>
<div>
<b></b> sign apiKey<b></b>& <span className="font-mono">MD5</span> apiKey <span className="font-mono">MD5</span> sign
</div>
<div>
<b></b>
<ol className="list-decimal list-inside ml-4">
<li> sign apiKey namephonetimestampsourceremarktags</li>
<li><b></b> value1value2value3...</li>
<li> MD5 </li>
<li> apiKey </li>
<li> MD5 sign</li>
</ol>
</div>
<div>
<b></b>
<pre className="bg-gray-50 rounded p-2 text-xs overflow-auto">
{`参数:
name=张三
phone=18888888888
timestamp=1700000000
apiKey=naxf1-82h2f-vdwcm-rrhpm-q9hd1
排序后拼接排除apiKey直接拼接值
张三188888888881700000000
第一步MD5
md5(张三188888888881700000000) = 123456abcdef...
拼接apiKey
123456abcdef...naxf1-82h2f-vdwcm-rrhpm-q9hd1
第二步MD5
sign=md5(123456abcdef...naxf1-82h2f-vdwcm-rrhpm-q9hd1)`}
</pre>
</div>
<div className="text-xs text-amber-700 mt-2"> sign apiKey URL </div>
</div>
</CardContent>
</Card>
<Card className="mb-6">
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className="mb-2">
<span className="font-mono text-sm">{testUrl}</span>
</div>
<div className="text-xs text-gray-500">
<div>: <b>phone</b> (), <b>timestamp</b> (), <b>apiKey</b> ()</div>
<div>: <b>name</b> (), <b>source</b> (), <b>remark</b> (), <b>tags</b> ()</div>
<div>: <b>POST</b> <b>GET</b></div>
</div>
</CardContent>
</Card>
<Card className="mb-6">
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className="bg-gray-50 rounded-md p-3 text-xs">
<div><b>phone</b> (string, ): </div>
<div><b>timestamp</b> (int, ): 1700000000 Math.floor(Date.now() / 1000) </div>
<div><b>apiKey</b> (string, ): </div>
<div><b>name</b> (string, ): </div>
<div><b>source</b> (string, ): </div>
<div><b>remark</b> (string, ): </div>
<div><b>tags</b> (string, ): </div>
</div>
</CardContent>
</Card>
<Card className="mb-6">
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<pre className="bg-gray-50 rounded-md p-3 text-xs overflow-auto">
{`{
"code": 200,
"msg": "导入成功",
"data": {
"customerId": "123456"
}
}`}
</pre>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<Tabs defaultValue="curl">
<TabsList className="mb-4">
<TabsTrigger value="curl">cURL</TabsTrigger>
<TabsTrigger value="python">Python</TabsTrigger>
<TabsTrigger value="node">Node.js</TabsTrigger>
<TabsTrigger value="php">PHP</TabsTrigger>
<TabsTrigger value="java">Java</TabsTrigger>
</TabsList>
<TabsContent value="curl">
<pre className="bg-gray-50 p-4 rounded-md overflow-auto text-xs">{`curl -X POST 'http://yishi.com/v1/plan/api/scenariosz' \
-d "phone=18888888888" \
-d "timestamp=1700000000" \
-d "name=张三" \
-d "apiKey=naxf1-82h2f-vdwcm-rrhpm-q9hd1" \
-d "sign=请用签名算法生成"`}</pre>
</TabsContent>
<TabsContent value="python">
<pre className="bg-gray-50 p-4 rounded-md overflow-auto text-xs">{`import hashlib
import time
import requests
def gen_sign(params, api_key):
data = {k: v for k, v in params.items() if k not in ('sign', 'apiKey')}
s = ''.join([str(data[k]) for k in sorted(data)])
first = hashlib.md5(s.encode('utf-8')).hexdigest()
return hashlib.md5((first + api_key).encode('utf-8')).hexdigest()
api_key = 'naxf1-82h2f-vdwcm-rrhpm-q9hd1'
params = {
'phone': '18888888888',
'timestamp': int(time.time()),
'name': '张三',
}
params['apiKey'] = api_key
params['sign'] = gen_sign(params, api_key)
resp = requests.post('http://yishi.com/v1/plan/api/scenariosz', data=params)
print(resp.json())`}</pre>
</TabsContent>
<TabsContent value="node">
<pre className="bg-gray-50 p-4 rounded-md overflow-auto text-xs">{`const axios = require('axios');
const crypto = require('crypto');
function genSign(params, apiKey) {
const data = {...params};
delete data.sign;
delete data.apiKey;
const keys = Object.keys(data).sort();
let str = '';
keys.forEach(k => { str += data[k]; });
const first = crypto.createHash('md5').update(str).digest('hex');
return crypto.createHash('md5').update(first + apiKey).digest('hex');
}
const apiKey = 'naxf1-82h2f-vdwcm-rrhpm-q9hd1';
const params = {
phone: '18888888888',
timestamp: Math.floor(Date.now() / 1000),
name: '张三',
};
params.apiKey = apiKey;
params.sign = genSign(params, apiKey);
axios.post('http://yishi.com/v1/plan/api/scenariosz', params)
.then(res => console.log(res.data));`}</pre>
</TabsContent>
<TabsContent value="php">
<pre className="bg-gray-50 p-4 rounded-md overflow-auto text-xs">{`<?php
function md5_sign($params, $apiKey) {
unset($params['sign']);
unset($params['apiKey']);
ksort($params);
$str = '';
foreach ($params as $v) {
$str .= $v;
}
$first = md5($str);
$final = md5($first . $apiKey);
return $final;
}
$params = [
'phone' => '18888888888',
'timestamp' => time(),
'name' => '张三',
// 'source' => '', 'remark' => '', 'tags' => ''
];
$apiKey = 'naxf1-82h2f-vdwcm-rrhpm-q9hd1';
$params['apiKey'] = $apiKey;
$params['sign'] = md5_sign($params, $apiKey);
$url = '${API_BASE_URL}/v1/api/scenarios';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>`}</pre>
</TabsContent>
<TabsContent value="java">
<pre className="bg-gray-50 p-4 rounded-md overflow-auto text-xs">{`import java.security.MessageDigest;
import java.util.*;
import java.net.*;
import java.io.*;
public class ApiSignDemo {
public static String md5(String s) throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest(s.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte b : array) sb.append(String.format("%02x", b));
return sb.toString();
}
public static void main(String[] args) throws Exception {
Map<String, String> params = new HashMap<>();
params.put("phone", "18888888888");
params.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
params.put("name", "张三");
// params.put("source", ""); params.put("remark", ""); params.put("tags", "");
String apiKey = "naxf1-82h2f-vdwcm-rrhpm-q9hd1";
// 排序并拼接
List<String> keys = new ArrayList<>(params.keySet());
keys.remove("sign");
keys.remove("apiKey");
Collections.sort(keys);
StringBuilder sb = new StringBuilder();
for (String k : keys) {
sb.append(params.get(k));
}
String first = md5(sb.toString());
String sign = md5(first + apiKey);
params.put("apiKey", apiKey);
params.put("sign", sign);
// 发送POST请求
StringBuilder postData = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {
if (postData.length() > 0) postData.append("&");
postData.append(URLEncoder.encode(entry.getKey(), "UTF-8"));
postData.append("=");
postData.append(URLEncoder.encode(entry.getValue(), "UTF-8"));
}
URL url = new URL("${API_BASE_URL}/v1/api/scenarios");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
OutputStream os = conn.getOutputStream();
os.write(postData.toString().getBytes("UTF-8"));
os.flush(); os.close();
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line; StringBuilder resp = new StringBuilder();
while ((line = in.readLine()) != null) resp.append(line);
in.close();
System.out.println(resp.toString());
}
}`}</pre>
</TabsContent>
</Tabs>
</CardContent>
</Card>
<div className="mt-8">
<h3 className="text-lg font-medium mb-4"></h3>
<Card className="mb-4">
<CardHeader>
<CardTitle className="text-base"></CardTitle>
</CardHeader>
<CardContent>
<ol className="list-decimal list-inside space-y-2 text-sm">
<li></li>
<li>"应用集成" &gt; "外部接口"</li>
<li>"添加新接口"</li>
<li>"X-API-KEY"API密钥</li>
<li>
URL为
<code className="bg-gray-100 px-1 py-0.5 rounded">
{testUrl}
</code>
</li>
<li>name, phone等</li>
<li></li>
</ol>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base"></CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<h4 className="text-sm font-medium mb-1"></h4>
<p className="text-sm text-gray-700">
X-API-KEY正确无误
</p>
</div>
<div>
<h4 className="text-sm font-medium mb-1"></h4>
<p className="text-sm text-gray-700">
</p>
</div>
<div>
<h4 className="text-sm font-medium mb-1"></h4>
<p className="text-sm text-gray-700">
API密钥每分钟最多可发送30个请求使
</p>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
)
}

View File

@@ -1,74 +0,0 @@
import { NextResponse } from "next/server"
import type { CreateScenarioParams, QueryScenarioParams, ScenarioBase, ApiResponse } from "@/types/scenario"
// 获客场景路由处理
export async function POST(request: Request) {
try {
const body: CreateScenarioParams = await request.json()
// TODO: 实现创建场景的具体逻辑
const scenario: ScenarioBase = {
id: "generated-id",
...body,
status: "draft",
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
creator: "current-user-id",
}
const response: ApiResponse<ScenarioBase> = {
code: 0,
message: "创建成功",
data: scenario,
}
return NextResponse.json(response)
} catch (error) {
return NextResponse.json(
{
code: 500,
message: "创建失败",
data: null,
},
{ status: 500 },
)
}
}
export async function GET(request: Request) {
try {
const { searchParams } = new URL(request.url)
const params: QueryScenarioParams = {
type: searchParams.get("type") as any,
status: searchParams.get("status") as any,
keyword: searchParams.get("keyword") || undefined,
dateRange: searchParams.get("dateRange") ? JSON.parse(searchParams.get("dateRange")!) : undefined,
page: Number(searchParams.get("page")) || 1,
pageSize: Number(searchParams.get("pageSize")) || 20,
}
// TODO: 实现查询场景列表的具体逻辑
return NextResponse.json({
code: 0,
message: "查询成功",
data: {
items: [],
total: 0,
page: params.page,
pageSize: params.pageSize,
totalPages: 0,
},
})
} catch (error) {
return NextResponse.json(
{
code: 500,
message: "查询失败",
data: null,
},
{ status: 500 },
)
}
}

View File

@@ -1,273 +0,0 @@
import { NextResponse } from "next/server"
import type { TrafficUser } from "@/types/traffic"
// 中文名字生成器数据
const familyNames = [
"张",
"王",
"李",
"赵",
"陈",
"刘",
"杨",
"黄",
"周",
"吴",
"朱",
"孙",
"马",
"胡",
"郭",
"林",
"何",
"高",
"梁",
"郑",
"罗",
"宋",
"谢",
"唐",
"韩",
"曹",
"许",
"邓",
"萧",
"冯",
]
const givenNames1 = [
"志",
"建",
"文",
"明",
"永",
"春",
"秀",
"金",
"水",
"玉",
"国",
"立",
"德",
"海",
"和",
"荣",
"伟",
"新",
"英",
"佳",
]
const givenNames2 = [
"华",
"平",
"军",
"强",
"辉",
"敏",
"峰",
"磊",
"超",
"艳",
"娜",
"霞",
"燕",
"娟",
"静",
"丽",
"涛",
"洋",
"勇",
"龙",
]
// 生成固定的用户数据池
const userPool: TrafficUser[] = Array.from({ length: 1610 }, (_, i) => {
const familyName = familyNames[Math.floor(Math.random() * familyNames.length)]
const givenName1 = givenNames1[Math.floor(Math.random() * givenNames1.length)]
const givenName2 = givenNames2[Math.floor(Math.random() * givenNames2.length)]
const fullName = Math.random() > 0.5 ? familyName + givenName1 + givenName2 : familyName + givenName1
// 生成随机时间在过去7天内
const date = new Date()
date.setDate(date.getDate() - Math.floor(Math.random() * 7))
return {
id: `${Date.now()}-${i}`,
avatar: `/placeholder.svg?height=40&width=40&text=${fullName[0]}`,
nickname: fullName,
wechatId: `wxid_${Math.random().toString(36).substr(2, 8)}`,
phone: `1${["3", "5", "7", "8", "9"][Math.floor(Math.random() * 5)]}${Array.from({ length: 9 }, () => Math.floor(Math.random() * 10)).join("")}`,
region: [
"广东深圳",
"浙江杭州",
"江苏苏州",
"北京",
"上海",
"四川成都",
"湖北武汉",
"福建厦门",
"山东青岛",
"河南郑州",
][Math.floor(Math.random() * 10)],
note: [
"咨询产品价格",
"对产品很感兴趣",
"准备购买",
"需要更多信息",
"想了解优惠活动",
"询问产品规格",
"要求产品demo",
"索要产品目录",
"询问售后服务",
"要求上门演示",
][Math.floor(Math.random() * 10)],
status: ["pending", "added", "failed"][Math.floor(Math.random() * 3)] as TrafficUser["status"],
addTime: date.toISOString(),
source: ["抖音直播", "小红书", "微信朋友圈", "视频号", "公众号", "个人主页"][Math.floor(Math.random() * 6)],
assignedTo: "",
category: ["potential", "customer", "lost"][Math.floor(Math.random() * 3)] as TrafficUser["category"],
tags: [],
}
})
// 计算今日新增数量
const todayStart = new Date()
todayStart.setHours(0, 0, 0, 0)
const todayUsers = userPool.filter((user) => new Date(user.addTime) >= todayStart)
// 生成微信好友数据池
const generateWechatFriends = (wechatId: string, count: number) => {
return Array.from({ length: count }, (_, i) => {
const familyName = familyNames[Math.floor(Math.random() * familyNames.length)]
const givenName1 = givenNames1[Math.floor(Math.random() * givenNames1.length)]
const givenName2 = givenNames2[Math.floor(Math.random() * givenNames2.length)]
const fullName = Math.random() > 0.5 ? familyName + givenName1 + givenName2 : familyName + givenName1
// 生成随机时间在过去30天内
const date = new Date()
date.setDate(date.getDate() - Math.floor(Math.random() * 30))
return {
id: `wechat-${wechatId}-${i}`,
avatar: `/placeholder.svg?height=40&width=40&text=${fullName[0]}`,
nickname: fullName,
wechatId: `wxid_${Math.random().toString(36).substr(2, 8)}`,
phone: `1${["3", "5", "7", "8", "9"][Math.floor(Math.random() * 5)]}${Array.from({ length: 9 }, () => Math.floor(Math.random() * 10)).join("")}`,
region: [
"广东深圳",
"浙江杭州",
"江苏苏州",
"北京",
"上海",
"四川成都",
"湖北武汉",
"福建厦门",
"山东青岛",
"河南郑州",
][Math.floor(Math.random() * 10)],
note: [
"咨询产品价格",
"对产品很感兴趣",
"准备购买",
"需要更多信息",
"想了解优惠活动",
"询问产品规格",
"要求产品demo",
"索要产品目录",
"询问售后服务",
"要求上门演示",
][Math.floor(Math.random() * 10)],
status: ["pending", "added", "failed"][Math.floor(Math.random() * 3)] as TrafficUser["status"],
addTime: date.toISOString(),
source: ["抖音直播", "小红书", "微信朋友圈", "视频号", "公众号", "个人主页", "微信好友"][
Math.floor(Math.random() * 7)
],
assignedTo: "",
category: ["potential", "customer", "lost"][Math.floor(Math.random() * 3)] as TrafficUser["category"],
tags: [],
}
})
}
// 微信好友数据缓存
const wechatFriendsCache = new Map<string, TrafficUser[]>()
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const page = Number.parseInt(searchParams.get("page") || "1")
const pageSize = Number.parseInt(searchParams.get("pageSize") || "10")
const search = searchParams.get("search") || ""
const category = searchParams.get("category") || "all"
const source = searchParams.get("source") || "all"
const status = searchParams.get("status") || "all"
const startDate = searchParams.get("startDate")
const endDate = searchParams.get("endDate")
const wechatSource = searchParams.get("wechatSource") || ""
let filteredUsers = [...userPool]
// 如果有微信来源参数,生成或获取微信好友数据
if (wechatSource) {
if (!wechatFriendsCache.has(wechatSource)) {
// 生成150-300个随机好友
const friendCount = Math.floor(Math.random() * (300 - 150)) + 150
wechatFriendsCache.set(wechatSource, generateWechatFriends(wechatSource, friendCount))
}
filteredUsers = wechatFriendsCache.get(wechatSource) || []
}
// 应用过滤条件
filteredUsers = filteredUsers.filter((user) => {
const matchesSearch = search
? user.nickname.toLowerCase().includes(search.toLowerCase()) ||
user.wechatId.toLowerCase().includes(search.toLowerCase()) ||
user.phone.includes(search)
: true
const matchesCategory = category === "all" ? true : user.category === category
const matchesSource = source === "all" ? true : user.source === source
const matchesStatus = status === "all" ? true : user.status === status
const matchesDate =
startDate && endDate
? new Date(user.addTime) >= new Date(startDate) && new Date(user.addTime) <= new Date(endDate)
: true
return matchesSearch && matchesCategory && matchesSource && matchesStatus && matchesDate
})
// 按添加时间倒序排序
filteredUsers.sort((a, b) => new Date(b.addTime).getTime() - new Date(a.addTime).getTime())
// 计算分页
const total = filteredUsers.length
const totalPages = Math.ceil(total / pageSize)
const start = (page - 1) * pageSize
const end = start + pageSize
const users = filteredUsers.slice(start, end)
// 计算分类统计
const categoryStats = {
potential: userPool.filter((user) => user.category === "potential").length,
customer: userPool.filter((user) => user.category === "customer").length,
lost: userPool.filter((user) => user.category === "lost").length,
}
// 模拟网络延迟
await new Promise((resolve) => setTimeout(resolve, 500))
return NextResponse.json({
users,
pagination: {
total,
totalPages,
currentPage: page,
pageSize,
},
stats: {
total: wechatSource ? filteredUsers.length : userPool.length,
todayNew: wechatSource ? Math.floor(filteredUsers.length * 0.1) : todayUsers.length,
categoryStats,
},
})
}

View File

@@ -1,48 +0,0 @@
"use client"
import "./globals.css"
import BottomNav from "./components/BottomNav"
import "regenerator-runtime/runtime"
import type React from "react"
import ErrorBoundary from "./components/ErrorBoundary"
import { VideoTutorialButton } from "@/components/VideoTutorialButton"
import { AuthProvider } from "@/app/components/AuthProvider"
import { usePathname } from "next/navigation"
// 创建一个包装组件来使用 usePathname hook
function LayoutContent({ children }: { children: React.ReactNode }) {
const pathname = usePathname()
// 只在主页路径显示底部导航栏
const showBottomNav =
pathname === "/" || pathname === "/devices" || pathname === "/content" || pathname === "/profile"
return (
<div className="mx-auto w-full">
<main className="w-full mx-auto bg-white min-h-screen flex flex-col relative lg:max-w-7xl xl:max-w-[1200px]">
{children}
{showBottomNav && <BottomNav />}
{showBottomNav && <VideoTutorialButton />}
</main>
</div>
)
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="zh-CN">
<body className="bg-gray-100">
<AuthProvider>
<ErrorBoundary>
<LayoutContent>{children}</LayoutContent>
</ErrorBoundary>
</AuthProvider>
</body>
</html>
)
}

View File

@@ -1,54 +0,0 @@
"use client"
import { useState } from "react"
import { X } from "lucide-react"
import { Button } from "./ui/button"
import { Card } from "./ui/card"
interface AIRewriteModalProps {
isOpen: boolean
onClose: () => void
originalContent: string
}
export function AIRewriteModal({ isOpen, onClose, originalContent }: AIRewriteModalProps) {
const [rewrittenContent, setRewrittenContent] = useState("")
const handleRewrite = async () => {
// 这里应该调用 AI 改写 API
// 为了演示,我们只是简单地反转字符串
setRewrittenContent(originalContent.split("").reverse().join(""))
}
if (!isOpen) return null
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<Card className="w-full max-w-lg p-6">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-semibold">AI </h2>
<Button variant="ghost" size="sm" onClick={onClose}>
<X className="w-4 h-4" />
</Button>
</div>
<div className="space-y-4">
<div>
<h3 className="font-medium mb-2"></h3>
<p className="text-sm text-gray-600">{originalContent}</p>
</div>
<div>
<h3 className="font-medium mb-2"></h3>
<p className="text-sm text-gray-600">{rewrittenContent || '点击"开始改写"按钮生成内容'}</p>
</div>
</div>
<div className="mt-6 flex justify-end space-x-2">
<Button variant="outline" onClick={onClose}>
</Button>
<Button onClick={handleRewrite}></Button>
</div>
</Card>
</div>
)
}

View File

@@ -1,194 +0,0 @@
"use client"
import { createContext, useContext, useEffect, useState, type ReactNode } from "react"
import { useRouter } from "next/navigation"
import { validateToken } from "@/lib/api"
// 安全的localStorage访问方法
const safeLocalStorage = {
getItem: (key: string): string | null => {
if (typeof window !== 'undefined') {
return localStorage.getItem(key)
}
return null
},
setItem: (key: string, value: string): void => {
if (typeof window !== 'undefined') {
localStorage.setItem(key, value)
}
},
removeItem: (key: string): void => {
if (typeof window !== 'undefined') {
localStorage.removeItem(key)
}
}
}
interface User {
id: number;
username: string;
account?: string;
avatar?: string;
}
interface AuthContextType {
isAuthenticated: boolean
token: string | null
user: User | null
login: (token: string, userData: User) => void
logout: () => void
updateToken: (newToken: string) => void
}
// 创建默认上下文
const AuthContext = createContext<AuthContextType>({
isAuthenticated: false,
token: null,
user: null,
login: () => {},
logout: () => {},
updateToken: () => {}
})
export const useAuth = () => useContext(AuthContext)
interface AuthProviderProps {
children: ReactNode
}
export function AuthProvider({ children }: AuthProviderProps) {
// 避免在服务端渲染时设置初始状态
const [token, setToken] = useState<string | null>(null)
const [user, setUser] = useState<User | null>(null)
const [isAuthenticated, setIsAuthenticated] = useState(false)
// 初始页面加载时显示为false避免在服务端渲染和客户端水合时不匹配
const [isLoading, setIsLoading] = useState(false)
const [isInitialized, setIsInitialized] = useState(false)
const router = useRouter()
// 初始化认证状态
useEffect(() => {
// 仅在客户端执行初始化
setIsLoading(true)
const initAuth = async () => {
try {
const storedToken = safeLocalStorage.getItem("token")
if (storedToken) {
// 首先尝试从localStorage获取用户信息
const userDataStr = safeLocalStorage.getItem("userInfo")
if (userDataStr) {
try {
// 如果能解析用户数据,先设置登录状态
const userData = JSON.parse(userDataStr) as User
setToken(storedToken)
setUser(userData)
setIsAuthenticated(true)
// 然后在后台尝试验证token但不影响当前登录状态
validateToken().then(isValid => {
// 只有在确认token绝对无效时才登出
// 网络错误等情况默认保持登录状态
if (isValid === false) {
console.warn('验证token失败但仍允许用户保持登录状态')
}
}).catch(error => {
// 捕获所有验证过程中的错误,并记录日志
console.error('验证token过程中出错:', error)
// 网络错误等不会导致登出
})
} catch (parseError) {
// 用户数据无法解析,需要清除
console.error('解析用户数据失败:', parseError)
handleLogout()
}
} else {
// 有token但没有用户信息可能是部分数据丢失
console.warn('找到token但没有用户信息尝试保持登录状态')
// 尝试验证token并获取用户信息
try {
const isValid = await validateToken()
if (isValid) {
// 如果token有效尝试从API获取用户信息
// 这里简化处理直接使用token
setToken(storedToken)
setIsAuthenticated(true)
} else {
// token确认无效清除
handleLogout()
}
} catch (error) {
// 验证过程出错,记录日志但不登出
console.error('验证token过程中出错:', error)
// 保留token允许用户继续使用
setToken(storedToken)
setIsAuthenticated(true)
}
}
}
} catch (error) {
console.error("初始化认证状态时出错:", error)
// 非401错误不应强制登出
if (error instanceof Error &&
(error.message.includes('401') ||
error.message.includes('未授权') ||
error.message.includes('token'))) {
handleLogout()
}
} finally {
setIsLoading(false)
setIsInitialized(true)
}
}
initAuth()
}, []) // 空依赖数组,仅在组件挂载时执行一次
const handleLogout = () => {
// 先清除所有认证相关的状态
safeLocalStorage.removeItem("token")
safeLocalStorage.removeItem("token_expired")
safeLocalStorage.removeItem("s2_accountId")
safeLocalStorage.removeItem("userInfo")
safeLocalStorage.removeItem("user")
setToken(null)
setUser(null)
setIsAuthenticated(false)
// 使用 window.location 而不是 router.push避免状态更新和路由跳转的竞态条件
if (typeof window !== 'undefined') {
window.location.href = '/login'
}
}
const login = (newToken: string, userData: User) => {
safeLocalStorage.setItem("token", newToken)
safeLocalStorage.setItem("userInfo", JSON.stringify(userData))
setToken(newToken)
setUser(userData)
setIsAuthenticated(true)
}
const logout = () => {
handleLogout()
}
// 用于刷新 token 的方法
const updateToken = (newToken: string) => {
safeLocalStorage.setItem("token", newToken)
setToken(newToken)
}
return (
<AuthContext.Provider value={{ isAuthenticated, token, user, login, logout, updateToken }}>
{isLoading && isInitialized ? (
<div className="flex h-screen w-screen items-center justify-center">...</div>
) : (
children
)}
</AuthContext.Provider>
)
}

View File

@@ -1,32 +0,0 @@
"use client"
import { useState } from "react"
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
import { QrCode } from "lucide-react"
export function BindDouyinQRCode() {
const [isOpen, setIsOpen] = useState(false)
return (
<>
<Button variant="ghost" size="icon" onClick={() => setIsOpen(true)}>
<QrCode className="h-4 w-4" />
</Button>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<div className="flex flex-col items-center p-4">
<div className="w-64 h-64 bg-gray-100 rounded-lg flex items-center justify-center">
<img src="/placeholder.svg?height=256&width=256" alt="抖音二维码" className="w-full h-full" />
</div>
<p className="mt-4 text-sm text-gray-600">使APP扫描二维码进行绑定</p>
</div>
</DialogContent>
</Dialog>
</>
)
}

View File

@@ -1,36 +0,0 @@
"use client"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { Home, Users, User, Briefcase } from "lucide-react"
const navItems = [
{ href: "/", icon: Home, label: "首页" },
{ href: "/scenarios", icon: Users, label: "场景获客" },
{ href: "/workspace", icon: Briefcase, label: "工作台" },
{ href: "/profile", icon: User, label: "我的" },
]
export default function BottomNav() {
const pathname = usePathname()
return (
<nav className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200">
<div className="w-full mx-auto flex justify-around">
{navItems.map((item) => (
<Link
key={item.href}
href={item.href}
className={`flex flex-col items-center py-2 px-3 ${
pathname === item.href ? "text-blue-500" : "text-gray-500"
}`}
>
<item.icon className="w-6 h-6" />
<span className="text-xs mt-1">{item.label}</span>
</Link>
))}
</div>
</nav>
)
}

View File

@@ -1,85 +0,0 @@
"use client"
import {
LineChart as RechartsLineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
} from "recharts"
import { BarChart as RechartsBarChart, Bar } from "recharts"
const lineData = [
{ name: "周一", 新增微信号: 12 },
{ name: "周二", 新增微信号: 19 },
{ name: "周三", 新增微信号: 3 },
{ name: "周四", 新增微信号: 5 },
{ name: "周五", 新增微信号: 2 },
{ name: "周六", 新增微信号: 3 },
{ name: "周日", 新增微信号: 10 },
]
const barData = [
{ name: "周一", 新增好友: 120 },
{ name: "周二", 新增好友: 190 },
{ name: "周三", 新增好友: 30 },
{ name: "周四", 新增好友: 50 },
{ name: "周五", 新增好友: 20 },
{ name: "周六", 新增好友: 30 },
{ name: "周日", 新增好友: 100 },
]
export function LineChart() {
return (
<ResponsiveContainer width="100%" height={180}>
<RechartsLineChart data={lineData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Line type="monotone" dataKey="新增微信号" stroke="#8884d8" />
</RechartsLineChart>
</ResponsiveContainer>
)
}
export function BarChart() {
return (
<ResponsiveContainer width="100%" height={180}>
<RechartsBarChart data={barData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Bar dataKey="新增好友" fill="#82ca9d" />
</RechartsBarChart>
</ResponsiveContainer>
)
}
export function TrendChart({ data, dataKey = "value", height = 300 }) {
return (
<div className="w-full" style={{ height: `${height}px` }}>
<ResponsiveContainer width="100%" height="100%">
<RechartsLineChart data={data} margin={{ top: 10, right: 30, left: 0, bottom: 0 }}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Line
type="monotone"
dataKey={dataKey}
stroke="#3b82f6"
strokeWidth={2}
dot={{ r: 4, fill: "#3b82f6" }}
activeDot={{ r: 6, fill: "#3b82f6" }}
isAnimationActive={true}
/>
</RechartsLineChart>
</ResponsiveContainer>
</div>
)
}

View File

@@ -1,40 +0,0 @@
import { Avatar } from "@/components/ui/avatar"
import { cn } from "@/lib/utils"
interface ChatMessageProps {
content: string
isUser: boolean
timestamp: Date
avatar?: string
}
export function ChatMessage({ content, isUser, timestamp, avatar }: ChatMessageProps) {
return (
<div className={cn("flex w-full gap-3 p-4", isUser ? "justify-end" : "justify-start")}>
{!isUser && (
<Avatar className="h-8 w-8">
<div className="bg-primary text-primary-foreground flex h-full w-full items-center justify-center rounded-full text-sm font-semibold">
AI
</div>
</Avatar>
)}
<div className={cn("rounded-lg p-3 max-w-[80%]", isUser ? "bg-primary text-primary-foreground" : "bg-muted")}>
<p className="whitespace-pre-wrap">{content}</p>
<div className={cn("text-xs mt-1", isUser ? "text-primary-foreground/70" : "text-muted-foreground")}>
{timestamp.toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})}
</div>
</div>
{isUser && avatar && (
<Avatar className="h-8 w-8">
<img src={avatar || "/placeholder.svg"} alt="User" className="rounded-full" />
</Avatar>
)}
</div>
)
}

View File

@@ -1,106 +0,0 @@
"use client"
import { useState } from "react"
import { Card } from "../ui/card"
import { Button } from "../ui/button"
import { Input } from "../ui/input"
import { ChevronLeft, Search, Plus } from "lucide-react"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../ui/table"
interface ContentLibrary {
id: string
name: string
type: string
count: number
}
const mockLibraries: ContentLibrary[] = [
{
id: "1",
name: "卡若朋友圈",
type: "朋友圈",
count: 307,
},
{
id: "2",
name: "业务推广内容",
type: "朋友圈",
count: 156,
},
]
export function ContentSelector({ onPrev, onFinish }) {
const [selectedLibraries, setSelectedLibraries] = useState<string[]>([])
return (
<Card className="p-6 max-w-4xl mx-auto">
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl font-semibold"></h2>
<Button>
<Plus className="w-4 h-4 mr-2" />
</Button>
</div>
<div className="flex items-center space-x-4 mb-6">
<div className="flex-1">
<Input placeholder="搜索内容库" className="w-full" prefix={<Search className="w-4 h-4 text-gray-400" />} />
</div>
</div>
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-12"></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{mockLibraries.map((library) => (
<TableRow key={library.id}>
<TableCell>
<input
type="checkbox"
checked={selectedLibraries.includes(library.id)}
onChange={(e) => {
if (e.target.checked) {
setSelectedLibraries([...selectedLibraries, library.id])
} else {
setSelectedLibraries(selectedLibraries.filter((id) => id !== library.id))
}
}}
/>
</TableCell>
<TableCell>{library.name}</TableCell>
<TableCell>{library.type}</TableCell>
<TableCell>{library.count}</TableCell>
<TableCell>
<Button variant="ghost" size="sm">
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<div className="flex justify-between mt-8">
<Button variant="outline" onClick={onPrev}>
<ChevronLeft className="w-4 h-4 mr-2" />
</Button>
<Button
onClick={onFinish}
disabled={selectedLibraries.length === 0}
className="bg-green-500 hover:bg-green-600"
>
</Button>
</div>
</Card>
)
}

View File

@@ -1,123 +0,0 @@
"use client"
import { useState } from "react"
import { Card } from "../ui/card"
import { Button } from "../ui/button"
import { Input } from "../ui/input"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../ui/table"
import { ChevronLeft, ChevronRight, Search, Plus } from "lucide-react"
interface Device {
id: string
imei: string
status: "online" | "offline"
friendStatus: string
}
const mockDevices: Device[] = [
{
id: "1",
imei: "123456789012345",
status: "online",
friendStatus: "正常",
},
{
id: "2",
imei: "987654321098765",
status: "offline",
friendStatus: "异常",
},
]
export function DeviceSelector({ onNext, onPrev }) {
const [selectedDevices, setSelectedDevices] = useState<string[]>([])
return (
<Card className="p-6 max-w-4xl mx-auto">
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl font-semibold"></h2>
<Button>
<Plus className="w-4 h-4 mr-2" />
</Button>
</div>
<div className="flex items-center space-x-4 mb-6">
<div className="flex-1">
<Input
placeholder="搜索设备IMEI/备注/手机号"
className="w-full"
prefix={<Search className="w-4 h-4 text-gray-400" />}
/>
</div>
</div>
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-12"></TableHead>
<TableHead>IMEI//</TableHead>
<TableHead>线</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{mockDevices.map((device) => (
<TableRow key={device.id}>
<TableCell>
<input
type="checkbox"
checked={selectedDevices.includes(device.id)}
onChange={(e) => {
if (e.target.checked) {
setSelectedDevices([...selectedDevices, device.id])
} else {
setSelectedDevices(selectedDevices.filter((id) => id !== device.id))
}
}}
/>
</TableCell>
<TableCell>{device.imei}</TableCell>
<TableCell>
<span
className={`inline-flex items-center px-2 py-1 rounded-full text-xs ${
device.status === "online" ? "bg-green-100 text-green-800" : "bg-gray-100 text-gray-800"
}`}
>
{device.status === "online" ? "在线" : "离线"}
</span>
</TableCell>
<TableCell>
<span
className={`inline-flex items-center px-2 py-1 rounded-full text-xs ${
device.friendStatus === "正常" ? "bg-green-100 text-green-800" : "bg-red-100 text-red-800"
}`}
>
{device.friendStatus}
</span>
</TableCell>
<TableCell>
<Button variant="ghost" size="sm">
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<div className="flex justify-between mt-8">
<Button variant="outline" onClick={onPrev}>
<ChevronLeft className="w-4 h-4 mr-2" />
</Button>
<Button onClick={onNext} disabled={selectedDevices.length === 0}>
<ChevronRight className="w-4 h-4 ml-2" />
</Button>
</div>
</Card>
)
}

View File

@@ -1,101 +0,0 @@
"use client"
import { useState } from "react"
import { Card } from "../ui/card"
import { Button } from "../ui/button"
import { Input } from "../ui/input"
import { Switch } from "../ui/switch"
import { Label } from "../ui/label"
import { ChevronLeft, ChevronRight, Plus, Minus } from "lucide-react"
interface TaskSetupProps {
onNext?: () => void
onPrev?: () => void
step: number
}
export function TaskSetup({ onNext, onPrev, step }: TaskSetupProps) {
const [syncCount, setSyncCount] = useState(5)
const [startTime, setStartTime] = useState("06:00")
const [endTime, setEndTime] = useState("23:59")
const [isEnabled, setIsEnabled] = useState(true)
const [accountType, setAccountType] = useState("business") // business or personal
return (
<Card className="p-6 max-w-2xl mx-auto">
<div className="flex items-center justify-between mb-8">
<h2 className="text-xl font-semibold"></h2>
<div className="flex items-center space-x-2">
<Label htmlFor="task-enabled"></Label>
<Switch id="task-enabled" checked={isEnabled} onCheckedChange={setIsEnabled} />
</div>
</div>
<div className="space-y-6">
<div className="grid gap-4">
<Label></Label>
<Input placeholder="请输入任务名称" />
</div>
<div className="grid gap-4">
<Label></Label>
<div className="flex items-center space-x-2">
<Input type="time" value={startTime} onChange={(e) => setStartTime(e.target.value)} className="w-32" />
<span></span>
<Input type="time" value={endTime} onChange={(e) => setEndTime(e.target.value)} className="w-32" />
</div>
</div>
<div className="grid gap-4">
<Label></Label>
<div className="flex items-center space-x-4">
<Button variant="outline" size="icon" onClick={() => setSyncCount(Math.max(1, syncCount - 1))}>
<Minus className="h-4 w-4" />
</Button>
<span className="w-12 text-center">{syncCount}</span>
<Button variant="outline" size="icon" onClick={() => setSyncCount(syncCount + 1)}>
<Plus className="h-4 w-4" />
</Button>
<span className="text-gray-500"></span>
</div>
</div>
<div className="grid gap-4">
<Label></Label>
<div className="flex space-x-4">
<Button
variant={accountType === "business" ? "default" : "outline"}
onClick={() => setAccountType("business")}
className="w-24"
>
</Button>
<Button
variant={accountType === "personal" ? "default" : "outline"}
onClick={() => setAccountType("personal")}
className="w-24"
>
</Button>
</div>
</div>
</div>
<div className="flex justify-between mt-8">
{step > 1 ? (
<Button variant="outline" onClick={onPrev}>
<ChevronLeft className="w-4 h-4 mr-2" />
</Button>
) : (
<div />
)}
<Button onClick={onNext}>
<ChevronRight className="w-4 h-4 ml-2" />
</Button>
</div>
</Card>
)
}

View File

@@ -1,257 +0,0 @@
"use client"
import { useState, useEffect } from "react"
import { Card } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Filter, Search, RefreshCw, AlertCircle } from "lucide-react"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Checkbox } from "@/components/ui/checkbox"
import { toast } from "@/components/ui/use-toast"
import { Progress } from "@/components/ui/progress"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination"
import { ImeiDisplay } from "@/components/ImeiDisplay"
interface WechatAccount {
wechatId: string
nickname: string
remainingAdds: number
maxDailyAdds: number
todayAdded: number
}
interface Device {
id: string
imei: string
name: string
status: "online" | "offline"
wechatAccounts: WechatAccount[]
usedInPlans: number
}
interface DeviceSelectorProps {
onSelect: (selectedDevices: string[]) => void
initialSelectedDevices?: string[]
excludeUsedDevices?: boolean
}
export function DeviceSelector({
onSelect,
initialSelectedDevices = [],
excludeUsedDevices = true,
}: DeviceSelectorProps) {
const [devices, setDevices] = useState<Device[]>([])
const [selectedDevices, setSelectedDevices] = useState<string[]>(initialSelectedDevices)
const [searchQuery, setSearchQuery] = useState("")
const [statusFilter, setStatusFilter] = useState("all")
const [currentPage, setCurrentPage] = useState(1)
const devicesPerPage = 10
useEffect(() => {
// 模拟获取设备数据
const fetchDevices = async () => {
await new Promise((resolve) => setTimeout(resolve, 500))
const mockDevices = Array.from({ length: 42 }, (_, i) => ({
id: `device-${i + 1}`,
imei: `IMEI-${Math.random().toString(36).substr(2, 9)}`,
name: `设备 ${i + 1}`,
status: Math.random() > 0.3 ? "online" : "offline",
wechatAccounts: Array.from({ length: Math.floor(Math.random() * 2) + 1 }, (_, j) => ({
wechatId: `wxid_${Math.random().toString(36).substr(2, 8)}`,
nickname: `微信号 ${j + 1}`,
remainingAdds: Math.floor(Math.random() * 10) + 5,
maxDailyAdds: 20,
todayAdded: Math.floor(Math.random() * 15),
})),
usedInPlans: Math.floor(Math.random() * 3),
}))
setDevices(mockDevices)
}
fetchDevices()
}, [])
const handleRefresh = () => {
toast({
title: "刷新成功",
description: "设备列表已更新",
})
}
const filteredDevices = devices.filter((device) => {
const matchesSearch =
device.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
device.imei.toLowerCase().includes(searchQuery.toLowerCase()) ||
device.wechatAccounts.some((account) => account.wechatId.toLowerCase().includes(searchQuery.toLowerCase()))
const matchesStatus = statusFilter === "all" || device.status === statusFilter
const matchesUsage = !excludeUsedDevices || device.usedInPlans === 0
return matchesSearch && matchesStatus && matchesUsage
})
const paginatedDevices = filteredDevices.slice((currentPage - 1) * devicesPerPage, currentPage * devicesPerPage)
const handleSelectAll = () => {
if (selectedDevices.length === paginatedDevices.length) {
setSelectedDevices([])
} else {
setSelectedDevices(paginatedDevices.map((device) => device.id))
}
onSelect(selectedDevices)
}
const handleDeviceSelect = (deviceId: string) => {
const updatedSelection = selectedDevices.includes(deviceId)
? selectedDevices.filter((id) => id !== deviceId)
: [...selectedDevices, deviceId]
setSelectedDevices(updatedSelection)
onSelect(updatedSelection)
}
return (
<div className="space-y-4">
<div className="flex items-center space-x-2">
<div className="relative flex-1">
<Search className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
<Input
placeholder="搜索设备IMEI/备注/微信号"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9"
/>
</div>
<Button variant="outline" size="icon">
<Filter className="h-4 w-4" />
</Button>
<Button variant="outline" size="icon" onClick={handleRefresh}>
<RefreshCw className="h-4 w-4" />
</Button>
</div>
<div className="flex items-center justify-between">
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-[120px]">
<SelectValue placeholder="全部状态" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
<SelectItem value="online">线</SelectItem>
<SelectItem value="offline">线</SelectItem>
</SelectContent>
</Select>
<Button variant="outline" onClick={handleSelectAll}>
{selectedDevices.length === paginatedDevices.length ? "取消全选" : "全选"}
</Button>
</div>
<div className="space-y-2">
{paginatedDevices.map((device) => (
<Card key={device.id} className="p-3 hover:shadow-md transition-shadow">
<div className="flex items-center space-x-3">
<Checkbox
checked={selectedDevices.includes(device.id)}
onCheckedChange={() => handleDeviceSelect(device.id)}
/>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between mb-1">
<div className="font-medium truncate">{device.name}</div>
<div
className={`px-2 py-1 rounded-full text-xs ${
device.status === "online" ? "bg-green-100 text-green-800" : "bg-gray-100 text-gray-800"
}`}
>
{device.status === "online" ? "在线" : "离线"}
</div>
</div>
<div className="text-sm text-gray-500 flex items-center">
<span className="mr-1">IMEI:</span>
<ImeiDisplay imei={device.imei} containerWidth={160} />
</div>
<div className="mt-2 space-y-2">
{device.wechatAccounts.map((account) => (
<div key={account.wechatId} className="bg-gray-50 rounded-lg p-2">
<div className="flex items-center justify-between text-sm">
<span className="font-medium">{account.nickname}</span>
<span className="text-gray-500">{account.wechatId}</span>
</div>
<div className="mt-1 space-y-1">
<div className="flex items-center justify-between text-sm">
<div className="flex items-center space-x-1">
<span></span>
<span className="font-medium">{account.remainingAdds}</span>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<AlertCircle className="h-4 w-4 text-gray-400" />
</TooltipTrigger>
<TooltipContent>
<p> {account.maxDailyAdds} </p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<span className="text-sm text-gray-500">
{account.todayAdded}/{account.maxDailyAdds}
</span>
</div>
<Progress value={(account.todayAdded / account.maxDailyAdds) * 100} className="h-1.5" />
</div>
</div>
))}
</div>
{!excludeUsedDevices && device.usedInPlans > 0 && (
<div className="text-sm text-orange-500 mt-2"> {device.usedInPlans} </div>
)}
</div>
</div>
</Card>
))}
</div>
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
href="#"
onClick={(e) => {
e.preventDefault()
setCurrentPage((prev) => Math.max(1, prev - 1))
}}
/>
</PaginationItem>
{Array.from({ length: Math.ceil(filteredDevices.length / devicesPerPage) }, (_, i) => i + 1).map((page) => (
<PaginationItem key={page}>
<PaginationLink
href="#"
isActive={currentPage === page}
onClick={(e) => {
e.preventDefault()
setCurrentPage(page)
}}
>
{page}
</PaginationLink>
</PaginationItem>
))}
<PaginationItem>
<PaginationNext
href="#"
onClick={(e) => {
e.preventDefault()
setCurrentPage((prev) => Math.min(Math.ceil(filteredDevices.length / devicesPerPage), prev + 1))
}}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
</div>
)
}

View File

@@ -1,47 +0,0 @@
"use client"
import React, { type ErrorInfo } from "react"
import { Card } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
interface ErrorBoundaryProps {
children: React.ReactNode
}
interface ErrorBoundaryState {
hasError: boolean
error?: Error
}
class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error }
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error("Uncaught error:", error, errorInfo)
}
render() {
if (this.state.hasError) {
return (
<Card className="p-6 max-w-md mx-auto mt-8">
<h2 className="text-2xl font-bold mb-4 text-red-600"></h2>
<p className="text-gray-600 mb-4"></p>
<p className="text-sm text-gray-500 mb-4">{this.state.error?.message}</p>
<Button onClick={() => this.setState({ hasError: false })}></Button>
</Card>
)
}
return this.props.children
}
}
export default ErrorBoundary

View File

@@ -1,150 +0,0 @@
"use client"
import type React from "react"
import { useState, useRef } from "react"
import { Button } from "@/components/ui/button"
import { Upload, X } from "lucide-react"
import { Progress } from "@/components/ui/progress"
interface FileUploaderProps {
onFileUploaded: (file: File) => void
acceptedTypes?: string
maxSize?: number // in MB
}
export function FileUploader({
onFileUploaded,
acceptedTypes = ".pdf,.doc,.docx,.jpg,.jpeg,.png",
maxSize = 10, // 10MB default
}: FileUploaderProps) {
const [dragActive, setDragActive] = useState(false)
const [selectedFile, setSelectedFile] = useState<File | null>(null)
const [uploadProgress, setUploadProgress] = useState(0)
const [error, setError] = useState<string | null>(null)
const inputRef = useRef<HTMLInputElement>(null)
const handleDrag = (e: React.DragEvent) => {
e.preventDefault()
e.stopPropagation()
if (e.type === "dragenter" || e.type === "dragover") {
setDragActive(true)
} else if (e.type === "dragleave") {
setDragActive(false)
}
}
const validateFile = (file: File): boolean => {
// 检查文件类型
const fileType = file.name.split(".").pop()?.toLowerCase() || ""
const isValidType = acceptedTypes.includes(fileType)
// 检查文件大小
const isValidSize = file.size <= maxSize * 1024 * 1024
if (!isValidType) {
setError(`不支持的文件类型。请上传 ${acceptedTypes} 格式的文件。`)
return false
}
if (!isValidSize) {
setError(`文件过大。最大支持 ${maxSize}MB。`)
return false
}
return true
}
const handleDrop = (e: React.DragEvent) => {
e.preventDefault()
e.stopPropagation()
setDragActive(false)
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
const file = e.dataTransfer.files[0]
handleFile(file)
}
}
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
e.preventDefault()
if (e.target.files && e.target.files[0]) {
const file = e.target.files[0]
handleFile(file)
}
}
const handleFile = (file: File) => {
setError(null)
if (validateFile(file)) {
setSelectedFile(file)
simulateUpload(file)
}
}
const simulateUpload = (file: File) => {
// 模拟上传进度
setUploadProgress(0)
const interval = setInterval(() => {
setUploadProgress((prev) => {
if (prev >= 100) {
clearInterval(interval)
onFileUploaded(file)
return 100
}
return prev + 10
})
}, 200)
}
const handleButtonClick = () => {
inputRef.current?.click()
}
const cancelUpload = () => {
setSelectedFile(null)
setUploadProgress(0)
setError(null)
}
return (
<div className="w-full">
{!selectedFile ? (
<div
className={`border-2 border-dashed rounded-lg p-6 text-center ${
dragActive ? "border-blue-500 bg-blue-50" : "border-gray-300"
}`}
onDragEnter={handleDrag}
onDragLeave={handleDrag}
onDragOver={handleDrag}
onDrop={handleDrop}
>
<input ref={inputRef} type="file" className="hidden" onChange={handleChange} accept={acceptedTypes} />
<Upload className="mx-auto h-12 w-12 text-gray-400" />
<p className="mt-2 text-sm font-medium text-gray-700"></p>
<Button variant="outline" onClick={handleButtonClick} className="mt-2">
</Button>
<p className="mt-1 text-xs text-gray-500">
{acceptedTypes.replace(/\./g, "")} {maxSize}MB
</p>
</div>
) : (
<div className="border rounded-lg p-4">
<div className="flex justify-between items-center mb-2">
<div className="font-medium truncate">{selectedFile.name}</div>
<Button variant="ghost" size="icon" onClick={cancelUpload} className="h-6 w-6 min-w-6">
<X className="h-4 w-4" />
</Button>
</div>
<Progress value={uploadProgress} className="h-2" />
<div className="text-xs text-right mt-1 text-gray-500">{uploadProgress}%</div>
</div>
)}
{error && <div className="mt-2 text-sm text-red-500">{error}</div>}
</div>
)
}

View File

@@ -1,58 +0,0 @@
"use client"
import { usePathname } from "next/navigation"
import BottomNav from "./BottomNav"
import { VideoTutorialButton } from "@/components/VideoTutorialButton"
import type React from "react"
import { createContext, useContext, useState, useEffect } from "react"
// 创建视图模式上下文
const ViewModeContext = createContext<{ viewMode: "desktop" | "mobile" }>({ viewMode: "desktop" })
// 创建视图模式钩子函数
export function useViewMode() {
const context = useContext(ViewModeContext)
if (!context) {
throw new Error("useViewMode must be used within a LayoutWrapper")
}
return context
}
export default function LayoutWrapper({ children }: { children: React.ReactNode }) {
const pathname = usePathname()
const [viewMode, setViewMode] = useState<"desktop" | "mobile">("desktop")
// 检测视图模式
useEffect(() => {
const checkViewMode = () => {
setViewMode(window.innerWidth < 768 ? "mobile" : "desktop")
}
// 初始检测
checkViewMode()
// 监听窗口大小变化
window.addEventListener("resize", checkViewMode)
return () => {
window.removeEventListener("resize", checkViewMode)
}
}, [])
// 只在四个主页显示底部导航栏:首页、场景获客、工作台和我的
const mainPages = ["/", "/scenarios", "/workspace", "/profile"]
const showBottomNav = mainPages.includes(pathname)
return (
<ViewModeContext.Provider value={{ viewMode }}>
<div className="mx-auto w-full">
<main className="w-full mx-auto bg-white min-h-screen flex flex-col relative lg:max-w-7xl xl:max-w-[1200px]">
{children}
{showBottomNav && <BottomNav />}
{showBottomNav && <VideoTutorialButton />}
</main>
</div>
</ViewModeContext.Provider>
)
}

View File

@@ -1,72 +0,0 @@
"use client"
import { useState, useEffect } from "react"
import { toast } from "@/components/ui/use-toast"
interface SpeechToTextProcessorProps {
audioUrl: string
onTranscriptReady: (transcript: string) => void
onQuestionExtracted: (question: string) => void
enabled: boolean
}
export function SpeechToTextProcessor({
audioUrl,
onTranscriptReady,
onQuestionExtracted,
enabled = true,
}: SpeechToTextProcessorProps) {
const [isProcessing, setIsProcessing] = useState(false)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
if (!enabled || !audioUrl) return
const processAudio = async () => {
try {
setIsProcessing(true)
setError(null)
// 模拟API调用延迟
await new Promise((resolve) => setTimeout(resolve, 2000))
// 模拟转录结果
const mockTranscript = `
客服: 您好这里是XX公司客服请问有什么可以帮到您
客户: 请问贵公司的产品有什么特点?
客服: 我们的产品主要有以下几个特点:首先,质量非常可靠;其次,价格比较有竞争力;第三,售后服务非常完善。
客户: 那你们的价格是怎么样的?
客服: 我们有多种套餐可以选择基础版每月只需99元高级版每月299元具体可以根据您的需求来选择。
客户: 好的,我了解了,谢谢。
客服: 不客气,如果您有兴趣,我可以添加您的微信,给您发送更详细的产品资料。
客户: 可以的,谢谢。
客服: 好的,稍后我会添加您为好友,再次感谢您的咨询。
`
onTranscriptReady(mockTranscript)
// 提取首句问题
const questionMatch = mockTranscript.match(/客户: (.*?)\n/)
if (questionMatch && questionMatch[1]) {
onQuestionExtracted(questionMatch[1])
} else {
onQuestionExtracted("未识别到有效问题")
}
setIsProcessing(false)
} catch (err) {
setError("处理音频时出错")
setIsProcessing(false)
toast({
title: "处理失败",
description: "语音转文字处理失败,请重试",
variant: "destructive",
})
}
}
processAudio()
}, [audioUrl, enabled, onTranscriptReady, onQuestionExtracted])
return null // 这是一个功能性组件不渲染任何UI
}

View File

@@ -1,177 +0,0 @@
"use client"
import { useState, useEffect } from "react"
import { Card } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@/components/ui/table"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"
import { Plus, Pencil, Trash2 } from "lucide-react"
interface TrafficTeam {
id: string
name: string
commission: number
}
interface TrafficTeamSettingsProps {
formData?: any
onChange: (data: any) => void
}
export function TrafficTeamSettings({ formData = {}, onChange }: TrafficTeamSettingsProps) {
// Initialize teams with an empty array if formData.trafficTeams is undefined
const [teams, setTeams] = useState<TrafficTeam[]>([])
const [isAddTeamOpen, setIsAddTeamOpen] = useState(false)
const [editingTeam, setEditingTeam] = useState<TrafficTeam | null>(null)
const [newTeam, setNewTeam] = useState<Partial<TrafficTeam>>({
name: "",
commission: 0,
})
// Initialize teams state safely
useEffect(() => {
if (formData && Array.isArray(formData.trafficTeams)) {
setTeams(formData.trafficTeams)
} else {
// If formData.trafficTeams is undefined or not an array, initialize with empty array
// Also update the parent formData to include the empty trafficTeams array
setTeams([])
onChange({ ...formData, trafficTeams: [] })
}
}, [formData])
const handleAddTeam = () => {
if (!newTeam.name) return
const updatedTeams = [...teams]
if (editingTeam) {
const index = updatedTeams.findIndex((team) => team.id === editingTeam.id)
if (index !== -1) {
updatedTeams[index] = {
...updatedTeams[index],
name: newTeam.name || updatedTeams[index].name,
commission: newTeam.commission !== undefined ? newTeam.commission : updatedTeams[index].commission,
}
}
} else {
updatedTeams.push({
id: Date.now().toString(),
name: newTeam.name,
commission: newTeam.commission || 0,
})
}
setTeams(updatedTeams)
setIsAddTeamOpen(false)
setNewTeam({ name: "", commission: 0 })
setEditingTeam(null)
// Ensure we're creating a new object for formData to trigger proper updates
const updatedFormData = { ...(formData || {}), trafficTeams: updatedTeams }
onChange(updatedFormData)
}
const handleEditTeam = (team: TrafficTeam) => {
setEditingTeam(team)
setNewTeam(team)
setIsAddTeamOpen(true)
}
const handleDeleteTeam = (teamId: string) => {
const updatedTeams = teams.filter((team) => team.id !== teamId)
setTeams(updatedTeams)
// Ensure we're creating a new object for formData to trigger proper updates
const updatedFormData = { ...(formData || {}), trafficTeams: updatedTeams }
onChange(updatedFormData)
}
return (
<Card className="p-6">
<div className="space-y-6">
<div className="flex justify-between items-center">
<h2 className="text-lg font-semibold"></h2>
<Button onClick={() => setIsAddTeamOpen(true)}>
<Plus className="h-4 w-4 mr-2" />
</Button>
</div>
<div className="border rounded-lg">
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="text-right"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{teams.length === 0 ? (
<TableRow>
<TableCell colSpan={3} className="text-center py-8 text-gray-500">
</TableCell>
</TableRow>
) : (
teams.map((team) => (
<TableRow key={team.id}>
<TableCell>{team.name}</TableCell>
<TableCell>{team.commission}%</TableCell>
<TableCell className="text-right">
<div className="flex justify-end space-x-2">
<Button variant="ghost" size="sm" onClick={() => handleEditTeam(team)}>
<Pencil className="h-4 w-4" />
</Button>
<Button variant="ghost" size="sm" onClick={() => handleDeleteTeam(team.id)}>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
</div>
<Dialog open={isAddTeamOpen} onOpenChange={setIsAddTeamOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>{editingTeam ? "编辑团队" : "添加团队"}</DialogTitle>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label></Label>
<Input
value={newTeam.name}
onChange={(e) => setNewTeam({ ...newTeam, name: e.target.value })}
placeholder="请输入团队名称"
/>
</div>
<div className="space-y-2">
<Label> (%)</Label>
<Input
type="number"
value={newTeam.commission}
onChange={(e) => setNewTeam({ ...newTeam, commission: Number(e.target.value) })}
placeholder="请输入佣金比例"
/>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setIsAddTeamOpen(false)}>
</Button>
<Button onClick={handleAddTeam}>{editingTeam ? "保存" : "添加"}</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</Card>
)
}

View File

@@ -1,63 +0,0 @@
"use client"
import { useEffect, useState } from "react"
interface VoiceRecognitionProps {
onResult: (text: string) => void
onStop: () => void
}
export function VoiceRecognition({ onResult, onStop }: VoiceRecognitionProps) {
const [isListening, setIsListening] = useState(true)
useEffect(() => {
// 模拟语音识别
const timer = setTimeout(() => {
const mockResults = [
"你好,我想了解一下私域运营的基本策略",
"请帮我分析一下最近的销售数据",
"我需要一份客户画像分析报告",
"如何提高朋友圈内容的互动率?",
"帮我生成一个营销方案",
]
const randomResult = mockResults[Math.floor(Math.random() * mockResults.length)]
onResult(randomResult)
setIsListening(false)
}, 2000)
return () => {
clearTimeout(timer)
}
}, [onResult])
useEffect(() => {
if (!isListening) {
onStop()
}
}, [isListening, onStop])
return (
<div className="fixed inset-0 bg-black/20 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 shadow-xl max-w-sm w-full">
<div className="flex flex-col items-center">
<div className="relative w-20 h-20 mb-4">
<div className="absolute inset-0 bg-blue-100 rounded-full animate-ping opacity-25"></div>
<div className="relative bg-blue-500 rounded-full w-20 h-20 flex items-center justify-center">
<div className="w-4 h-16 bg-white rounded-full animate-pulse"></div>
</div>
</div>
<h3 className="text-lg font-medium mb-2">...</h3>
<p className="text-gray-500 text-center"></p>
<button
className="mt-4 px-4 py-2 bg-gray-200 rounded-md hover:bg-gray-300 transition-colors"
onClick={onStop}
>
</button>
</div>
</div>
</div>
)
}

View File

@@ -1,99 +0,0 @@
"use client"
import { useState, useEffect } from "react"
import { ResponsiveContainer, Tooltip, XAxis, YAxis, CartesianGrid, Area, AreaChart, Legend } from "recharts"
interface AcquisitionPlanChartProps {
data: { date: string; customers: number }[]
}
export function AcquisitionPlanChart({ data }: AcquisitionPlanChartProps) {
const [chartData, setChartData] = useState<any[]>([])
// 生成更真实的数据
useEffect(() => {
if (!data || data.length === 0) return
// 使用获客计划中的获客数和添加数作为指标模拟近7天数据
const enhancedData = data.map((item) => {
// 添加数通常是获客数的一定比例这里使用70%-90%的随机比例
const addRate = 0.7 + Math.random() * 0.2
const addedCount = Math.round(item.customers * addRate)
return {
date: item.date,
获客数: item.customers,
添加数: addedCount,
}
})
setChartData(enhancedData)
}, [data])
// 如果没有数据,显示空状态
if (!data || data.length === 0 || chartData.length === 0) {
return <div className="h-[180px] flex items-center justify-center text-gray-400"></div>
}
return (
<div className="h-[180px]">
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={chartData} margin={{ top: 5, right: 5, left: 0, bottom: 5 }}>
<defs>
<linearGradient id="colorCustomers" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#3b82f6" stopOpacity={0.8} />
<stop offset="95%" stopColor="#3b82f6" stopOpacity={0.1} />
</linearGradient>
<linearGradient id="colorAdded" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#8b5cf6" stopOpacity={0.8} />
<stop offset="95%" stopColor="#8b5cf6" stopOpacity={0.1} />
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#f0f0f0" />
<XAxis dataKey="date" axisLine={false} tickLine={false} tick={{ fontSize: 12, fill: "#6b7280" }} />
<YAxis axisLine={false} tickLine={false} tick={{ fontSize: 12, fill: "#6b7280" }} width={30} />
<Tooltip
contentStyle={{
backgroundColor: "rgba(255, 255, 255, 0.9)",
borderRadius: "6px",
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
border: "none",
padding: "8px",
}}
labelStyle={{ fontWeight: "bold", marginBottom: "4px" }}
/>
<Legend
verticalAlign="top"
height={36}
iconType="circle"
iconSize={8}
formatter={(value) => <span style={{ color: "#6b7280", fontSize: "12px" }}>{value}</span>}
/>
<Area
type="monotone"
dataKey="获客数"
name="获客数"
stroke="#3b82f6"
fillOpacity={1}
fill="url(#colorCustomers)"
strokeWidth={2}
dot={{ r: 3, strokeWidth: 2 }}
activeDot={{ r: 5, strokeWidth: 0 }}
/>
<Area
type="monotone"
dataKey="添加数"
name="添加数"
stroke="#8b5cf6"
fillOpacity={0.5}
fill="url(#colorAdded)"
strokeWidth={2}
dot={{ r: 3, strokeWidth: 2 }}
activeDot={{ r: 5, strokeWidth: 0 }}
/>
</AreaChart>
</ResponsiveContainer>
</div>
)
}

View File

@@ -1,55 +0,0 @@
"use client"
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from "recharts"
interface DailyAcquisitionData {
date: string
acquired: number
added: number
}
interface DailyAcquisitionChartProps {
data: DailyAcquisitionData[]
height?: number
}
export function DailyAcquisitionChart({ data, height = 200 }: DailyAcquisitionChartProps) {
return (
<div className="w-full" style={{ height: `${height}px` }}>
<ResponsiveContainer width="100%" height="100%">
<LineChart data={data} margin={{ top: 10, right: 30, left: 0, bottom: 0 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" />
<XAxis dataKey="date" stroke="#666" />
<YAxis stroke="#666" />
<Tooltip
contentStyle={{
backgroundColor: "white",
border: "1px solid #e5e7eb",
borderRadius: "6px",
}}
/>
<Legend />
<Line
type="monotone"
dataKey="acquired"
name="获客数量"
stroke="#3b82f6"
strokeWidth={2}
dot={{ fill: "#3b82f6" }}
activeDot={{ r: 6 }}
/>
<Line
type="monotone"
dataKey="added"
name="添加成功"
stroke="#10b981"
strokeWidth={2}
dot={{ fill: "#10b981" }}
activeDot={{ r: 6 }}
/>
</LineChart>
</ResponsiveContainer>
</div>
)
}

View File

@@ -1,75 +0,0 @@
"use client"
import { useState } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis, CartesianGrid } from "recharts"
// 模拟数据
const weeklyData = [
{ day: "周一", 获客数: 12, 添加好友: 8 },
{ day: "周二", 获客数: 18, 添加好友: 12 },
{ day: "周三", 获客数: 15, 添加好友: 10 },
{ day: "周四", 获客数: 25, 添加好友: 18 },
{ day: "周五", 获客数: 30, 添加好友: 22 },
{ day: "周六", 获客数: 18, 添加好友: 14 },
{ day: "周日", 获客数: 15, 添加好友: 11 },
]
const monthlyData = [
{ day: "1月", 获客数: 120, 添加好友: 85 },
{ day: "2月", 获客数: 180, 添加好友: 130 },
{ day: "3月", 获客数: 150, 添加好友: 110 },
{ day: "4月", 获客数: 250, 添加好友: 180 },
{ day: "5月", 获客数: 300, 添加好友: 220 },
{ day: "6月", 获客数: 280, 添加好友: 210 },
]
export function DeviceTreeChart() {
const [period, setPeriod] = useState("week")
const data = period === "week" ? weeklyData : monthlyData
return (
<Card className="w-full mt-4">
<CardHeader className="pb-2">
<div className="flex items-center justify-between">
<CardTitle className="text-base font-medium"></CardTitle>
<Tabs defaultValue="week" value={period} onValueChange={setPeriod} className="h-9">
<TabsList className="grid w-[180px] grid-cols-2">
<TabsTrigger value="week"></TabsTrigger>
<TabsTrigger value="month"></TabsTrigger>
</TabsList>
</Tabs>
</div>
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={250}>
<LineChart data={data} margin={{ top: 5, right: 10, left: 0, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" vertical={false} />
<XAxis dataKey="day" axisLine={false} tickLine={false} tick={{ fontSize: 12 }} />
<YAxis axisLine={false} tickLine={false} tick={{ fontSize: 12 }} />
<Tooltip />
<Line
type="monotone"
dataKey="获客数"
stroke="#4f46e5"
strokeWidth={2}
dot={{ r: 4 }}
activeDot={{ r: 6 }}
/>
<Line
type="monotone"
dataKey="添加好友"
stroke="#10b981"
strokeWidth={2}
dot={{ r: 4 }}
activeDot={{ r: 6 }}
/>
</LineChart>
</ResponsiveContainer>
</CardContent>
</Card>
)
}

View File

@@ -1,205 +0,0 @@
"use client"
import { useState } from "react"
import { ChevronDown, ChevronUp, MoreVertical, Copy, Pencil, Trash2, Clock, Link } from "lucide-react"
import { Button } from "@/components/ui/button"
import { useRouter } from "next/navigation"
import { Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis, CartesianGrid } from "recharts"
import { Card } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { DailyAcquisitionChart } from "./DailyAcquisitionChart"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
interface Task {
id: string
name: string
status: "running" | "paused" | "completed"
stats: {
devices: number
acquired: number
added: number
}
lastUpdated: string
executionTime: string
nextExecutionTime: string
trend: { date: string; customers: number }[]
dailyData?: { date: string; acquired: number; added: number }[]
}
interface ExpandableAcquisitionCardProps {
task: Task
channel: string
onCopy: (taskId: string) => void
onDelete: (taskId: string) => void
onOpenSettings?: (taskId: string) => void
onStatusChange?: (taskId: string, newStatus: "running" | "paused") => void
}
export function ExpandableAcquisitionCard({
task,
channel,
onCopy,
onDelete,
onOpenSettings,
onStatusChange,
}: ExpandableAcquisitionCardProps) {
const router = useRouter()
const [expanded, setExpanded] = useState(false)
const { devices: deviceCount, acquired: acquiredCount, added: addedCount } = task.stats
const passRate = calculatePassRate(acquiredCount, addedCount)
const handleEdit = (taskId: string) => {
router.push(`/scenarios/${channel}/edit/${taskId}`)
}
const toggleTaskStatus = () => {
if (onStatusChange) {
onStatusChange(task.id, task.status === "running" ? "paused" : "running")
}
}
return (
<div className="mb-6">
<Card className="p-6 hover:shadow-lg transition-all bg-white/80 backdrop-blur-sm">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center space-x-3">
<h3 className="font-medium text-lg">{task.name}</h3>
<Badge
variant={task.status === "running" ? "success" : "secondary"}
className="cursor-pointer hover:opacity-80"
onClick={toggleTaskStatus}
>
{task.status === "running" ? "进行中" : "已暂停"}
</Badge>
</div>
<div className="relative z-20">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8 hover:bg-gray-100 rounded-full">
<MoreVertical className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48">
<DropdownMenuItem onClick={() => handleEdit(task.id)}>
<Pencil className="w-4 h-4 mr-2" />
</DropdownMenuItem>
<DropdownMenuItem onClick={() => onCopy(task.id)}>
<Copy className="w-4 h-4 mr-2" />
</DropdownMenuItem>
{onOpenSettings && (
<DropdownMenuItem onClick={() => onOpenSettings(task.id)}>
<Link className="w-4 h-4 mr-2" />
</DropdownMenuItem>
)}
<DropdownMenuItem onClick={() => onDelete(task.id)} className="text-red-600">
<Trash2 className="w-4 h-4 mr-2" />
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
<div className="grid grid-cols-4 gap-2 mb-4">
<a href={`/scenarios/${channel}/devices`}>
<Card className="p-2 hover:bg-gray-50 transition-colors cursor-pointer">
<div className="text-sm text-gray-500 mb-1"></div>
<div className="text-2xl font-semibold">{deviceCount}</div>
</Card>
</a>
<a href={`/scenarios/${channel}/acquired`}>
<Card className="p-2 hover:bg-gray-50 transition-colors cursor-pointer">
<div className="text-sm text-gray-500 mb-1"></div>
<div className="text-2xl font-semibold">{acquiredCount}</div>
</Card>
</a>
<a href={`/scenarios/${channel}/added`}>
<Card className="p-2 hover:bg-gray-50 transition-colors cursor-pointer">
<div className="text-sm text-gray-500 mb-1"></div>
<div className="text-2xl font-semibold">{addedCount}</div>
</Card>
</a>
<Card className="p-2">
<div className="text-sm text-gray-500 mb-1"></div>
<div className="text-2xl font-semibold">{passRate}%</div>
</Card>
</div>
<div className="h-48 bg-white rounded-lg p-4 mb-4">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={task.trend}>
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" />
<XAxis dataKey="date" stroke="#666" />
<YAxis stroke="#666" />
<Tooltip
contentStyle={{
backgroundColor: "white",
border: "1px solid #e5e7eb",
borderRadius: "6px",
}}
/>
<Line
type="monotone"
dataKey="customers"
name="获客数"
stroke="#3b82f6"
strokeWidth={2}
dot={{ fill: "#3b82f6" }}
/>
</LineChart>
</ResponsiveContainer>
</div>
<div className="flex items-center justify-between text-sm border-t pt-4 text-gray-500">
<div className="flex items-center space-x-2">
<Clock className="w-4 h-4" />
<span>{task.lastUpdated}</span>
</div>
<div className="flex items-center space-x-2">
<Clock className="w-4 h-4" />
<span>{task.nextExecutionTime}</span>
</div>
</div>
</Card>
{expanded && task.dailyData && (
<div className="mt-4 bg-white p-6 rounded-lg shadow-sm">
<h4 className="text-lg font-medium mb-4"></h4>
<div className="h-64">
<DailyAcquisitionChart data={task.dailyData} />
</div>
</div>
)}
<div className="flex justify-center mt-2">
<Button variant="ghost" size="sm" onClick={() => setExpanded(!expanded)} className="text-gray-500">
{expanded ? (
<>
<ChevronUp className="h-4 w-4 mr-1" />
</>
) : (
<>
<ChevronDown className="h-4 w-4 mr-1" />
</>
)}
</Button>
</div>
</div>
)
}
// 计算通过率
function calculatePassRate(acquired: number, added: number) {
if (acquired === 0) return 0
return Math.round((added / acquired) * 100)
}

View File

@@ -1,97 +0,0 @@
"use client"
import type React from "react"
import { useState } from "react"
import { Card } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { TrafficTeamSettings } from "@/app/components/TrafficTeamSettings"
interface NewAcquisitionPlanFormProps {
onSubmit: (data: any) => void
onCancel: () => void
}
export function NewAcquisitionPlanForm({ onSubmit, onCancel }: NewAcquisitionPlanFormProps) {
const [formData, setFormData] = useState({
name: "",
description: "",
trafficTeams: [], // Initialize trafficTeams as an empty array
})
const [currentStep, setCurrentStep] = useState(1)
const handleChange = (data: any) => {
setFormData(data)
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
onSubmit(formData)
}
return (
<form onSubmit={handleSubmit} className="space-y-6">
<div className="steps-container">
{/* 步骤指示器 */}
<div className="step-indicators flex justify-between mb-8">
{[1, 2, 3, 4].map((step) => (
<div
key={step}
className={`flex flex-col items-center ${currentStep >= step ? "text-primary" : "text-gray-400"}`}
>
<div
className={`flex items-center justify-center w-8 h-8 rounded-full mb-2 ${
currentStep >= step ? "bg-primary text-white" : "bg-gray-100 text-gray-400"
}`}
>
{step}
</div>
<div className="text-sm font-medium">
{step === 1 && "基础设置"}
{step === 2 && "好友设置"}
{step === 3 && "消息设置"}
{step === 4 && "流量标签"}
</div>
</div>
))}
</div>
</div>
<Card className="p-6">
<div className="space-y-4">
<h2 className="text-lg font-semibold"></h2>
<div className="space-y-2">
<Label></Label>
<Input
placeholder="输入计划名称"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
required
/>
</div>
<div className="space-y-2">
<Label></Label>
<Input
placeholder="输入计划描述"
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
/>
</div>
</div>
</Card>
<TrafficTeamSettings formData={formData} onChange={handleChange} />
<div className="flex justify-end space-x-4">
<Button type="button" variant="outline" onClick={onCancel}>
</Button>
<Button type="submit"></Button>
</div>
</form>
)
}

View File

@@ -1,212 +0,0 @@
"use client"
import { Card } from "@/components/ui/card"
import type React from "react"
import { useState, useRef, useEffect } from "react"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { MoreHorizontal, Copy, Pencil, Trash2, Clock, Link } from "lucide-react"
interface Task {
id: string
name: string
status: "running" | "paused" | "completed"
stats: {
devices: number
acquired: number
added: number
}
lastUpdated: string
executionTime: string
nextExecutionTime: string
trend: { date: string; customers: number }[]
reqConf?: {
device?: string[]
selectedDevices?: string[]
}
}
interface ScenarioAcquisitionCardProps {
task: Task
channel: string
onEdit: (taskId: string) => void
onCopy: (taskId: string) => void
onDelete: (taskId: string) => void
onOpenSettings?: (taskId: string) => void
onStatusChange?: (taskId: string, newStatus: "running" | "paused") => void
}
export function ScenarioAcquisitionCard({
task,
channel,
onEdit,
onCopy,
onDelete,
onOpenSettings,
onStatusChange,
}: ScenarioAcquisitionCardProps) {
// 兼容后端真实数据结构
const deviceCount = Array.isArray(task.reqConf?.device)
? task.reqConf.device.length
: Array.isArray(task.reqConf?.selectedDevices)
? task.reqConf.selectedDevices.length
: 0
// 获客数和已添加数可根据 msgConf 或其它字段自定义
const acquiredCount = task.acquiredCount ?? 0
const addedCount = task.addedCount ?? 0
const passRate = task.passRate ?? 0
const [menuOpen, setMenuOpen] = useState(false)
const menuRef = useRef<HTMLDivElement>(null)
const isActive = task.status === 1;
const handleStatusChange = (e: React.MouseEvent) => {
e.stopPropagation()
if (onStatusChange) {
onStatusChange(task.id, task.status === "running" ? "paused" : "running")
}
}
const handleEdit = (e: React.MouseEvent) => {
e.stopPropagation()
setMenuOpen(false)
onEdit(task.id)
}
const handleCopy = (e: React.MouseEvent) => {
e.stopPropagation()
setMenuOpen(false)
onCopy(task.id)
}
const handleOpenSettings = (e: React.MouseEvent) => {
e.stopPropagation()
setMenuOpen(false)
if (onOpenSettings) {
onOpenSettings(task.id)
}
}
const handleDelete = (e: React.MouseEvent) => {
e.stopPropagation()
setMenuOpen(false)
onDelete(task.id)
}
const toggleMenu = (e: React.MouseEvent) => {
e.stopPropagation()
setMenuOpen(!menuOpen)
}
// 点击外部关闭菜单
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
setMenuOpen(false)
}
}
document.addEventListener("mousedown", handleClickOutside)
return () => {
document.removeEventListener("mousedown", handleClickOutside)
}
}, [])
return (
<Card className="p-6 hover:shadow-lg transition-all mb-4 bg-white/80">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center space-x-3">
<h3 className="font-medium text-lg">{task.name}</h3>
<Badge
variant={isActive ? "success" : "secondary"}
className="cursor-pointer hover:opacity-80"
onClick={handleStatusChange}
>
{isActive ? "进行中" : "已暂停"}
</Badge>
</div>
<div className="relative z-20" ref={menuRef}>
<Button variant="ghost" size="icon" className="h-8 w-8 hover:bg-gray-100 rounded-full" onClick={toggleMenu}>
<MoreHorizontal className="h-4 w-4" />
</Button>
{menuOpen && (
<div className="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg z-50 py-1 border">
<button
className="flex items-center w-full px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
onClick={handleEdit}
>
<Pencil className="w-4 h-4 mr-2" />
</button>
<button
className="flex items-center w-full px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
onClick={handleCopy}
>
<Copy className="w-4 h-4 mr-2" />
</button>
{onOpenSettings && (
<button
className="flex items-center w-full px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
onClick={handleOpenSettings}
>
<Link className="w-4 h-4 mr-2" />
</button>
)}
<button
className="flex items-center w-full px-4 py-2 text-sm text-red-600 hover:bg-gray-100"
onClick={handleDelete}
>
<Trash2 className="w-4 h-4 mr-2" />
</button>
</div>
)}
</div>
</div>
<div className="grid grid-cols-2 gap-2 mb-4">
<a href={`/scenarios/${channel}/devices`} className="block">
<Card className="p-2 hover:bg-gray-50 transition-colors cursor-pointer">
<div className="text-sm text-gray-500 mb-1"></div>
<div className="text-2xl font-semibold">{deviceCount}</div>
</Card>
</a>
<a href={`/scenarios/${channel}/acquired`} className="block">
<Card className="p-2 hover:bg-gray-50 transition-colors cursor-pointer">
<div className="text-sm text-gray-500 mb-1"></div>
<div className="text-2xl font-semibold">{acquiredCount}</div>
</Card>
</a>
<a href={`/scenarios/${channel}/added`} className="block">
<Card className="p-2 hover:bg-gray-50 transition-colors cursor-pointer">
<div className="text-sm text-gray-500 mb-1"></div>
<div className="text-2xl font-semibold">{addedCount}</div>
</Card>
</a>
<Card className="p-2">
<div className="text-sm text-gray-500 mb-1"></div>
<div className="text-2xl font-semibold">{passRate}%</div>
</Card>
</div>
<div className="flex items-center justify-between text-sm border-t pt-4 text-gray-500">
<div className="flex items-center space-x-2">
<Clock className="w-4 h-4" />
<span>{task.lastUpdated}</span>
</div>
</div>
</Card>
)
}
// 计算通过率
function calculatePassRate(acquired: number, added: number) {
if (acquired === 0) return 0
return Math.round((added / acquired) * 100)
}

View File

@@ -1,27 +0,0 @@
"use client"
import { useEffect } from "react"
import { useRouter, usePathname } from "next/navigation"
export function AuthCheck({ children }: { children: React.ReactNode }) {
const router = useRouter()
const pathname = usePathname()
useEffect(() => {
// 排除不需要登录的页面
const publicPaths = ['/login', '/register', '/forgot-password']
if (publicPaths.includes(pathname)) {
return
}
const token = localStorage.getItem('token')
if (!token) {
// 如果没有token重定向到登录页面并携带当前页面URL作为回调
const currentPath = window.location.pathname + window.location.search
router.push(`/login?redirect=${encodeURIComponent(currentPath)}`)
return
}
}, [router, pathname])
return <>{children}</>
}

View File

@@ -1,341 +0,0 @@
"use client"
import { useState, useEffect } from "react"
import { Card } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Checkbox } from "@/components/ui/checkbox"
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Battery, Smartphone, MessageCircle, Users, Clock, Search, Power, RefreshCcw, Settings, AlertTriangle } from "lucide-react"
import { ImeiDisplay } from "@/components/ImeiDisplay"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
export interface Device {
id: string
imei: string
name: string
status: "online" | "offline"
battery: number
wechatId: string
friendCount: number
todayAdded: number
messageCount: number
lastActive: string
addFriendStatus: "normal" | "abnormal"
}
interface DeviceGridProps {
devices: Device[]
selectable?: boolean
selectedDevices?: string[]
onSelect?: (deviceIds: string[]) => void
itemsPerRow?: number
}
export function DeviceGrid({
devices,
selectable = false,
selectedDevices = [],
onSelect,
itemsPerRow = 2,
}: DeviceGridProps) {
const [selectedDevice, setSelectedDevice] = useState<Device | null>(null)
const [searchTerm, setSearchTerm] = useState("")
const [filteredDevices, setFilteredDevices] = useState(devices)
useEffect(() => {
const filtered = devices.filter(
(device) =>
device.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
device.imei.includes(searchTerm) ||
device.wechatId.toLowerCase().includes(searchTerm.toLowerCase())
)
setFilteredDevices(filtered)
}, [searchTerm, devices])
const handleSelectAll = () => {
if (selectedDevices.length === filteredDevices.length) {
onSelect?.([])
} else {
onSelect?.(filteredDevices.map((d) => d.id))
}
}
return (
<div className="space-y-4">
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<div className="relative w-full sm:w-64">
<Search className="absolute left-2 top-2.5 h-4 w-4 text-gray-500" />
<Input
placeholder="搜索设备..."
className="pl-8"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
{selectable && (
<div className="flex items-center justify-between w-full sm:w-auto">
<div className="flex items-center space-x-2">
<Checkbox
checked={selectedDevices.length === filteredDevices.length && filteredDevices.length > 0}
onCheckedChange={handleSelectAll}
/>
<span className="text-sm"></span>
</div>
<span className="text-sm text-gray-500 ml-4"> {selectedDevices.length} </span>
</div>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{filteredDevices.map((device) => (
<Card
key={device.id}
className={`p-4 hover:shadow-md transition-all cursor-pointer ${
selectedDevices.includes(device.id) ? "ring-2 ring-primary" : ""
}`}
onClick={() => {
if (selectable) {
const newSelection = selectedDevices.includes(device.id)
? selectedDevices.filter((id) => id !== device.id)
: [...selectedDevices, device.id]
onSelect?.(newSelection)
} else {
setSelectedDevice(device)
}
}}
>
<div className="flex items-start space-x-3">
{selectable && (
<Checkbox
checked={selectedDevices.includes(device.id)}
className="mt-1"
onClick={(e) => e.stopPropagation()}
/>
)}
<div className="flex-1 space-y-2">
<div className="relative">
<div className="flex items-center justify-between">
<div className="font-medium flex items-center">
<span>{device.name}</span>
{device.addFriendStatus === "abnormal" && (
<Badge variant="destructive" className="ml-2 text-xs">
</Badge>
)}
</div>
<div className="absolute top-0 right-0">
<Badge
variant={device.status === "online" ? "default" : "secondary"}
className={`${
device.status === "online"
? "bg-green-100 text-green-800 hover:bg-green-200"
: "bg-gray-100 text-gray-800 hover:bg-gray-200"
}`}
>
<div className="flex items-center space-x-1">
<div className={`w-2 h-2 rounded-full ${
device.status === "online" ? "bg-green-500" : "bg-gray-500"
}`} />
<span>{device.status === "online" ? "在线" : "离线"}</span>
</div>
</Badge>
</div>
</div>
</div>
<div className="grid grid-cols-2 gap-3 text-sm">
<div className="flex items-center space-x-2 bg-gray-50 rounded-lg p-2">
<Battery className={`w-4 h-4 ${
device.battery < 20
? "text-red-500"
: device.battery < 50
? "text-yellow-500"
: "text-green-500"
}`} />
<span className={`${
device.battery < 20
? "text-red-700"
: device.battery < 50
? "text-yellow-700"
: "text-green-700"
}`}>{device.battery}%</span>
</div>
<div className="flex items-center space-x-2 bg-gray-50 rounded-lg p-2">
<Users className="w-4 h-4 text-blue-500" />
<span className="text-blue-700">{device.friendCount}</span>
</div>
<div className="flex items-center space-x-2 bg-gray-50 rounded-lg p-2">
<MessageCircle className="w-4 h-4 text-purple-500" />
<span className="text-purple-700">{device.messageCount}</span>
</div>
<div className="flex items-center space-x-2 bg-gray-50 rounded-lg p-2">
<Clock className="w-4 h-4 text-indigo-500" />
<span className="text-indigo-700">+{device.todayAdded}</span>
</div>
</div>
<div className="text-sm space-y-1.5 mt-3">
<div className="flex items-center text-gray-600">
<span className="w-16">IMEI:</span>
<ImeiDisplay imei={device.imei} containerWidth={120} />
</div>
<div className="flex items-center text-gray-600">
<span className="w-16">:</span>
<span className="font-mono">{device.wechatId}</span>
</div>
</div>
</div>
</div>
</Card>
))}
</div>
<Dialog open={!!selectedDevice} onOpenChange={() => setSelectedDevice(null)}>
<DialogContent className="max-w-3xl">
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
{selectedDevice && (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className={`p-3 rounded-lg ${
selectedDevice.status === "online"
? "bg-green-100"
: "bg-gray-100"
}`}>
<Smartphone className={`w-6 h-6 ${
selectedDevice.status === "online"
? "text-green-700"
: "text-gray-700"
}`} />
</div>
<div>
<h3 className="font-medium flex items-center space-x-2">
<span>{selectedDevice.name}</span>
<Badge
variant={selectedDevice.status === "online" ? "default" : "secondary"}
className={`${
selectedDevice.status === "online"
? "bg-green-100 text-green-800"
: "bg-gray-100 text-gray-800"
}`}
>
<div className="flex items-center space-x-1">
<div className={`w-2 h-2 rounded-full ${
selectedDevice.status === "online" ? "bg-green-500" : "bg-gray-500"
}`} />
<span>{selectedDevice.status === "online" ? "在线" : "离线"}</span>
</div>
</Badge>
</h3>
<div className="text-sm text-gray-500 mt-1 space-x-4">
<span>IMEI: <ImeiDisplay imei={selectedDevice.imei} containerWidth={160} /></span>
<span>: {selectedDevice.wechatId}</span>
</div>
</div>
</div>
<div className="flex items-center space-x-2">
<Button variant="outline" size="sm" className="space-x-1">
<RefreshCcw className="w-4 h-4" />
<span></span>
</Button>
<Button variant="outline" size="sm" className="space-x-1">
<Power className="w-4 h-4" />
<span></span>
</Button>
<Button variant="outline" size="sm">
<Settings className="w-4 h-4" />
</Button>
</div>
</div>
<Tabs defaultValue="status" className="w-full">
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="status"></TabsTrigger>
<TabsTrigger value="stats"></TabsTrigger>
<TabsTrigger value="tasks"></TabsTrigger>
<TabsTrigger value="logs"></TabsTrigger>
</TabsList>
<TabsContent value="status" className="space-y-4">
<div className="grid grid-cols-2 gap-4 mt-4">
<div className="space-y-1 bg-gray-50 p-4 rounded-lg">
<div className="text-sm text-gray-500"></div>
<div className="flex items-center space-x-2">
<Battery className={`w-5 h-5 ${
selectedDevice.battery < 20
? "text-red-500"
: selectedDevice.battery < 50
? "text-yellow-500"
: "text-green-500"
}`} />
<span className={`font-medium ${
selectedDevice.battery < 20
? "text-red-700"
: selectedDevice.battery < 50
? "text-yellow-700"
: "text-green-700"
}`}>{selectedDevice.battery}%</span>
</div>
</div>
<div className="space-y-1 bg-gray-50 p-4 rounded-lg">
<div className="text-sm text-gray-500"></div>
<div className="flex items-center space-x-2">
<Users className="w-5 h-5 text-blue-500" />
<span className="font-medium text-blue-700">{selectedDevice.friendCount}</span>
</div>
</div>
<div className="space-y-1 bg-gray-50 p-4 rounded-lg">
<div className="text-sm text-gray-500"></div>
<div className="flex items-center space-x-2">
<Users className="w-5 h-5 text-green-500" />
<span className="font-medium text-green-700">+{selectedDevice.todayAdded}</span>
</div>
</div>
<div className="space-y-1 bg-gray-50 p-4 rounded-lg">
<div className="text-sm text-gray-500"></div>
<div className="flex items-center space-x-2">
<MessageCircle className="w-5 h-5 text-purple-500" />
<span className="font-medium text-purple-700">{selectedDevice.messageCount}</span>
</div>
</div>
</div>
{selectedDevice.addFriendStatus === "abnormal" && (
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mt-4">
<div className="flex items-center space-x-2 text-red-800">
<AlertTriangle className="w-5 h-5" />
<span className="font-medium"></span>
</div>
<p className="text-sm text-red-600 mt-1">
</p>
</div>
)}
</TabsContent>
<TabsContent value="stats">
<div className="text-center text-gray-500 py-8">
...
</div>
</TabsContent>
<TabsContent value="tasks">
<div className="text-center text-gray-500 py-8">
...
</div>
</TabsContent>
<TabsContent value="logs">
<div className="text-center text-gray-500 py-8">
...
</div>
</TabsContent>
</Tabs>
</div>
)}
</DialogContent>
</Dialog>
</div>
)
}

View File

@@ -1,206 +0,0 @@
"use client"
import { useState, useEffect } from "react"
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
import { Search, RefreshCw, Filter } from "lucide-react"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Checkbox } from "@/components/ui/checkbox"
import { api } from "@/lib/api"
interface Device {
id: string
name: string
imei: string
status: "online" | "offline"
wechatAccounts: {
wechatId: string
nickname: string
remainingAdds: number
maxDailyAdds: number
}[]
}
interface DeviceSelectionDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
selectedDevices: string[]
onSelect: (deviceIds: string[]) => void
}
export function DeviceSelectionDialog({ open, onOpenChange, selectedDevices, onSelect }: DeviceSelectionDialogProps) {
const [devices, setDevices] = useState<Device[]>([])
const [loading, setLoading] = useState(false)
const [searchQuery, setSearchQuery] = useState("")
const [statusFilter, setStatusFilter] = useState("all")
const [selectedDeviceIds, setSelectedDeviceIds] = useState<string[]>([])
useEffect(() => {
if (open) setSelectedDeviceIds(selectedDevices)
}, [open, selectedDevices])
useEffect(() => {
if (!open) return
const fetchDevices = async () => {
setLoading(true)
try {
const params = []
if (searchQuery) params.push(`keyword=${encodeURIComponent(searchQuery)}`)
if (statusFilter !== "all") params.push(`status=${statusFilter}`)
params.push("page=1", "limit=100")
const url = `/v1/devices?${params.join("&")}`
const response = await api.get<any>(url)
const list = response.data?.list || response.data?.items || []
const devices = list.map((device: any) => ({
id: device.id?.toString() || device.id,
imei: device.imei || "",
name: device.memo || device.name || `设备_${device.id}`,
status: device.alive === 1 || device.status === "online" ? "online" : "offline",
wechatAccounts: [
{
wechatId: device.wechatId || device.wxid || "",
nickname: device.nickname || "",
remainingAdds: device.remainingAdds || 0,
maxDailyAdds: device.maxDailyAdds || 0,
},
],
}))
setDevices(devices)
} catch {
setDevices([])
} finally {
setLoading(false)
}
}
fetchDevices()
}, [open, searchQuery, statusFilter])
const handleSelectDevice = (deviceId: string) => {
setSelectedDeviceIds((prev) =>
prev.includes(deviceId) ? prev.filter((id) => id !== deviceId) : [...prev, deviceId]
)
}
const handleSelectAll = () => {
if (selectedDeviceIds.length === filteredDevices.length) {
setSelectedDeviceIds([])
} else {
setSelectedDeviceIds(filteredDevices.map((device) => device.id))
}
}
const handleConfirm = () => {
onSelect(selectedDeviceIds)
onOpenChange(false)
}
const handleCancel = () => {
setSelectedDeviceIds(selectedDevices)
onOpenChange(false)
}
const filteredDevices = devices.filter((device) => {
const searchLower = searchQuery.toLowerCase()
const matchesSearch =
(device.name || '').toLowerCase().includes(searchLower) ||
(device.imei || '').toLowerCase().includes(searchLower) ||
(device.wechatAccounts[0]?.wechatId || '').toLowerCase().includes(searchLower)
const matchesStatus =
statusFilter === "all" ||
(statusFilter === "online" && device.status === "online") ||
(statusFilter === "offline" && device.status === "offline")
return matchesSearch && matchesStatus
})
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-xl w-full p-0 rounded-2xl shadow-2xl max-h-[80vh]">
<DialogHeader>
<DialogTitle className="text-lg font-bold text-center py-3 border-b"></DialogTitle>
</DialogHeader>
<div className="p-6 pt-4">
{/* 搜索和筛选 */}
<div className="flex items-center gap-2 mb-4">
<Input
placeholder="搜索设备IMEI/备注/微信号"
value={searchQuery}
onChange={e => setSearchQuery(e.target.value)}
className="flex-1 rounded-lg border-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100"
/>
<select
className="border rounded-lg px-3 py-2 text-sm bg-gray-50 focus:border-blue-500"
value={statusFilter}
onChange={e => setStatusFilter(e.target.value)}
>
<option value="all"></option>
<option value="online">线</option>
<option value="offline">线</option>
</select>
</div>
{/* 设备列表 */}
<div className="max-h-[400px] overflow-y-auto space-y-2 pr-2">
{loading ? (
<div className="text-center text-gray-400 py-8">...</div>
) : filteredDevices.length === 0 ? (
<div className="text-center text-gray-400 py-8"></div>
) : (
filteredDevices.map(device => {
const checked = selectedDeviceIds.includes(device.id)
const wx = device.wechatAccounts[0] || {}
return (
<label
key={device.id}
className={`
flex items-center gap-3 p-4 rounded-xl border
${checked ? "border-blue-500 bg-blue-50" : "border-gray-200 bg-white"}
hover:border-blue-400 transition-colors cursor-pointer
`}
>
<input
type="checkbox"
className="accent-blue-500 scale-110"
checked={checked}
onChange={() => {
setSelectedDeviceIds(prev =>
prev.includes(device.id)
? prev.filter(id => id !== device.id)
: [...prev, device.id]
)
}}
/>
<div className="flex-1">
<div className="font-semibold text-base">{device.name}</div>
<div className="text-xs text-gray-500">IMEI: {device.imei}</div>
<div className="text-xs text-gray-400">: {wx.wechatId || '--'}{wx.nickname || '--'}</div>
</div>
<span className="flex items-center gap-1 text-xs font-medium">
<span className={`w-2 h-2 rounded-full ${device.status === 'online' ? 'bg-green-500' : 'bg-gray-300'}`}></span>
<span className={device.status === 'online' ? 'text-green-600' : 'text-gray-400'}>
{device.status === 'online' ? '在线' : '离线'}
</span>
</span>
</label>
)
})
)}
</div>
{/* 确认按钮 */}
<div className="flex justify-center mt-8">
<Button
className="w-4/5 py-3 rounded-full text-base font-bold shadow-md"
onClick={() => {
onSelect(selectedDeviceIds)
onOpenChange(false)
}}
>
</Button>
</div>
</div>
</DialogContent>
</Dialog>
)
}

View File

@@ -1,85 +0,0 @@
"use client"
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
import { Check } from "lucide-react"
interface PosterTemplate {
id: string
title: string
type: "领取" | "了解"
imageUrl: string
}
interface PosterSelectorProps {
open: boolean
onOpenChange: (open: boolean) => void
onSelect: (template: PosterTemplate) => void
}
const templates: PosterTemplate[] = [
{
id: "1",
title: "点击领取",
type: "领取",
imageUrl: "/placeholder.svg?height=400&width=300",
},
{
id: "2",
title: "点击了解",
type: "了解",
imageUrl: "/placeholder.svg?height=400&width=300",
},
// ... 其他模板
]
export function PosterSelector({ open, onOpenChange, onSelect }: PosterSelectorProps) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl">
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<div>
<h3 className="text-sm text-gray-500 mb-4">使</h3>
<div className="grid grid-cols-3 gap-6">
{templates.map((template) => (
<div
key={template.id}
className="group relative cursor-pointer"
onClick={() => {
onSelect(template)
onOpenChange(false)
}}
>
<div className="aspect-[3/4] rounded-lg overflow-hidden bg-gray-100">
<img
src={template.imageUrl || "/placeholder.svg"}
alt={template.title}
className="w-full h-full object-cover transition-transform group-hover:scale-105"
/>
</div>
<div className="absolute inset-0 flex items-center justify-center opacity-0 bg-black/50 group-hover:opacity-100 transition-opacity">
<Check className="w-8 h-8 text-white" />
</div>
<div className="mt-2 text-center">
<div className="font-medium">{template.title}</div>
<div className="text-sm text-gray-500">{template.type}</div>
</div>
</div>
))}
</div>
</div>
<div className="flex justify-end space-x-2 mt-4">
<Button variant="outline" onClick={() => onOpenChange(false)}>
</Button>
<Button></Button>
</div>
</DialogContent>
</Dialog>
)
}

View File

@@ -1,324 +0,0 @@
"use client"
import { useState, useEffect } from "react"
import { Card } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Search, Filter } from "lucide-react"
import { Checkbox } from "@/components/ui/checkbox"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
interface UserTag {
id: string
name: string
color: string
}
interface TrafficUser {
id: string
avatar: string
nickname: string
wechatId: string
phone: string
region: string
note: string
status: "pending" | "added" | "failed"
addTime: string
source: string
assignedTo: string
category: "potential" | "customer" | "lost"
tags: UserTag[]
}
interface TrafficPoolSelectorProps {
open: boolean
onOpenChange: (open: boolean) => void
selectedUsers: TrafficUser[]
onSelect: (users: TrafficUser[]) => void
}
export function TrafficPoolSelector({ open, onOpenChange, selectedUsers, onSelect }: TrafficPoolSelectorProps) {
const [users, setUsers] = useState<TrafficUser[]>([])
const [loading, setLoading] = useState(false)
const [searchQuery, setSearchQuery] = useState("")
const [activeCategory, setActiveCategory] = useState("all")
const [sourceFilter, setSourceFilter] = useState("all")
const [tagFilter, setTagFilter] = useState("all")
const [selectedUserIds, setSelectedUserIds] = useState<string[]>([])
// 初始化已选用户
useEffect(() => {
if (open) {
setSelectedUserIds(selectedUsers.map((user) => user.id))
}
}, [open, selectedUsers])
// 模拟获取用户数据
useEffect(() => {
if (!open) return
const fetchUsers = async () => {
setLoading(true)
try {
// 模拟API请求
await new Promise((resolve) => setTimeout(resolve, 800))
// 生成模拟数据
const mockUsers: TrafficUser[] = Array.from({ length: 30 }, (_, i) => {
// 随机标签
const tagPool = [
{ id: "tag1", name: "潜在客户", color: "bg-blue-100 text-blue-800" },
{ id: "tag2", name: "高意向", color: "bg-green-100 text-green-800" },
{ id: "tag3", name: "已成交", color: "bg-purple-100 text-purple-800" },
{ id: "tag4", name: "需跟进", color: "bg-yellow-100 text-yellow-800" },
{ id: "tag5", name: "活跃用户", color: "bg-indigo-100 text-indigo-800" },
{ id: "tag6", name: "沉默用户", color: "bg-gray-100 text-gray-800" },
{ id: "tag7", name: "企业客户", color: "bg-red-100 text-red-800" },
{ id: "tag8", name: "个人用户", color: "bg-pink-100 text-pink-800" },
]
const randomTags = Array.from(
{ length: Math.floor(Math.random() * 3) + 1 },
() => tagPool[Math.floor(Math.random() * tagPool.length)],
)
// 确保标签唯一
const uniqueTags = randomTags.filter((tag, index, self) => index === self.findIndex((t) => t.id === tag.id))
const sources = ["抖音直播", "小红书", "微信朋友圈", "视频号", "公众号"]
const statuses = ["pending", "added", "failed"] as const
return {
id: `user-${i + 1}`,
avatar: `/placeholder.svg?height=40&width=40`,
nickname: `用户${i + 1}`,
wechatId: `wxid_${Math.random().toString(36).substring(2, 10)}`,
phone: `1${Math.floor(Math.random() * 9 + 1)}${Array(9)
.fill(0)
.map(() => Math.floor(Math.random() * 10))
.join("")}`,
region: ["北京", "上海", "广州", "深圳", "杭州"][Math.floor(Math.random() * 5)],
note: Math.random() > 0.7 ? `这是用户${i + 1}的备注信息` : "",
status: statuses[Math.floor(Math.random() * 3)],
addTime: new Date(Date.now() - Math.floor(Math.random() * 30) * 24 * 60 * 60 * 1000).toISOString(),
source: sources[Math.floor(Math.random() * sources.length)],
assignedTo: Math.random() > 0.5 ? `销售${Math.floor(Math.random() * 5) + 1}` : "",
category: ["potential", "customer", "lost"][Math.floor(Math.random() * 3)] as
| "potential"
| "customer"
| "lost",
tags: uniqueTags,
}
})
setUsers(mockUsers)
} catch (error) {
console.error("Failed to fetch users:", error)
} finally {
setLoading(false)
}
}
fetchUsers()
}, [open])
// 过滤用户
const filteredUsers = users.filter((user) => {
const matchesSearch =
searchQuery === "" ||
user.nickname.toLowerCase().includes(searchQuery.toLowerCase()) ||
user.wechatId.toLowerCase().includes(searchQuery.toLowerCase()) ||
user.phone.includes(searchQuery)
const matchesCategory = activeCategory === "all" || user.category === activeCategory
const matchesSource = sourceFilter === "all" || user.source === sourceFilter
const matchesTag = tagFilter === "all" || user.tags.some((tag) => tag.id === tagFilter)
return matchesSearch && matchesCategory && matchesSource && matchesTag
})
// 处理选择用户
const handleSelectUser = (userId: string) => {
setSelectedUserIds((prev) => (prev.includes(userId) ? prev.filter((id) => id !== userId) : [...prev, userId]))
}
// 处理全选
const handleSelectAll = () => {
if (selectedUserIds.length === filteredUsers.length) {
setSelectedUserIds([])
} else {
setSelectedUserIds(filteredUsers.map((user) => user.id))
}
}
// 处理确认选择
const handleConfirm = () => {
const selectedUsersList = users.filter((user) => selectedUserIds.includes(user.id))
onSelect(selectedUsersList)
onOpenChange(false)
}
// 获取所有标签选项
const allTags = Array.from(new Set(users.flatMap((user) => user.tags).map((tag) => JSON.stringify(tag)))).map(
(tag) => JSON.parse(tag) as UserTag,
)
// 获取所有来源选项
const allSources = Array.from(new Set(users.map((user) => user.source)))
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-3xl max-h-[90vh] flex flex-col">
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<div className="flex-1 overflow-hidden flex flex-col">
{/* 搜索和筛选区域 */}
<div className="space-y-4 mb-4">
<div className="flex items-center space-x-2">
<div className="relative flex-1">
<Search className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
<Input
placeholder="搜索用户名/微信号/手机号"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9"
/>
</div>
<Button variant="outline" size="icon">
<Filter className="h-4 w-4" />
</Button>
</div>
{/* 分类标签页 */}
<Tabs defaultValue="all" value={activeCategory} onValueChange={setActiveCategory}>
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="all"></TabsTrigger>
<TabsTrigger value="potential"></TabsTrigger>
<TabsTrigger value="customer"></TabsTrigger>
<TabsTrigger value="lost"></TabsTrigger>
</TabsList>
</Tabs>
{/* 筛选器 */}
<div className="flex space-x-2">
<Select value={sourceFilter} onValueChange={setSourceFilter}>
<SelectTrigger className="w-[140px]">
<SelectValue placeholder="来源" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
{allSources.map((source) => (
<SelectItem key={source} value={source}>
{source}
</SelectItem>
))}
</SelectContent>
</Select>
<Select value={tagFilter} onValueChange={setTagFilter}>
<SelectTrigger className="w-[140px]">
<SelectValue placeholder="标签" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
{allTags.map((tag) => (
<SelectItem key={tag.id} value={tag.id}>
{tag.name}
</SelectItem>
))}
</SelectContent>
</Select>
<Button variant="outline" className="ml-auto" onClick={handleSelectAll}>
{selectedUserIds.length === filteredUsers.length && filteredUsers.length > 0 ? "取消全选" : "全选"}
</Button>
</div>
</div>
{/* 用户列表 */}
<ScrollArea className="flex-1">
{loading ? (
<div className="flex items-center justify-center h-40">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
</div>
) : filteredUsers.length === 0 ? (
<div className="text-center py-8 text-gray-500">
{searchQuery || activeCategory !== "all" || sourceFilter !== "all" || tagFilter !== "all"
? "没有符合条件的用户"
: "暂无用户数据"}
</div>
) : (
<div className="space-y-2 pr-4">
{filteredUsers.map((user) => (
<Card
key={user.id}
className={`p-3 hover:shadow-md transition-shadow ${
selectedUserIds.includes(user.id) ? "border-primary" : ""
}`}
>
<div className="flex items-center space-x-3">
<Checkbox
checked={selectedUserIds.includes(user.id)}
onCheckedChange={() => handleSelectUser(user.id)}
id={`user-${user.id}`}
/>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between mb-1">
<label htmlFor={`user-${user.id}`} className="font-medium truncate cursor-pointer">
{user.nickname}
</label>
<div
className={`px-2 py-1 rounded-full text-xs ${
user.status === "added"
? "bg-green-100 text-green-800"
: user.status === "pending"
? "bg-yellow-100 text-yellow-800"
: "bg-red-100 text-red-800"
}`}
>
{user.status === "added" ? "已添加" : user.status === "pending" ? "待处理" : "已失败"}
</div>
</div>
<div className="text-sm text-gray-500">: {user.wechatId}</div>
<div className="text-sm text-gray-500">: {user.source}</div>
{/* 标签展示 */}
<div className="flex flex-wrap gap-1 mt-2">
{user.tags.map((tag) => (
<span key={tag.id} className={`text-xs px-2 py-0.5 rounded-full ${tag.color}`}>
{tag.name}
</span>
))}
</div>
</div>
</div>
</Card>
))}
</div>
)}
</ScrollArea>
</div>
<DialogFooter className="flex items-center justify-between pt-4 border-t">
<div className="text-sm">
<span className="font-medium text-primary">{selectedUserIds.length}</span>
</div>
<div className="space-x-2">
<Button variant="outline" onClick={() => onOpenChange(false)}>
</Button>
<Button onClick={handleConfirm}></Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
)
}

View File

@@ -1,42 +0,0 @@
"use client"
import type React from "react"
import { cn } from "@/app/lib/utils"
interface CardGridProps {
children: React.ReactNode
className?: string
columns?: {
sm?: number
md?: number
lg?: number
xl?: number
}
gap?: "none" | "sm" | "md" | "lg"
}
/**
* 自适应卡片网格组件
* 根据屏幕尺寸自动调整卡片布局
*/
export function CardGrid({ children, className, columns = { sm: 1, md: 2, lg: 3, xl: 4 }, gap = "md" }: CardGridProps) {
// 根据gap参数设置间距
const gapClasses = {
none: "gap-0",
sm: "gap-2",
md: "gap-4",
lg: "gap-6",
}
// 根据columns参数设置网格列数
const getGridCols = () => {
const cols = []
if (columns.sm) cols.push(`grid-cols-${columns.sm}`)
if (columns.md) cols.push(`md:grid-cols-${columns.md}`)
if (columns.lg) cols.push(`lg:grid-cols-${columns.lg}`)
if (columns.xl) cols.push(`xl:grid-cols-${columns.xl}`)
return cols.join(" ")
}
return <div className={cn("grid w-full", getGridCols(), gapClasses[gap], className)}>{children}</div>
}

View File

@@ -1,138 +0,0 @@
"use client"
import { Card } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Clock, TrendingUp, Users } from "lucide-react"
interface StatsCardProps {
title: string
value: string | number
description?: string
trend?: number
trendLabel?: string
className?: string
valueClassName?: string
}
/**
* 统计数据卡片
* 用于展示关键指标数据
*/
export function StatsCard({
title,
value,
description,
trend,
trendLabel,
className = "",
valueClassName = "text-xl font-bold text-blue-600",
}: StatsCardProps) {
return (
<Card className={`p-3 ${className}`}>
<div className="text-sm text-gray-500">{title}</div>
<div className={valueClassName}>{value}</div>
{description && <div className="text-xs text-gray-500 mt-1">{description}</div>}
{trend !== undefined && (
<div className={`flex items-center text-xs mt-1 ${trend >= 0 ? "text-green-600" : "text-red-600"}`}>
{trend >= 0 ? "↑" : "↓"} {Math.abs(trend)}% {trendLabel || ""}
</div>
)}
</Card>
)
}
interface DistributionPlanCardProps {
id: string
name: string
status: "active" | "paused" | "completed"
source: string
sourceIcon: string
targetGroups: string[]
totalUsers: number
dailyAverage: number
lastUpdated: string
createTime: string
creator: string
onView?: (id: string) => void
onEdit?: (id: string) => void
onDelete?: (id: string) => void
onToggleStatus?: (id: string, status: "active" | "paused") => void
}
/**
* 流量分发计划卡片
* 用于展示流量分发计划信息
*/
export function DistributionPlanCard({
id,
name,
status,
source,
sourceIcon,
targetGroups,
totalUsers,
dailyAverage,
lastUpdated,
createTime,
creator,
onView,
onEdit,
onDelete,
onToggleStatus,
}: DistributionPlanCardProps) {
return (
<Card className="p-4">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center space-x-2">
<span className="text-xl mr-1">{sourceIcon}</span>
<h3 className="font-medium">{name}</h3>
<Badge variant={status === "active" ? "success" : status === "completed" ? "outline" : "secondary"}>
{status === "active" ? "进行中" : status === "completed" ? "已完成" : "已暂停"}
</Badge>
</div>
<div className="flex items-center space-x-2">
{onToggleStatus && status !== "completed" && (
<Button
variant="ghost"
size="sm"
onClick={() => onToggleStatus(id, status === "active" ? "paused" : "active")}
>
{status === "active" ? "暂停" : "启动"}
</Button>
)}
{onView && (
<Button variant="ghost" size="sm" onClick={() => onView(id)}>
<Users className="h-4 w-4 mr-1" />
</Button>
)}
{onEdit && (
<Button variant="ghost" size="sm" onClick={() => onEdit(id)}>
<TrendingUp className="h-4 w-4 mr-1" />
</Button>
)}
</div>
</div>
<div className="grid grid-cols-2 gap-4 mb-4">
<div className="text-sm text-gray-500">
<div>{targetGroups.join(", ")}</div>
<div>{totalUsers} </div>
</div>
<div className="text-sm text-gray-500">
<div>{dailyAverage} </div>
<div>{creator}</div>
</div>
</div>
<div className="flex items-center justify-between text-xs text-gray-500 border-t pt-4">
<div className="flex items-center">
<Clock className="w-4 h-4 mr-1" />
{lastUpdated}
</div>
<div>{createTime}</div>
</div>
</Card>
)
}

View File

@@ -1,87 +0,0 @@
"use client"
import type React from "react"
import { cn } from "@/app/lib/utils"
interface FormLayoutProps {
children: React.ReactNode
className?: string
layout?: "vertical" | "horizontal" | "responsive"
labelWidth?: string
gap?: "none" | "sm" | "md" | "lg"
}
/**
* 自适应表单布局组件
* 支持垂直、水平和响应式布局
*/
export function FormLayout({
children,
className,
layout = "responsive",
labelWidth = "w-32",
gap = "md",
}: FormLayoutProps) {
// 根据gap参数设置间距
const gapClasses = {
none: "space-y-0",
sm: "space-y-2",
md: "space-y-4",
lg: "space-y-6",
}
// 根据layout参数设置布局类
const getLayoutClasses = () => {
switch (layout) {
case "horizontal":
return "form-horizontal"
case "vertical":
return "form-vertical"
case "responsive":
return "form-vertical md:form-horizontal"
default:
return "form-vertical"
}
}
return (
<div
className={cn("w-full", getLayoutClasses(), gapClasses[gap], className, {
[`[--label-width:${labelWidth}]`]: layout !== "vertical",
})}
>
{children}
</div>
)
}
export default FormLayout
interface FormItemProps {
children: React.ReactNode
label?: React.ReactNode
className?: string
required?: boolean
error?: string
}
/**
* 表单项组件
* 配合FormLayout使用
*/
export function FormItem({ children, label, className, required, error }: FormItemProps) {
return (
<div className={cn("form-item", className)}>
{label && (
<div className="form-label">
{label}
{required && <span className="text-red-500 ml-1">*</span>}
</div>
)}
<div className="form-control">
{children}
{error && <div className="text-red-500 text-sm mt-1">{error}</div>}
</div>
</div>
)
}

View File

@@ -1,273 +0,0 @@
"use client"
/**
* 表单组件模板
*
* 包含项目中常用的各种表单组件
*/
import type React from "react"
import { Label } from "@/components/ui/label"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { Slider } from "@/components/ui/slider"
import { Switch } from "@/components/ui/switch"
interface FormFieldProps {
label: string
htmlFor: string
required?: boolean
description?: string
error?: string
children: React.ReactNode
}
/**
* 表单字段容器
* 用于包装表单控件
*/
export function FormField({ label, htmlFor, required = false, description, error, children }: FormFieldProps) {
return (
<div className="space-y-2">
<Label htmlFor={htmlFor}>
{label} {required && <span className="text-red-500">*</span>}
</Label>
{children}
{description && <p className="text-xs text-gray-500">{description}</p>}
{error && <p className="text-xs text-red-500">{error}</p>}
</div>
)
}
interface TextInputFieldProps {
label: string
id: string
value: string
onChange: (value: string) => void
placeholder?: string
required?: boolean
description?: string
error?: string
type?: string
}
/**
* 文本输入字段
* 用于文本输入
*/
export function TextInputField({
label,
id,
value,
onChange,
placeholder,
required = false,
description,
error,
type = "text",
}: TextInputFieldProps) {
return (
<FormField label={label} htmlFor={id} required={required} description={description} error={error}>
<Input id={id} type={type} value={value} onChange={(e) => onChange(e.target.value)} placeholder={placeholder} />
</FormField>
)
}
interface TextareaFieldProps {
label: string
id: string
value: string
onChange: (value: string) => void
placeholder?: string
required?: boolean
description?: string
error?: string
}
/**
* 多行文本输入字段
* 用于多行文本输入
*/
export function TextareaField({
label,
id,
value,
onChange,
placeholder,
required = false,
description,
error,
}: TextareaFieldProps) {
return (
<FormField label={label} htmlFor={id} required={required} description={description} error={error}>
<Textarea id={id} value={value} onChange={(e) => onChange(e.target.value)} placeholder={placeholder} />
</FormField>
)
}
interface SelectFieldProps {
label: string
id: string
value: string
onChange: (value: string) => void
options: Array<{ value: string; label: string }>
placeholder?: string
required?: boolean
description?: string
error?: string
}
/**
* 下拉选择字段
* 用于从选项中选择
*/
export function SelectField({
label,
id,
value,
onChange,
options,
placeholder = "请选择",
required = false,
description,
error,
}: SelectFieldProps) {
return (
<FormField label={label} htmlFor={id} required={required} description={description} error={error}>
<Select value={value} onValueChange={onChange}>
<SelectTrigger id={id}>
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent>
{options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</FormField>
)
}
interface RadioFieldProps {
label: string
name: string
value: string
onChange: (value: string) => void
options: Array<{ value: string; label: string; description?: string }>
required?: boolean
description?: string
error?: string
}
/**
* 单选按钮组字段
* 用于从选项中单选
*/
export function RadioField({
label,
name,
value,
onChange,
options,
required = false,
description,
error,
}: RadioFieldProps) {
return (
<div className="space-y-2">
<Label>
{label} {required && <span className="text-red-500">*</span>}
</Label>
<RadioGroup value={value} onValueChange={onChange} className="space-y-3">
{options.map((option) => (
<div key={option.value} className="flex items-center space-x-2">
<RadioGroupItem value={option.value} id={`${name}-${option.value}`} />
<Label htmlFor={`${name}-${option.value}`} className="cursor-pointer">
{option.label}
</Label>
{option.description && <span className="text-xs text-gray-500 ml-2">({option.description})</span>}
</div>
))}
</RadioGroup>
{description && <p className="text-xs text-gray-500">{description}</p>}
{error && <p className="text-xs text-red-500">{error}</p>}
</div>
)
}
interface SliderFieldProps {
label: string
id: string
value: number
onChange: (value: number) => void
min: number
max: number
step?: number
unit?: string
required?: boolean
description?: string
error?: string
}
/**
* 滑块字段
* 用于数值范围选择
*/
export function SliderField({
label,
id,
value,
onChange,
min,
max,
step = 1,
unit = "",
required = false,
description,
error,
}: SliderFieldProps) {
return (
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label htmlFor={id}>
{label} {required && <span className="text-red-500">*</span>}
</Label>
<span className="text-sm font-medium">
{value} {unit}
</span>
</div>
<Slider id={id} min={min} max={max} step={step} value={[value]} onValueChange={(values) => onChange(values[0])} />
{description && <p className="text-xs text-gray-500">{description}</p>}
{error && <p className="text-xs text-red-500">{error}</p>}
</div>
)
}
interface SwitchFieldProps {
label: string
id: string
checked: boolean
onCheckedChange: (checked: boolean) => void
description?: string
disabled?: boolean
}
/**
* 开关字段
* 用于布尔值选择
*/
export function SwitchField({ label, id, checked, onCheckedChange, description, disabled = false }: SwitchFieldProps) {
return (
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label htmlFor={id}>{label}</Label>
<Switch id={id} checked={checked} onCheckedChange={onCheckedChange} disabled={disabled} />
</div>
{description && <p className="text-xs text-gray-500">{description}</p>}
</div>
)
}

View File

@@ -1,12 +0,0 @@
/**
* 存客宝UI组件模板库
*
* 这个文件导出所有可重用的UI组件模板方便在项目中快速引用
*/
export * from "./cards"
export * from "./forms"
export * from "./layouts"
export * from "./tables"
export * from "./stats"
export * from "./selectors"

View File

@@ -1,119 +0,0 @@
"use client"
/**
* 布局组件模板
*
* 包含项目中常用的各种布局组件
*/
import type React from "react"
import { Button } from "@/components/ui/button"
import { ChevronLeft } from "lucide-react"
interface PageHeaderProps {
title: string
onBack?: () => void
actionButton?: React.ReactNode
}
/**
* 页面头部组件
* 用于页面顶部的标题和操作区
*/
export function PageHeader({ title, onBack, actionButton }: PageHeaderProps) {
return (
<header className="sticky top-0 z-10 bg-white border-b">
<div className="flex items-center justify-between p-4">
<div className="flex items-center space-x-3">
{onBack && (
<Button variant="ghost" size="icon" onClick={onBack}>
<ChevronLeft className="h-5 w-5" />
</Button>
)}
<h1 className="text-lg font-medium">{title}</h1>
</div>
{actionButton}
</div>
</header>
)
}
interface StepIndicatorProps {
steps: Array<{
step: number
title: string
icon: React.ReactNode
}>
currentStep: number
}
/**
* 步骤指示器组件
* 用于多步骤流程的导航
*/
export function StepIndicator({ steps, currentStep }: StepIndicatorProps) {
return (
<div className="mb-6">
<div className="flex items-center justify-between">
{steps.map(({ step, title, icon }) => (
<div key={step} className="flex flex-col items-center">
<div
className={`w-10 h-10 rounded-full flex items-center justify-center ${
step === currentStep
? "bg-blue-600 text-white"
: step < currentStep
? "bg-green-500 text-white"
: "bg-gray-200 text-gray-500"
}`}
>
{step < currentStep ? "✓" : icon}
</div>
<span className="text-xs mt-1">{title}</span>
</div>
))}
</div>
<div className="relative mt-2">
<div className="absolute top-0 left-0 right-0 h-1 bg-gray-200"></div>
<div
className="absolute top-0 left-0 h-1 bg-blue-600 transition-all"
style={{ width: `${((currentStep - 1) / (steps.length - 1)) * 100}%` }}
></div>
</div>
</div>
)
}
interface StepNavigationProps {
onBack: () => void
onNext: () => void
nextLabel?: string
backLabel?: string
isLastStep?: boolean
isNextDisabled?: boolean
}
/**
* 步骤导航组件
* 用于多步骤流程的前进后退按钮
*/
export function StepNavigation({
onBack,
onNext,
nextLabel = "下一步",
backLabel = "上一步",
isLastStep = false,
isNextDisabled = false,
}: StepNavigationProps) {
return (
<div className="pt-4 flex justify-between">
<Button variant="outline" onClick={onBack}>
<ChevronLeft className="mr-2 h-4 w-4" />
{backLabel}
</Button>
<Button onClick={onNext} disabled={isNextDisabled}>
{isLastStep ? "完成" : nextLabel}
{!isLastStep && <span className="ml-2"></span>}
</Button>
</div>
)
}

View File

@@ -1,49 +0,0 @@
"use client"
import type React from "react"
import { cn } from "@/app/lib/utils"
import { Button } from "@/app/components/ui/button"
import { ChevronLeft } from "lucide-react"
import { useRouter } from "next/navigation"
interface PageHeaderProps {
title: React.ReactNode
subtitle?: React.ReactNode
backButton?: boolean
backUrl?: string
actions?: React.ReactNode
className?: string
}
/**
* 页面标题组件
* 支持返回按钮和操作按钮
*/
export function PageHeader({ title, subtitle, backButton = false, backUrl, actions, className }: PageHeaderProps) {
const router = useRouter()
const handleBack = () => {
if (backUrl) {
router.push(backUrl)
} else {
router.back()
}
}
return (
<div className={cn("flex flex-col md:flex-row md:items-center justify-between gap-4 mb-6", className)}>
<div className="flex items-center gap-2">
{backButton && (
<Button variant="ghost" size="icon" onClick={handleBack} className="h-8 w-8">
<ChevronLeft className="h-4 w-4" />
</Button>
)}
<div>
<h1 className="text-2xl font-bold">{title}</h1>
{subtitle && <p className="text-muted-foreground mt-1">{subtitle}</p>}
</div>
</div>
{actions && <div className="flex items-center gap-2">{actions}</div>}
</div>
)
}

View File

@@ -1,103 +0,0 @@
"use client"
import React from "react"
import { cn } from "@/app/lib/utils"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/app/components/ui/table"
interface Column<T> {
key: string
title: React.ReactNode
render?: (value: any, record: T, index: number) => React.ReactNode
width?: number | string
className?: string
responsive?: boolean // 是否在小屏幕上显示
}
interface ResponsiveTableProps<T> {
columns: Column<T>[]
dataSource: T[]
rowKey?: string | ((record: T) => string)
className?: string
loading?: boolean
emptyText?: React.ReactNode
}
/**
* 响应式表格组件
* 在小屏幕上自动隐藏不重要的列
*/
export function ResponsiveTable<T extends Record<string, any>>({
columns,
dataSource,
rowKey = "id",
className,
loading = false,
emptyText = "暂无数据",
}: ResponsiveTableProps<T>) {
// 根据屏幕尺寸过滤列
const [visibleColumns, setVisibleColumns] = React.useState(columns)
React.useEffect(() => {
const handleResize = () => {
if (window.innerWidth < 768) {
// 在小屏幕上只显示responsive不为false的列
setVisibleColumns(columns.filter((col) => col.responsive !== false))
} else {
setVisibleColumns(columns)
}
}
handleResize()
window.addEventListener("resize", handleResize)
return () => window.removeEventListener("resize", handleResize)
}, [columns])
// 获取行的唯一键
const getRowKey = (record: T, index: number) => {
if (typeof rowKey === "function") {
return rowKey(record)
}
return record[rowKey] || index
}
return (
<div className="responsive-table-container">
<Table className={cn("w-full", className)}>
<TableHeader>
<TableRow>
{visibleColumns.map((column) => (
<TableHead key={column.key} className={column.className} style={{ width: column.width }}>
{column.title}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
<TableRow>
<TableCell colSpan={visibleColumns.length} className="text-center h-24">
...
</TableCell>
</TableRow>
) : dataSource.length === 0 ? (
<TableRow>
<TableCell colSpan={visibleColumns.length} className="text-center h-24">
{emptyText}
</TableCell>
</TableRow>
) : (
dataSource.map((record, index) => (
<TableRow key={getRowKey(record, index)}>
{visibleColumns.map((column) => (
<TableCell key={column.key} className={column.className}>
{column.render ? column.render(record[column.key], record, index) : record[column.key]}
</TableCell>
))}
</TableRow>
))
)}
</TableBody>
</Table>
</div>
)
}

View File

@@ -1,130 +0,0 @@
"use client"
import { Card } from "@/components/ui/card"
import { Checkbox } from "@/components/ui/checkbox"
import { Label } from "@/components/ui/label"
import { Badge } from "@/components/ui/badge"
interface CustomerServiceRepProps {
id: string
name: string
deviceId: string
status: "online" | "offline"
avatar?: string
selected: boolean
disabled?: boolean
onSelect: (id: string, checked: boolean) => void
}
/**
* 客服代表选择卡片
* 用于选择客服代表
*/
export function CustomerServiceRepCard({
id,
name,
deviceId,
status,
avatar,
selected,
disabled = false,
onSelect,
}: CustomerServiceRepProps) {
return (
<Card
className={`p-3 hover:shadow-md transition-shadow cursor-pointer ${
selected ? "border-primary bg-blue-50" : ""
} ${disabled ? "opacity-50 pointer-events-none" : ""}`}
onClick={() => !disabled && onSelect(id, !selected)}
>
<div className="flex items-center space-x-3">
<Checkbox
checked={selected}
onCheckedChange={() => !disabled && onSelect(id, !selected)}
disabled={disabled}
id={`rep-${id}`}
/>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between mb-1">
<label htmlFor={`rep-${id}`} className="font-medium truncate cursor-pointer">
{name}
</label>
<div
className={`px-2 py-1 rounded-full text-xs ${
status === "online" ? "bg-green-100 text-green-800" : "bg-red-100 text-red-800"
}`}
>
{status === "online" ? "在线" : "离线"}
</div>
</div>
<div className="text-sm text-gray-500">ID: {deviceId}</div>
<div className="text-sm text-gray-500">ID: {id}</div>
</div>
</div>
</Card>
)
}
interface DeviceSelectProps {
allDevices: boolean
newDevices: boolean
targetDevices: string[]
onAllDevicesChange: (checked: boolean) => void
onNewDevicesChange: (checked: boolean) => void
onShowDeviceSelector: () => void
}
/**
* 设备选择组件
* 用于选择设备
*/
export function DeviceSelectSection({
allDevices,
newDevices,
targetDevices,
onAllDevicesChange,
onNewDevicesChange,
onShowDeviceSelector,
}: DeviceSelectProps) {
return (
<div className="space-y-4">
<div className="flex items-center space-x-2">
<Checkbox
id="allDevices"
checked={allDevices}
onCheckedChange={(checked) => onAllDevicesChange(checked === true)}
/>
<Label htmlFor="allDevices" className="font-medium">
</Label>
<span className="text-xs text-gray-500">(线)</span>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="newDevices"
checked={newDevices}
onCheckedChange={(checked) => onNewDevicesChange(checked === true)}
disabled={allDevices}
/>
<Label htmlFor="newDevices" className="font-medium">
</Label>
<span className="text-xs text-gray-500">()</span>
</div>
<div className="space-y-2 pt-4">
<Label className="font-medium"></Label>
<p className="text-xs text-gray-500 mb-2"></p>
<button
className="w-full flex items-center justify-center space-x-2 border border-gray-300 rounded-md py-2 px-4 hover:bg-gray-50 transition-colors"
disabled={allDevices}
onClick={onShowDeviceSelector}
>
<span></span>
{targetDevices?.length > 0 && <Badge variant="secondary"> {targetDevices.length} </Badge>}
</button>
</div>
</div>
)
}

View File

@@ -1,58 +0,0 @@
/**
* 统计组件模板
*
* 包含项目中常用的各种统计和数据展示组件
*/
import type React from "react"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
interface StatsSummaryProps {
stats: Array<{
title: string
value: string | number
color?: string
}>
}
/**
* 统计摘要组件
* 用于展示多个统计数据
*/
export function StatsSummary({ stats }: StatsSummaryProps) {
return (
<div className="grid grid-cols-2 gap-3">
{stats.map((stat, index) => (
<Card key={index} className="p-3">
<div className="text-sm text-gray-500">{stat.title}</div>
<div className={`text-xl font-bold ${stat.color || "text-blue-600"}`}>{stat.value}</div>
</Card>
))}
</div>
)
}
interface DataCardProps {
title: string
children: React.ReactNode
className?: string
headerAction?: React.ReactNode
}
/**
* 数据卡片组件
* 用于包装数据展示内容
*/
export function DataCard({ title, children, className = "", headerAction }: DataCardProps) {
return (
<Card className={className}>
<CardHeader className="pb-2">
<div className="flex items-center justify-between">
<CardTitle className="text-base">{title}</CardTitle>
{headerAction}
</div>
</CardHeader>
<CardContent>{children}</CardContent>
</Card>
)
}

View File

@@ -1,46 +0,0 @@
"use client"
import { cn } from "@/app/lib/utils"
interface StepIndicatorProps {
steps: { id: number; title: string; subtitle?: string }[]
currentStep: number
className?: string
}
export function StepIndicator({ steps, currentStep, className }: StepIndicatorProps) {
return (
<div className={cn("w-full", className)}>
<div className="flex justify-between items-center mb-2">
{steps.map((step) => (
<div
key={step.id}
className={cn("flex flex-col items-center", currentStep === step.id ? "text-blue-600" : "text-gray-400")}
>
<div
className={cn(
"w-10 h-10 rounded-full flex items-center justify-center text-lg font-medium mb-1",
currentStep === step.id
? "bg-blue-600 text-white"
: currentStep > step.id
? "bg-blue-100 text-blue-600"
: "bg-gray-200 text-gray-500",
)}
>
{step.id}
</div>
<div className="text-xs">{step.title}</div>
{step.subtitle && <div className="text-xs font-medium">{step.subtitle}</div>}
</div>
))}
</div>
<div className="relative h-1 bg-gray-200 mt-2">
<div
className="absolute top-0 left-0 h-full bg-blue-600 transition-all duration-300"
style={{ width: `${((currentStep - 1) / (steps.length - 1)) * 100}%` }}
/>
</div>
</div>
)
}

View File

@@ -1,198 +0,0 @@
"use client"
/**
* 表格组件模板
*
* 包含项目中常用的各种表格组件
*/
import type React from "react"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Checkbox } from "@/components/ui/checkbox"
import { Badge } from "@/components/ui/badge"
interface Column<T> {
header: string
accessorKey: keyof T | ((row: T) => React.ReactNode)
cell?: (row: T) => React.ReactNode
}
interface DataTableProps<T> {
data: T[]
columns: Column<T>[]
keyField: keyof T
selectable?: boolean
selectedRows?: (string | number)[]
onSelectRow?: (id: string | number, checked: boolean) => void
onSelectAll?: (checked: boolean) => void
onRowClick?: (row: T) => void
emptyMessage?: string
}
/**
* 通用数据表格
* 用于展示表格数据
*/
export function DataTable<T extends Record<string, any>>({
data,
columns,
keyField,
selectable = false,
selectedRows = [],
onSelectRow,
onSelectAll,
onRowClick,
emptyMessage = "没有数据",
}: DataTableProps<T>) {
const handleRowClick = (row: T) => {
if (onRowClick) {
onRowClick(row)
}
}
return (
<div className="border rounded-md">
<Table>
<TableHeader>
<TableRow>
{selectable && onSelectRow && onSelectAll && (
<TableHead className="w-12">
<Checkbox
checked={data.length > 0 && selectedRows.length === data.length}
onCheckedChange={(checked) => onSelectAll(checked === true)}
/>
</TableHead>
)}
{columns.map((column, index) => (
<TableHead key={index}>{column.header}</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{data.length === 0 ? (
<TableRow>
<TableCell colSpan={columns.length + (selectable ? 1 : 0)} className="text-center py-6 text-gray-500">
{emptyMessage}
</TableCell>
</TableRow>
) : (
data.map((row) => (
<TableRow
key={String(row[keyField])}
className={onRowClick ? "cursor-pointer hover:bg-gray-50" : ""}
onClick={() => handleRowClick(row)}
>
{selectable && onSelectRow && (
<TableCell className="w-12" onClick={(e) => e.stopPropagation()}>
<Checkbox
checked={selectedRows.includes(row[keyField])}
onCheckedChange={(checked) => onSelectRow(row[keyField], checked === true)}
/>
</TableCell>
)}
{columns.map((column, index) => (
<TableCell key={index}>
{column.cell
? column.cell(row)
: typeof column.accessorKey === "function"
? column.accessorKey(row)
: row[column.accessorKey]}
</TableCell>
))}
</TableRow>
))
)}
</TableBody>
</Table>
</div>
)
}
interface DeviceTableProps {
devices: Array<{
id: string
name: string
imei: string
status: "online" | "offline"
battery?: number
wechatId?: string
lastActive?: string
}>
selectedDevices: string[]
onSelectDevice: (deviceId: string, checked: boolean) => void
onSelectAll: (checked: boolean) => void
onDeviceClick: (deviceId: string) => void
}
/**
* 设备表格
* 用于展示设备列表
*/
export function DeviceTableTemplate({
devices,
selectedDevices,
onSelectDevice,
onSelectAll,
onDeviceClick,
}: DeviceTableProps) {
const columns: Column<(typeof devices)[0]>[] = [
{
header: "设备名称",
accessorKey: "name",
cell: (row) => (
<div>
<div className="font-medium">{row.name}</div>
<div className="text-xs text-gray-500">IMEI: {row.imei}</div>
</div>
),
},
{
header: "状态",
accessorKey: "status",
cell: (row) => (
<Badge variant={row.status === "online" ? "success" : "secondary"}>
{row.status === "online" ? "在线" : "离线"}
</Badge>
),
},
{
header: "微信号",
accessorKey: "wechatId",
cell: (row) => row.wechatId || "-",
},
{
header: "电量",
accessorKey: "battery",
cell: (row) => (
<div className="flex items-center">
<div className="w-16 h-2 bg-gray-200 rounded-full overflow-hidden mr-2">
<div
className={`h-full ${(row.battery || 0) > 20 ? "bg-green-500" : "bg-red-500"}`}
style={{ width: `${row.battery || 0}%` }}
></div>
</div>
<span>{row.battery || 0}%</span>
</div>
),
},
{
header: "最后活跃",
accessorKey: "lastActive",
cell: (row) => row.lastActive || "-",
},
]
return (
<DataTable
data={devices}
columns={columns}
keyField="id"
selectable={true}
selectedRows={selectedDevices}
onSelectRow={onSelectDevice}
onSelectAll={onSelectAll}
onRowClick={(row) => onDeviceClick(row.id)}
emptyMessage="没有找到设备"
/>
)
}

View File

@@ -1,57 +0,0 @@
"use client"
import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"
const Accordion = AccordionPrimitive.Root
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item ref={ref} className={cn("border-b", className)} {...props} />
))
AccordionItem.displayName = "AccordionItem"
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
className,
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
))
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className={cn(
"overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down",
className,
)}
{...props}
>
<div className="pb-4 pt-0">{children}</div>
</AccordionPrimitive.Content>
))
AccordionContent.displayName = AccordionPrimitive.Content.displayName
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }

View File

@@ -1,41 +0,0 @@
"use client"
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { cn } from "@/lib/utils"
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image ref={ref} className={cn("aspect-square h-full w-full", className)} {...props} />
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn("flex h-full w-full items-center justify-center rounded-full bg-muted", className)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
export { Avatar, AvatarImage, AvatarFallback }

View File

@@ -1,30 +0,0 @@
import type * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
},
)
export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return <div className={cn(badgeVariants({ variant }), className)} {...props} />
}
export { Badge, badgeVariants }

View File

@@ -1,60 +0,0 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, children, ...props }, ref) => {
// If asChild is true and the first child is a valid element, clone it with the button props
if (asChild && React.isValidElement(children)) {
return React.cloneElement(children, {
className: cn(buttonVariants({ variant, size, className })),
ref,
...props,
...children.props,
})
}
return (
<button className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props}>
{children}
</button>
)
},
)
Button.displayName = "Button"
export { Button, buttonVariants }

View File

@@ -1,55 +0,0 @@
"use client"
import type * as React from "react"
import { ChevronLeft, ChevronRight } from "lucide-react"
import { DayPicker } from "react-day-picker"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
export type CalendarProps = React.ComponentProps<typeof DayPicker>
function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) {
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn("p-3", className)}
classNames={{
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
month: "space-y-4",
caption: "flex justify-center pt-1 relative items-center",
caption_label: "text-sm font-medium",
nav: "space-x-1 flex items-center",
nav_button: cn(
buttonVariants({ variant: "outline" }),
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
),
nav_button_previous: "absolute left-1",
nav_button_next: "absolute right-1",
table: "w-full border-collapse space-y-1",
head_row: "flex",
head_cell: "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
row: "flex w-full mt-2",
cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
day: cn(buttonVariants({ variant: "ghost" }), "h-9 w-9 p-0 font-normal aria-selected:opacity-100"),
day_selected:
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
day_today: "bg-accent text-accent-foreground",
day_outside: "text-muted-foreground opacity-50",
day_disabled: "text-muted-foreground opacity-50",
day_range_middle: "aria-selected:bg-accent aria-selected:text-accent-foreground",
day_hidden: "invisible",
...classNames,
}}
components={{
IconLeft: ({ ...props }) => <ChevronLeft className="h-4 w-4" />,
IconRight: ({ ...props }) => <ChevronRight className="h-4 w-4" />,
}}
{...props}
/>
)
}
Calendar.displayName = "Calendar"
export { Calendar }

Some files were not shown because too many files have changed in this diff Show More