diff --git a/.gitignore b/.gitignore
index df8964e0..fca0d36b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,9 +5,11 @@ Store_vue/node_modules/
Cunkebao/.specstory/
*.cursorindexingignore
Server/.specstory/
+Server/thinkphp/
Store_vue/.specstory/
Store_vue/unpackage/
Store_vue/.vscode/
SuperAdmin/.specstory/
Cunkebao/dist
Touchkebao/.specstory/
+Serverruntime/
diff --git a/Cunkebao/pnpm-lock.yaml b/Cunkebao/pnpm-lock.yaml
index 818ed7d7..99f410fa 100644
--- a/Cunkebao/pnpm-lock.yaml
+++ b/Cunkebao/pnpm-lock.yaml
@@ -26,9 +26,6 @@ importers:
dayjs:
specifier: ^1.11.13
version: 1.11.13
- dexie:
- specifier: ^4.2.0
- version: 4.2.0
echarts:
specifier: ^5.6.0
version: 5.6.0
@@ -1070,9 +1067,6 @@ packages:
engines: {node: '>=0.10'}
hasBin: true
- dexie@4.2.0:
- resolution: {integrity: sha512-OSeyyWOUetDy9oFWeddJgi83OnRA3hSFh3RrbltmPgqHszE9f24eUCVLI4mPg0ifsWk0lQTdnS+jyGNrPMvhDA==}
-
dir-glob@3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
@@ -3410,8 +3404,6 @@ snapshots:
detect-libc@1.0.3:
optional: true
- dexie@4.2.0: {}
-
dir-glob@3.0.1:
dependencies:
path-type: 4.0.0
diff --git a/Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/InputMessage/index.module.scss b/Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/InputMessage/index.module.scss
deleted file mode 100644
index b8085ce9..00000000
--- a/Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/InputMessage/index.module.scss
+++ /dev/null
@@ -1,147 +0,0 @@
-.chatFooter {
- background: #f7f7f7;
- border-top: 1px solid #e1e1e1;
- padding: 0;
- height: auto;
- border-radius: 8px;
-}
-
-.inputContainer {
- padding: 8px 12px;
- display: flex;
- flex-direction: column;
- gap: 6px;
-}
-
-.inputToolbar {
- display: flex;
- align-items: center;
- padding: 4px 0;
-}
-
-.leftTool {
- display: flex;
- gap: 4px;
- align-items: center;
-}
-
-.toolbarButton {
- width: 28px;
- height: 28px;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 4px;
- color: #666;
- font-size: 16px;
- transition: all 0.15s;
- border: none;
- background: transparent;
-
- &:hover {
- background: #e6e6e6;
- color: #333;
- }
-
- &:active {
- background: #d9d9d9;
- }
-}
-
-.inputArea {
- display: flex;
- flex-direction: column;
- padding: 4px 0;
-}
-
-.inputWrapper {
- border: 1px solid #d1d1d1;
- border-radius: 4px;
- background: #fff;
- overflow: hidden;
-
- &:focus-within {
- border-color: #07c160;
- }
-}
-
-.messageInput {
- width: 100%;
- border: none;
- resize: none;
- font-size: 13px;
- line-height: 1.4;
- padding: 8px 10px;
- background: transparent;
-
- &:focus {
- box-shadow: none;
- outline: none;
- }
-
- &::placeholder {
- color: #b3b3b3;
- }
-}
-
-.sendButtonArea {
- padding: 8px 10px;
- display: flex;
- justify-content: flex-end;
- gap: 8px;
-}
-
-.sendButton {
- height: 32px;
- border-radius: 4px;
- font-weight: normal;
- min-width: 60px;
- font-size: 13px;
- background: #07c160;
- border-color: #07c160;
-
- &:hover {
- background: #06ad56;
- border-color: #06ad56;
- }
-
- &:active {
- background: #059748;
- border-color: #059748;
- }
-
- &:disabled {
- background: #b3b3b3;
- border-color: #b3b3b3;
- opacity: 1;
- }
-}
-
-.hintButton {
- border: none;
- background: transparent;
- color: #666;
- font-size: 12px;
-
- &:hover {
- color: #333;
- }
-}
-
-.inputHint {
- font-size: 11px;
- color: #999;
- text-align: right;
- margin-top: 2px;
-}
-
-@media (max-width: 768px) {
- .inputToolbar {
- flex-wrap: wrap;
- gap: 8px;
- }
-
- .sendButtonArea {
- justify-content: space-between;
- }
-}
diff --git a/Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.module.scss b/Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.module.scss
deleted file mode 100644
index fee50c0a..00000000
--- a/Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.module.scss
+++ /dev/null
@@ -1,265 +0,0 @@
-.stepContent {
- .stepHeader {
- margin-bottom: 20px;
-
- h3 {
- font-size: 18px;
- font-weight: 600;
- color: #1a1a1a;
- margin: 0 0 8px 0;
- }
-
- p {
- font-size: 14px;
- color: #666;
- margin: 0;
- }
- }
-}
-
-.step3Content {
- display: flex;
- gap: 24px;
- align-items: flex-start;
-
- .leftColumn {
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: 20px;
- }
-
- .rightColumn {
- width: 400px;
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: 20px;
- }
-
- .messagePreview {
- border: 2px dashed #52c41a;
- border-radius: 8px;
- padding: 20px;
- background: #f6ffed;
-
- .previewTitle {
- font-size: 14px;
- color: #52c41a;
- font-weight: 500;
- margin-bottom: 12px;
- }
-
- .messageBubble {
- min-height: 60px;
- padding: 12px;
- background: #fff;
- border-radius: 6px;
- color: #666;
- font-size: 14px;
- line-height: 1.6;
-
- .currentEditingLabel {
- font-size: 12px;
- color: #999;
- margin-bottom: 8px;
- }
-
- .messageText {
- color: #333;
- white-space: pre-wrap;
- word-break: break-word;
- }
- }
- }
-
- .savedScriptGroups {
- .scriptGroupTitle {
- font-size: 14px;
- font-weight: 500;
- color: #333;
- margin-bottom: 12px;
- }
-
- .scriptGroupItem {
- border: 1px solid #e8e8e8;
- border-radius: 8px;
- padding: 12px;
- margin-bottom: 12px;
- background: #fff;
-
- .scriptGroupHeader {
- display: flex;
- justify-content: space-between;
- align-items: center;
-
- .scriptGroupLeft {
- display: flex;
- align-items: center;
- gap: 8px;
- flex: 1;
-
- :global(.ant-radio) {
- margin-right: 4px;
- }
-
- .scriptGroupName {
- font-size: 14px;
- font-weight: 500;
- color: #333;
- }
-
- .messageCount {
- font-size: 12px;
- color: #999;
- margin-left: 8px;
- }
- }
-
- .scriptGroupActions {
- display: flex;
- gap: 4px;
-
- .actionButton {
- padding: 4px;
- color: #666;
-
- &:hover {
- color: #1890ff;
- }
- }
- }
- }
-
- .scriptGroupContent {
- margin-top: 8px;
- padding-top: 8px;
- border-top: 1px solid #f0f0f0;
- font-size: 13px;
- color: #666;
- }
- }
- }
-
- .messageInputArea {
- .messageInput {
- margin-bottom: 12px;
- }
-
- .attachmentButtons {
- display: flex;
- gap: 8px;
- margin-bottom: 12px;
- }
-
- .aiRewriteSection {
- display: flex;
- align-items: center;
- margin-bottom: 8px;
- }
-
- .messageHint {
- font-size: 12px;
- color: #999;
- }
- }
-
- .settingsPanel {
- border: 1px solid #e8e8e8;
- border-radius: 8px;
- padding: 20px;
- background: #fafafa;
-
- .settingsTitle {
- font-size: 14px;
- font-weight: 500;
- color: #1a1a1a;
- margin-bottom: 16px;
- }
-
- .settingItem {
- margin-bottom: 20px;
-
- &:last-child {
- margin-bottom: 0;
- }
-
- .settingLabel {
- font-size: 14px;
- font-weight: 500;
- color: #1a1a1a;
- margin-bottom: 12px;
- }
-
- .settingControl {
- display: flex;
- align-items: center;
- gap: 8px;
-
- span {
- font-size: 14px;
- color: #666;
- min-width: 80px;
- }
- }
- }
- }
-
- .tagSection {
- .settingLabel {
- font-size: 14px;
- font-weight: 500;
- color: #1a1a1a;
- margin-bottom: 12px;
- }
- }
-
- .pushPreview {
- border: 1px solid #e8e8e8;
- border-radius: 8px;
- padding: 20px;
- background: #f0f7ff;
-
- .previewTitle {
- font-size: 14px;
- font-weight: 500;
- color: #1a1a1a;
- margin-bottom: 12px;
- }
-
- ul {
- list-style: none;
- padding: 0;
- margin: 0;
-
- li {
- font-size: 14px;
- color: #666;
- line-height: 1.8;
- }
- }
- }
-}
-
-@media (max-width: 1200px) {
- .step3Content {
- .rightColumn {
- width: 350px;
- }
- }
-}
-
-@media (max-width: 768px) {
- .step3Content {
- flex-direction: column;
-
- .leftColumn {
- width: 100%;
- }
-
- .rightColumn {
- width: 100%;
- }
- }
-}
-
diff --git a/Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.tsx b/Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.tsx
deleted file mode 100644
index 082831d5..00000000
--- a/Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import ContentSelection from "@/components/ContentSelection";
-import { ContentItem } from "@/components/ContentSelection/data";
-import InputMessage from "./InputMessage/InputMessage";
-import styles from "./index.module.scss";
-
-interface StepSendMessageProps {
diff --git a/Moncter/src/pages/pc/ckbox/weChat/api.ts b/Moncter/src/pages/pc/ckbox/weChat/api.ts
deleted file mode 100644
index e69de29b..00000000
diff --git a/Moncter/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/index.tsx b/Moncter/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/index.tsx
deleted file mode 100644
index e69de29b..00000000
diff --git a/Server/application/api/controller/WebSocketController.php b/Server/application/api/controller/WebSocketController.php
index 773cbb3d..b9b8a860 100644
--- a/Server/application/api/controller/WebSocketController.php
+++ b/Server/application/api/controller/WebSocketController.php
@@ -457,22 +457,16 @@ class WebSocketController extends BaseController
// 构建请求参数
$params = [
- "cmdType" => 'CmdDownloadMomentImagesResult',
+ "cmdType" => 'CmdDownloadMomentImages',
"snsId" => $data['snsId'],
"urls" => $data['snsUrls'],
"wechatAccountId" => $data['wechatAccountId'],
"seq" => time(),
];
-
// 记录请求日志
Log::info('获取朋友圈资源链接请求:' . json_encode($params, 256));
- // 发送请求
- $this->client->send(json_encode($params));
-
- // 接收响应
- $response = $this->client->receive();
- $message = json_decode($response, true);
+ $message = $this->sendMessage($params);
if (empty($message)) {
return json_encode(['code' => 500, 'msg' => '获取朋友圈资源链接失败']);
@@ -558,15 +552,17 @@ class WebSocketController extends BaseController
$dataToSave['create_time'] = time();
$res = WechatMoments::create($dataToSave);
}
- // // 获取资源链接
- // if(empty($momentEntity['resUrls']) && !empty($momentEntity['urls'])){
- // $snsData = [
- // 'snsId' => $moment['snsId'],
- // 'snsUrls' => $momentEntity['urls'],
- // 'wechatAccountId' => $wechatAccountId,
- // ];
- // $this->getMomentSourceRealUrl($snsData);
- // }
+
+
+ // 获取资源链接
+ if(empty($momentEntity['resUrls']) && !empty($momentEntity['urls']) && $moment['type'] == 1) {
+ $snsData = [
+ 'snsId' => $moment['snsId'],
+ 'snsUrls' => $momentEntity['urls'],
+ 'wechatAccountId' => $wechatAccountId,
+ ];
+ $this->getMomentSourceRealUrl($snsData);
+ }
}
//Log::write('朋友圈数据已存入数据库,共' . count($momentList) . '条');
diff --git a/Server/application/api/model/WechatAccountModel.php b/Server/application/api/model/WechatAccountModel.php
index 2f72b908..919da478 100644
--- a/Server/application/api/model/WechatAccountModel.php
+++ b/Server/application/api/model/WechatAccountModel.php
@@ -7,5 +7,27 @@ use think\Model;
class WechatAccountModel extends Model
{
// 设置表名
- protected $table = 's2_wechat_account';
+ protected $table = 's2_wechat_account';
+
+ // 定义字段类型
+ protected $type = [
+ 'healthScore' => 'integer',
+ 'baseScore' => 'integer',
+ 'dynamicScore' => 'integer',
+ 'isModifiedAlias' => 'integer',
+ 'frequentCount' => 'integer',
+ 'consecutiveNoFrequentDays' => 'integer',
+ 'lastFrequentTime' => 'integer',
+ 'lastNoFrequentTime' => 'integer',
+ 'scoreUpdateTime' => 'integer',
+ ];
+
+ // 允许批量赋值的字段
+ protected $field = [
+ 'id', 'wechatId', 'alias', 'nickname', 'avatar', 'gender', 'region', 'signature',
+ 'healthScore', 'baseScore', 'dynamicScore', 'isModifiedAlias',
+ 'lastFrequentTime', 'frequentCount', 'lastNoFrequentTime',
+ 'consecutiveNoFrequentDays', 'scoreUpdateTime',
+ 'createTime', 'updateTime', 'status', 'isDeleted'
+ ];
}
\ No newline at end of file
diff --git a/Server/application/command.php b/Server/application/command.php
index f1d4d72b..b5c4d0e5 100644
--- a/Server/application/command.php
+++ b/Server/application/command.php
@@ -39,4 +39,7 @@ return [
'workbench:groupCreate' => 'app\command\WorkbenchGroupCreateCommand', // 工作台群创建任务
'workbench:import-contact' => 'app\command\WorkbenchImportContactCommand', // 工作台通讯录导入任务
'kf:notice' => 'app\command\KfNoticeCommand', // 客服端消息通知
+
+ 'wechat:calculate-score' => 'app\command\CalculateWechatAccountScoreCommand', // 统一计算微信账号健康分
+ 'wechat:update-score' => 'app\command\UpdateWechatAccountScoreCommand', // 更新微信账号评分记录
];
diff --git a/Server/application/command/CalculateWechatAccountScoreCommand.php b/Server/application/command/CalculateWechatAccountScoreCommand.php
new file mode 100644
index 00000000..aeb73784
--- /dev/null
+++ b/Server/application/command/CalculateWechatAccountScoreCommand.php
@@ -0,0 +1,558 @@
+setName('wechat:calculate-score')
+ ->setDescription('统一计算微信账号健康分(包含初始化、更新评分记录、批量计算)')
+ ->addOption('only-init', null, \think\console\input\Option::VALUE_NONE, '仅执行初始化步骤')
+ ->addOption('only-update', null, \think\console\input\Option::VALUE_NONE, '仅执行更新评分记录步骤')
+ ->addOption('only-batch', null, \think\console\input\Option::VALUE_NONE, '仅执行批量更新健康分步骤')
+ ->addOption('account-id', 'a', \think\console\input\Option::VALUE_OPTIONAL, '指定账号ID,仅处理该账号')
+ ->addOption('batch-size', 'b', \think\console\input\Option::VALUE_OPTIONAL, '批处理大小', 50)
+ ->addOption('force-recalculate', 'f', \think\console\input\Option::VALUE_NONE, '强制重新计算基础分');
+ }
+
+ /**
+ * 执行命令
+ *
+ * @param Input $input 输入对象
+ * @param Output $output 输出对象
+ * @return int 命令执行状态码(0表示成功)
+ */
+ protected function execute(Input $input, Output $output)
+ {
+ // 解析命令行参数
+ $onlyInit = $input->getOption('only-init');
+ $onlyUpdate = $input->getOption('only-update');
+ $onlyBatch = $input->getOption('only-batch');
+ $accountId = $input->getOption('account-id');
+ $batchSize = (int)$input->getOption('batch-size');
+ $forceRecalculate = $input->getOption('force-recalculate');
+
+ // 参数验证
+ if ($batchSize <= 0) {
+ $batchSize = 50; // 默认批处理大小
+ }
+
+ // 显示执行参数
+ $output->writeln("==========================================");
+ $output->writeln("开始统一计算微信账号健康分...");
+ $output->writeln("==========================================");
+
+ if ($accountId) {
+ $output->writeln("指定账号ID: {$accountId}");
+ }
+
+ if ($onlyInit) {
+ $output->writeln("仅执行初始化步骤");
+ } elseif ($onlyUpdate) {
+ $output->writeln("仅执行更新评分记录步骤");
+ } elseif ($onlyBatch) {
+ $output->writeln("仅执行批量更新健康分步骤");
+ }
+
+ if ($forceRecalculate) {
+ $output->writeln("强制重新计算基础分");
+ }
+
+ $output->writeln("批处理大小: {$batchSize}");
+
+ // 记录命令开始执行的日志
+ Log::info('开始执行微信账号健康分计算命令', [
+ 'accountId' => $accountId,
+ 'onlyInit' => $onlyInit ? 'true' : 'false',
+ 'onlyUpdate' => $onlyUpdate ? 'true' : 'false',
+ 'onlyBatch' => $onlyBatch ? 'true' : 'false',
+ 'batchSize' => $batchSize,
+ 'forceRecalculate' => $forceRecalculate ? 'true' : 'false'
+ ]);
+
+ $startTime = time();
+
+ try {
+ // 实例化服务
+ $service = new WechatAccountHealthScoreService();
+ } catch (\Exception $e) {
+ $errorMsg = "实例化WechatAccountHealthScoreService失败: " . $e->getMessage();
+ $output->writeln("{$errorMsg}");
+ Log::error($errorMsg);
+ return 1; // 返回非零状态码表示失败
+ }
+
+ // 初始化统计数据
+ $initStats = ['success' => 0, 'failed' => 0, 'errors' => []];
+ $updateStats = ['total' => 0];
+ $batchStats = ['success' => 0, 'failed' => 0, 'errors' => []];
+
+ try {
+ // 步骤1: 初始化未计算基础分的账号
+ if (!$onlyUpdate && !$onlyBatch) {
+ $output->writeln("\n[步骤1] 初始化未计算基础分的账号...");
+ Log::info('[步骤1] 开始初始化未计算基础分的账号');
+ $initStats = $this->initUncalculatedAccounts($service, $output, $accountId, $batchSize);
+ $output->writeln("初始化完成:成功 {$initStats['success']} 条,失败 {$initStats['failed']} 条");
+ Log::info("初始化完成:成功 {$initStats['success']} 条,失败 {$initStats['failed']} 条");
+ }
+
+ // 步骤2: 更新评分记录(根据wechatId和alias不一致情况)
+ if (!$onlyInit && !$onlyBatch) {
+ $output->writeln("\n[步骤2] 更新评分记录(根据wechatId和alias不一致情况)...");
+ Log::info('[步骤2] 开始更新评分记录(根据wechatId和alias不一致情况)');
+ $updateStats = $this->updateScoreRecords($service, $output, $accountId, $batchSize);
+ $output->writeln("更新完成:处理了 {$updateStats['total']} 条记录");
+ Log::info("更新评分记录完成:处理了 {$updateStats['total']} 条记录");
+ }
+
+ // 步骤3: 批量更新健康分(只更新动态分,不重新计算基础分)
+ if (!$onlyInit && !$onlyUpdate) {
+ $output->writeln("\n[步骤3] 批量更新健康分(只更新动态分)...");
+ Log::info('[步骤3] 开始批量更新健康分(只更新动态分)');
+ $batchStats = $this->batchUpdateHealthScore($service, $output, $accountId, $batchSize, $forceRecalculate);
+ $output->writeln("批量更新完成:成功 {$batchStats['success']} 条,失败 {$batchStats['failed']} 条");
+ Log::info("批量更新健康分完成:成功 {$batchStats['success']} 条,失败 {$batchStats['failed']} 条");
+ }
+
+ // 统计信息
+ $endTime = time();
+ $duration = $endTime - $startTime;
+
+ $output->writeln("\n==========================================");
+ $output->writeln("任务完成!");
+ $output->writeln("==========================================");
+ $output->writeln("总耗时: {$duration} 秒");
+ $output->writeln("初始化: 成功 {$initStats['success']} 条,失败 {$initStats['failed']} 条");
+ $output->writeln("更新评分记录: {$updateStats['total']} 条");
+ $output->writeln("批量更新: 成功 {$batchStats['success']} 条,失败 {$batchStats['failed']} 条");
+
+ // 记录命令执行完成的日志
+ Log::info("微信账号健康分计算命令执行完成,总耗时: {$duration} 秒," .
+ "初始化: 成功 {$initStats['success']} 条,失败 {$initStats['failed']} 条," .
+ "更新评分记录: {$updateStats['total']} 条," .
+ "批量更新: 成功 {$batchStats['success']} 条,失败 {$batchStats['failed']} 条");
+
+ if (!empty($initStats['errors'])) {
+ $output->writeln("\n初始化错误详情:");
+ Log::warning("初始化阶段出现 " . count($initStats['errors']) . " 个错误");
+
+ foreach (array_slice($initStats['errors'], 0, 10) as $error) {
+ $output->writeln(" 账号ID {$error['accountId']}: {$error['error']}");
+ Log::error("初始化错误 - 账号ID {$error['accountId']}: {$error['error']}");
+ }
+
+ if (count($initStats['errors']) > 10) {
+ $output->writeln(" ... 还有 " . (count($initStats['errors']) - 10) . " 个错误");
+ Log::warning("初始化错误过多,只记录前10个,还有 " . (count($initStats['errors']) - 10) . " 个错误未显示");
+ }
+ }
+
+ if (!empty($batchStats['errors'])) {
+ $output->writeln("\n批量更新错误详情:");
+ Log::warning("批量更新阶段出现 " . count($batchStats['errors']) . " 个错误");
+
+ foreach (array_slice($batchStats['errors'], 0, 10) as $error) {
+ $output->writeln(" 账号ID {$error['accountId']}: {$error['error']}");
+ Log::error("批量更新错误 - 账号ID {$error['accountId']}: {$error['error']}");
+ }
+
+ if (count($batchStats['errors']) > 10) {
+ $output->writeln(" ... 还有 " . (count($batchStats['errors']) - 10) . " 个错误");
+ Log::warning("批量更新错误过多,只记录前10个,还有 " . (count($batchStats['errors']) - 10) . " 个错误未显示");
+ }
+ }
+
+ } catch (\PDOException $e) {
+ // 数据库异常
+ $errorMsg = "数据库操作失败: " . $e->getMessage();
+ $output->writeln("\n数据库错误: " . $errorMsg . "");
+ $output->writeln($e->getTraceAsString());
+
+ // 记录数据库错误
+ Log::error("数据库错误: " . $errorMsg);
+ Log::error("错误堆栈: " . $e->getTraceAsString());
+
+ return 2; // 数据库错误状态码
+ } catch (\Exception $e) {
+ // 一般异常
+ $errorMsg = "命令执行失败: " . $e->getMessage();
+ $output->writeln("\n错误: " . $errorMsg . "");
+ $output->writeln($e->getTraceAsString());
+
+ // 记录严重错误
+ Log::error($errorMsg);
+ Log::error("错误堆栈: " . $e->getTraceAsString());
+
+ return 1; // 一般错误状态码
+ } catch (\Throwable $e) {
+ // 其他所有错误
+ $errorMsg = "严重错误: " . $e->getMessage();
+ $output->writeln("\n严重错误: " . $errorMsg . "");
+ $output->writeln($e->getTraceAsString());
+
+ // 记录严重错误
+ Log::critical($errorMsg);
+ Log::critical("错误堆栈: " . $e->getTraceAsString());
+
+ return 3; // 严重错误状态码
+ }
+
+ return 0; // 成功执行
+ }
+
+ /**
+ * 初始化未计算基础分的账号
+ *
+ * @param WechatAccountHealthScoreService $service 健康分服务实例
+ * @param Output $output 输出对象
+ * @return array 处理结果统计
+ * @throws \Exception 如果查询或处理过程中出现错误
+ */
+ private function initUncalculatedAccounts($service, $output, $accountId = null, $batchSize = 50)
+ {
+ $stats = [
+ 'total' => 0,
+ 'success' => 0,
+ 'failed' => 0,
+ 'errors' => []
+ ];
+
+ try {
+ // 获取所有未计算基础分的账号
+ // 优化查询:使用索引字段,只查询必要的字段
+ $query = Db::table(self::TABLE_WECHAT_ACCOUNT)
+ ->alias('a')
+ ->leftJoin([self::TABLE_WECHAT_ACCOUNT_SCORE => 's'], 's.accountId = a.id')
+ ->where('a.isDeleted', 0)
+ ->where(function($query) {
+ $query->whereNull('s.id')
+ ->whereOr('s.baseScoreCalculated', 0);
+ });
+
+ // 如果指定了账号ID,则只处理该账号
+ if ($accountId) {
+ $query->where('a.id', $accountId);
+ }
+
+ $accounts = $query->field('a.id, a.wechatId') // 只查询必要的字段
+ ->select();
+ } catch (\Exception $e) {
+ Log::error("查询未计算基础分的账号失败: " . $e->getMessage());
+ throw new \Exception("查询未计算基础分的账号失败: " . $e->getMessage(), 0, $e);
+ }
+
+ $stats['total'] = count($accounts);
+
+ if ($stats['total'] == 0) {
+ $output->writeln("没有需要初始化的账号");
+ Log::info("没有需要初始化的账号");
+ return $stats;
+ }
+
+ $output->writeln("找到 {$stats['total']} 个需要初始化的账号");
+ Log::info("找到 {$stats['total']} 个需要初始化的账号");
+
+ // 优化批处理:使用传入的批处理大小
+ $batches = array_chunk($accounts, $batchSize);
+ $batchCount = count($batches);
+
+ Log::info("将分 {$batchCount} 批处理,每批 {$batchSize} 个账号");
+
+ foreach ($batches as $batchIndex => $batch) {
+ $batchStartTime = microtime(true);
+ $batchSuccessCount = 0;
+ $batchFailedCount = 0;
+
+ foreach ($batch as $account) {
+ try {
+ $service->calculateAndUpdate($account['id']);
+ $stats['success']++;
+ $batchSuccessCount++;
+
+ if ($stats['success'] % 20 == 0) { // 更频繁地显示进度
+ $output->write(".");
+ Log::debug("已成功初始化 {$stats['success']} 个账号");
+ }
+ } catch (\Exception $e) {
+ $stats['failed']++;
+ $batchFailedCount++;
+ $errorMsg = "初始化账号 {$account['id']} 失败: " . $e->getMessage();
+ Log::error($errorMsg);
+ $stats['errors'][] = [
+ 'accountId' => $account['id'],
+ 'error' => $e->getMessage()
+ ];
+ }
+ }
+
+ $batchEndTime = microtime(true);
+ $batchDuration = round($batchEndTime - $batchStartTime, 2);
+
+ // 每批次完成后输出进度信息
+ $output->writeln(" 批次 " . ($batchIndex + 1) . "/{$batchCount} 完成,耗时 {$batchDuration} 秒,成功 {$batchSuccessCount},失败 {$batchFailedCount}");
+ Log::info("初始化批次 " . ($batchIndex + 1) . "/{$batchCount} 完成,耗时 {$batchDuration} 秒,成功 {$batchSuccessCount},失败 {$batchFailedCount}");
+ }
+
+ return $stats;
+ }
+
+ /**
+ * 更新评分记录(根据wechatId和alias不一致情况)
+ *
+ * @param WechatAccountHealthScoreService $service 健康分服务实例
+ * @param Output $output 输出对象
+ * @return array 处理结果统计
+ * @throws \Exception 如果查询或处理过程中出现错误
+ */
+ private function updateScoreRecords($service, $output, $accountId = null, $batchSize = 50)
+ {
+ $stats = ['total' => 0];
+
+ try {
+ // 优化查询:合并两次查询为一次,减少数据库访问次数
+ $query = Db::table(self::TABLE_WECHAT_ACCOUNT)
+ ->where('isDeleted', 0)
+ ->where('wechatId', '<>', '')
+ ->where('alias', '<>', '');
+
+ // 如果指定了账号ID,则只处理该账号
+ if ($accountId) {
+ $query->where('id', $accountId);
+ }
+
+ $accounts = $query->field('id, wechatId, alias, IF(wechatId = alias, 0, 1) as isModifiedAlias')
+ ->select();
+
+ // 分类处理查询结果
+ $inconsistentAccounts = [];
+ $consistentAccounts = [];
+
+ foreach ($accounts as $account) {
+ if ($account['isModifiedAlias'] == 1) {
+ $inconsistentAccounts[] = $account;
+ } else {
+ $consistentAccounts[] = $account;
+ }
+ }
+ } catch (\Exception $e) {
+ Log::error("查询需要更新评分记录的账号失败: " . $e->getMessage());
+ throw new \Exception("查询需要更新评分记录的账号失败: " . $e->getMessage(), 0, $e);
+ }
+
+ $allAccounts = array_merge($inconsistentAccounts, $consistentAccounts);
+ $stats['total'] = count($allAccounts);
+
+ if ($stats['total'] == 0) {
+ $output->writeln("没有需要更新的账号");
+ Log::info("没有需要更新的评分记录");
+ return $stats;
+ }
+
+ $output->writeln("找到 {$stats['total']} 个需要更新的账号(不一致: " . count($inconsistentAccounts) . ",一致: " . count($consistentAccounts) . ")");
+ Log::info("找到 {$stats['total']} 个需要更新的账号(不一致: " . count($inconsistentAccounts) . ",一致: " . count($consistentAccounts) . ")");
+
+ $updatedCount = 0;
+
+ // 优化批处理:使用传入的批处理大小
+ $batches = array_chunk($allAccounts, $batchSize);
+ $batchCount = count($batches);
+
+ Log::info("将分 {$batchCount} 批更新评分记录,每批 {$batchSize} 个账号");
+
+ foreach ($batches as $batchIndex => $batch) {
+ $batchStartTime = microtime(true);
+ $batchUpdatedCount = 0;
+
+ foreach ($batch as $account) {
+ $isModifiedAlias = isset($account['isModifiedAlias']) ?
+ ($account['isModifiedAlias'] == 1) :
+ in_array($account['id'], array_column($inconsistentAccounts, 'id'));
+
+ $this->updateScoreRecord($account['id'], $isModifiedAlias, $service);
+ $updatedCount++;
+ $batchUpdatedCount++;
+
+ if ($batchUpdatedCount % 20 == 0) {
+ $output->write(".");
+ }
+ }
+
+ $batchEndTime = microtime(true);
+ $batchDuration = round($batchEndTime - $batchStartTime, 2);
+
+ // 每批次完成后输出进度信息
+ $output->writeln(" 批次 " . ($batchIndex + 1) . "/{$batchCount} 完成,耗时 {$batchDuration} 秒,更新 {$batchUpdatedCount} 条记录");
+ Log::info("更新评分记录批次 " . ($batchIndex + 1) . "/{$batchCount} 完成,耗时 {$batchDuration} 秒,更新 {$batchUpdatedCount} 条记录");
+ }
+
+ if ($updatedCount > 0 && $updatedCount % 100 == 0) {
+ $output->writeln("");
+ }
+
+ return $stats;
+ }
+
+ /**
+ * 批量更新健康分(只更新动态分)
+ *
+ * @param WechatAccountHealthScoreService $service 健康分服务实例
+ * @param Output $output 输出对象
+ * @return array 处理结果统计
+ * @throws \Exception 如果查询或处理过程中出现错误
+ */
+ private function batchUpdateHealthScore($service, $output, $accountId = null, $batchSize = 50, $forceRecalculate = false)
+ {
+ try {
+ // 获取所有已计算基础分的账号
+ // 优化查询:只查询必要的字段,使用索引字段
+ $query = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE)
+ ->where('baseScoreCalculated', 1);
+
+ // 如果指定了账号ID,则只处理该账号
+ if ($accountId) {
+ $query->where('accountId', $accountId);
+ }
+
+ $accountIds = $query->column('accountId');
+ } catch (\Exception $e) {
+ Log::error("查询需要批量更新健康分的账号失败: " . $e->getMessage());
+ throw new \Exception("查询需要批量更新健康分的账号失败: " . $e->getMessage(), 0, $e);
+ }
+
+ $total = count($accountIds);
+
+ if ($total == 0) {
+ $output->writeln("没有需要更新的账号");
+ Log::info("没有需要批量更新健康分的账号");
+ return ['success' => 0, 'failed' => 0, 'errors' => []];
+ }
+
+ $output->writeln("找到 {$total} 个需要更新动态分的账号");
+ Log::info("找到 {$total} 个需要更新动态分的账号");
+
+ // 使用传入的批处理大小和强制重新计算标志
+ Log::info("使用批量大小 {$batchSize} 进行批量更新健康分,强制重新计算基础分: " . ($forceRecalculate ? 'true' : 'false'));
+ $stats = $service->batchCalculateAndUpdate($accountIds, $batchSize, $forceRecalculate);
+
+ return $stats;
+ }
+
+ /**
+ * 更新评分记录
+ *
+ * @param int $accountId 账号ID
+ * @param bool $isModifiedAlias 是否已修改微信号
+ * @param WechatAccountHealthScoreService $service 评分服务
+ */
+ /**
+ * 更新评分记录
+ *
+ * @param int $accountId 账号ID
+ * @param bool $isModifiedAlias 是否已修改微信号
+ * @param WechatAccountHealthScoreService $service 评分服务
+ * @return bool 是否成功更新
+ */
+ private function updateScoreRecord($accountId, $isModifiedAlias, $service)
+ {
+ Log::debug("开始更新账号 {$accountId} 的评分记录,isModifiedAlias: " . ($isModifiedAlias ? 'true' : 'false'));
+
+ try {
+ // 获取账号数据 - 只查询必要的字段
+ $accountData = Db::table(self::TABLE_WECHAT_ACCOUNT)
+ ->where('id', $accountId)
+ ->field('id, wechatId, alias') // 只查询必要的字段
+ ->find();
+
+ if (empty($accountData)) {
+ Log::warning("账号 {$accountId} 不存在,跳过更新评分记录");
+ return false;
+ }
+
+ // 确保评分记录存在 - 只查询必要的字段
+ $scoreRecord = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE)
+ ->where('accountId', $accountId)
+ ->field('accountId, baseScore, baseScoreCalculated, baseInfoScore, dynamicScore') // 只查询必要的字段
+ ->find();
+
+ if (empty($scoreRecord)) {
+ // 如果记录不存在,创建并计算基础分
+ Log::info("账号 {$accountId} 的评分记录不存在,创建并计算基础分");
+ $service->calculateAndUpdate($accountId);
+ $scoreRecord = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE)
+ ->where('accountId', $accountId)
+ ->find();
+ }
+
+ if (empty($scoreRecord)) {
+ Log::warning("账号 {$accountId} 的评分记录创建失败,跳过更新");
+ return;
+ }
+
+ // 更新isModifiedAlias字段
+ $updateData = [
+ 'isModifiedAlias' => $isModifiedAlias ? 1 : 0,
+ 'updateTime' => time()
+ ];
+
+ // 如果基础分已计算,需要更新基础信息分和基础分
+ if ($scoreRecord['baseScoreCalculated']) {
+ $oldBaseInfoScore = $scoreRecord['baseInfoScore'] ?? 0;
+ $newBaseInfoScore = $isModifiedAlias ? 10 : 0; // 已修改微信号得10分
+
+ if ($oldBaseInfoScore != $newBaseInfoScore) {
+ $oldBaseScore = $scoreRecord['baseScore'] ?? 60;
+ $newBaseScore = $oldBaseScore - $oldBaseInfoScore + $newBaseInfoScore;
+
+ $updateData['baseInfoScore'] = $newBaseInfoScore;
+ $updateData['baseScore'] = $newBaseScore;
+
+ // 重新计算健康分
+ $dynamicScore = $scoreRecord['dynamicScore'] ?? 0;
+ $healthScore = $newBaseScore + $dynamicScore;
+ $healthScore = max(0, min(100, $healthScore));
+ $updateData['healthScore'] = $healthScore;
+ $updateData['maxAddFriendPerDay'] = (int)floor($healthScore * 0.2);
+
+ Log::info("账号 {$accountId} 的基础信息分从 {$oldBaseInfoScore} 更新为 {$newBaseInfoScore}," .
+ "基础分从 {$oldBaseScore} 更新为 {$newBaseScore},健康分更新为 {$healthScore}");
+ }
+ } else {
+ // 基础分未计算,只更新标记和基础信息分
+ $updateData['baseInfoScore'] = $isModifiedAlias ? 10 : 0;
+ }
+
+ $result = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE)
+ ->where('accountId', $accountId)
+ ->update($updateData);
+
+ Log::debug("账号 {$accountId} 的评分记录更新" . ($result !== false ? "成功" : "失败"));
+
+ return $result !== false;
+ } catch (\Exception $e) {
+ Log::error("更新账号 {$accountId} 的评分记录失败: " . $e->getMessage());
+ return false;
+ }
+ }
+}
+
diff --git a/Server/application/command/UpdateWechatAccountScoreCommand.php b/Server/application/command/UpdateWechatAccountScoreCommand.php
new file mode 100644
index 00000000..13ded8d4
--- /dev/null
+++ b/Server/application/command/UpdateWechatAccountScoreCommand.php
@@ -0,0 +1,168 @@
+setName('wechat:update-score')
+ ->setDescription('更新微信账号评分记录,根据wechatId和alias不一致情况更新isModifiedAlias字段(仅用于评分)');
+ }
+
+ protected function execute(Input $input, Output $output)
+ {
+ $output->writeln("开始更新微信账号评分记录...");
+
+ try {
+ // 1. 查找所有需要更新的账号
+ $output->writeln("步骤1: 查找需要更新的账号...");
+
+ // 查找wechatId和alias不一致的账号
+ $inconsistentAccounts = Db::table('s2_wechat_account')
+ ->where('isDeleted', 0)
+ ->where('wechatId', '<>', '')
+ ->where('alias', '<>', '')
+ ->whereRaw('wechatId != alias')
+ ->field('id, wechatId, alias')
+ ->select();
+
+ // 查找wechatId和alias一致的账号
+ $consistentAccounts = Db::table('s2_wechat_account')
+ ->where('isDeleted', 0)
+ ->where('wechatId', '<>', '')
+ ->where('alias', '<>', '')
+ ->whereRaw('wechatId = alias')
+ ->field('id, wechatId, alias')
+ ->select();
+
+ $output->writeln("发现 " . count($inconsistentAccounts) . " 条不一致记录(已修改微信号)");
+ $output->writeln("发现 " . count($consistentAccounts) . " 条一致记录(未修改微信号)");
+
+ // 2. 更新评分记录表中的isModifiedAlias字段
+ $output->writeln("步骤2: 更新评分记录表...");
+ $updatedCount = 0;
+ $healthScoreService = new WechatAccountHealthScoreService();
+
+ // 更新不一致的记录
+ foreach ($inconsistentAccounts as $account) {
+ $this->updateScoreRecord($account['id'], true, $healthScoreService);
+ $updatedCount++;
+ }
+
+ // 更新一致的记录
+ foreach ($consistentAccounts as $account) {
+ $this->updateScoreRecord($account['id'], false, $healthScoreService);
+ $updatedCount++;
+ }
+
+ $output->writeln("已更新 " . $updatedCount . " 条评分记录");
+
+ // 3. 重新计算健康分(只更新基础信息分,不重新计算基础分)
+ $output->writeln("步骤3: 重新计算健康分...");
+ $allAccountIds = array_merge(
+ array_column($inconsistentAccounts, 'id'),
+ array_column($consistentAccounts, 'id')
+ );
+
+ if (!empty($allAccountIds)) {
+ $stats = $healthScoreService->batchCalculateAndUpdate($allAccountIds, 100, false);
+ $output->writeln("健康分计算完成:成功 " . $stats['success'] . " 条,失败 " . $stats['failed'] . " 条");
+
+ if (!empty($stats['errors'])) {
+ $output->writeln("错误详情:");
+ foreach ($stats['errors'] as $error) {
+ $output->writeln(" 账号ID {$error['accountId']}: {$error['error']}");
+ }
+ }
+ }
+
+ $output->writeln("任务完成!");
+
+ } catch (\Exception $e) {
+ $output->writeln("错误: " . $e->getMessage());
+ $output->writeln($e->getTraceAsString());
+ }
+ }
+
+ /**
+ * 更新评分记录
+ *
+ * @param int $accountId 账号ID
+ * @param bool $isModifiedAlias 是否已修改微信号
+ * @param WechatAccountHealthScoreService $service 评分服务
+ */
+ private function updateScoreRecord($accountId, $isModifiedAlias, $service)
+ {
+ // 获取或创建评分记录
+ $accountData = Db::table('s2_wechat_account')
+ ->where('id', $accountId)
+ ->find();
+
+ if (empty($accountData)) {
+ return;
+ }
+
+ // 确保评分记录存在
+ $scoreRecord = Db::table('s2_wechat_account_score')
+ ->where('accountId', $accountId)
+ ->find();
+
+ if (empty($scoreRecord)) {
+ // 如果记录不存在,创建并计算基础分
+ $service->calculateAndUpdate($accountId);
+ $scoreRecord = Db::table('s2_wechat_account_score')
+ ->where('accountId', $accountId)
+ ->find();
+ }
+
+ if (empty($scoreRecord)) {
+ return;
+ }
+
+ // 更新isModifiedAlias字段
+ $updateData = [
+ 'isModifiedAlias' => $isModifiedAlias ? 1 : 0,
+ 'updateTime' => time()
+ ];
+
+ // 如果基础分已计算,需要更新基础信息分和基础分
+ if ($scoreRecord['baseScoreCalculated']) {
+ $oldBaseInfoScore = $scoreRecord['baseInfoScore'] ?? 0;
+ $newBaseInfoScore = $isModifiedAlias ? 10 : 0; // 已修改微信号得10分
+
+ if ($oldBaseInfoScore != $newBaseInfoScore) {
+ $oldBaseScore = $scoreRecord['baseScore'] ?? 60;
+ $newBaseScore = $oldBaseScore - $oldBaseInfoScore + $newBaseInfoScore;
+
+ $updateData['baseInfoScore'] = $newBaseInfoScore;
+ $updateData['baseScore'] = $newBaseScore;
+
+ // 重新计算健康分
+ $dynamicScore = $scoreRecord['dynamicScore'] ?? 0;
+ $healthScore = $newBaseScore + $dynamicScore;
+ $healthScore = max(0, min(100, $healthScore));
+ $updateData['healthScore'] = $healthScore;
+ $updateData['maxAddFriendPerDay'] = (int)floor($healthScore * 0.2);
+ }
+ } else {
+ // 基础分未计算,只更新标记和基础信息分
+ $updateData['baseInfoScore'] = $isModifiedAlias ? 10 : 0;
+ }
+
+ Db::table('s2_wechat_account_score')
+ ->where('accountId', $accountId)
+ ->update($updateData);
+ }
+}
+
diff --git a/Server/application/common.php b/Server/application/common.php
index 37a12739..8785f12c 100644
--- a/Server/application/common.php
+++ b/Server/application/common.php
@@ -75,7 +75,18 @@ if (!function_exists('requestCurl')) {
if (!function_exists('dataBuild')) {
function dataBuild($array)
{
- return is_array($array) ? http_build_query($array) : $array;
+ if (!is_array($array)) {
+ return $array;
+ }
+
+ // 处理嵌套数组
+ foreach ($array as $key => $value) {
+ if (is_array($value)) {
+ $array[$key] = json_encode($value);
+ }
+ }
+
+ return http_build_query($array);
}
}
@@ -550,14 +561,15 @@ if (!function_exists('exit_data')) {
exit();
}
}
-
-/**
- * 调试打印变量但不终止程序
- * @return void
- */
-function dump()
-{
- call_user_func_array(['app\\common\\helper\\Debug', 'dump'], func_get_args());
+if (!function_exists('dump')) {
+ /**
+ * 调试打印变量但不终止程序
+ * @return void
+ */
+ function dump()
+ {
+ call_user_func_array(['app\\common\\helper\\Debug', 'dump'], func_get_args());
+ }
}
if (!function_exists('artificialAllotWechatFriend')) {
diff --git a/Server/application/common/model/WechatAccountScore.php b/Server/application/common/model/WechatAccountScore.php
new file mode 100644
index 00000000..43170f78
--- /dev/null
+++ b/Server/application/common/model/WechatAccountScore.php
@@ -0,0 +1,44 @@
+ 'integer',
+ 'baseScore' => 'integer',
+ 'baseScoreCalculated' => 'integer',
+ 'baseInfoScore' => 'integer',
+ 'friendCountScore' => 'integer',
+ 'friendCount' => 'integer',
+ 'dynamicScore' => 'integer',
+ 'frequentCount' => 'integer',
+ 'frequentPenalty' => 'integer',
+ 'consecutiveNoFrequentDays' => 'integer',
+ 'noFrequentBonus' => 'integer',
+ 'banPenalty' => 'integer',
+ 'healthScore' => 'integer',
+ 'maxAddFriendPerDay' => 'integer',
+ 'isModifiedAlias' => 'integer',
+ 'isBanned' => 'integer',
+ 'lastFrequentTime' => 'integer',
+ 'lastNoFrequentTime' => 'integer',
+ 'baseScoreCalcTime' => 'integer',
+ 'createTime' => 'integer',
+ 'updateTime' => 'integer',
+ ];
+}
+
diff --git a/Server/application/common/service/WechatAccountHealthScoreService.php b/Server/application/common/service/WechatAccountHealthScoreService.php
new file mode 100644
index 00000000..d6246a26
--- /dev/null
+++ b/Server/application/common/service/WechatAccountHealthScoreService.php
@@ -0,0 +1,1064 @@
+where('id', $accountId)
+ ->find();
+
+ Log::debug("查询账号数据: " . ($accountData ? "成功" : "失败"));
+ }
+
+ if (empty($accountData)) {
+ $errorMsg = "账号不存在:{$accountId}";
+ Log::error($errorMsg);
+ throw new Exception($errorMsg);
+ }
+
+ $wechatId = $accountData['wechatId'] ?? '';
+ if (empty($wechatId)) {
+ $errorMsg = "账号wechatId为空:{$accountId}";
+ Log::error($errorMsg);
+ throw new Exception($errorMsg);
+ }
+
+ Log::debug("账号数据: accountId={$accountId}, wechatId={$wechatId}");
+
+ // 获取或创建评分记录
+ $scoreRecord = $this->getOrCreateScoreRecord($accountId, $wechatId);
+ Log::debug("获取评分记录: " . ($scoreRecord ? "成功" : "失败"));
+
+ // 计算基础分(只计算一次,除非强制重新计算)
+ if (!$scoreRecord['baseScoreCalculated'] || $forceRecalculateBase) {
+ Log::info("计算基础分,accountId: {$accountId}, baseScoreCalculated: " .
+ ($scoreRecord['baseScoreCalculated'] ? 'true' : 'false') .
+ ", forceRecalculateBase: " . ($forceRecalculateBase ? 'true' : 'false'));
+
+ $baseScoreData = $this->calculateBaseScore($accountData, $scoreRecord);
+ $this->updateBaseScore($accountId, $baseScoreData);
+
+ Log::debug("基础分计算结果: " . json_encode($baseScoreData));
+
+ // 重新获取记录以获取最新数据
+ $scoreRecord = $this->getScoreRecord($accountId);
+ }
+
+ // 计算动态分(每次都要重新计算)
+ Log::info("计算动态分,accountId: {$accountId}");
+ $dynamicScoreData = $this->calculateDynamicScore($accountData, $scoreRecord);
+
+ // 计算总分
+ $baseScore = $scoreRecord['baseScore'];
+ $dynamicScore = $dynamicScoreData['total'];
+ $healthScore = $baseScore + $dynamicScore;
+
+ // 确保健康分在合理范围内(0-100)
+ $healthScore = max(0, min(100, $healthScore));
+
+ // 计算每日最大加人次数
+ $maxAddFriendPerDay = $this->getMaxAddFriendPerDay($healthScore);
+
+ Log::info("健康分计算结果,accountId: {$accountId}, baseScore: {$baseScore}, dynamicScore: {$dynamicScore}, " .
+ "healthScore: {$healthScore}, maxAddFriendPerDay: {$maxAddFriendPerDay}");
+
+ // 更新评分记录
+ $updateData = [
+ 'dynamicScore' => $dynamicScore,
+ 'frequentPenalty' => $dynamicScoreData['frequentPenalty'],
+ 'noFrequentBonus' => $dynamicScoreData['noFrequentBonus'],
+ 'banPenalty' => $dynamicScoreData['banPenalty'],
+ 'lastFrequentTime' => $dynamicScoreData['lastFrequentTime'],
+ 'frequentCount' => $dynamicScoreData['frequentCount'],
+ 'lastNoFrequentTime' => $dynamicScoreData['lastNoFrequentTime'],
+ 'consecutiveNoFrequentDays' => $dynamicScoreData['consecutiveNoFrequentDays'],
+ 'isBanned' => $dynamicScoreData['isBanned'],
+ 'healthScore' => $healthScore,
+ 'maxAddFriendPerDay' => $maxAddFriendPerDay,
+ 'updateTime' => time()
+ ];
+
+ $updateResult = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE)
+ ->where('accountId', $accountId)
+ ->update($updateData);
+
+ // 更新成功后,清除缓存
+ if ($updateResult !== false) {
+ $this->clearScoreCache($accountId);
+ }
+
+ $result = [
+ 'accountId' => $accountId,
+ 'wechatId' => $wechatId,
+ 'healthScore' => $healthScore,
+ 'baseScore' => $baseScore,
+ 'baseInfoScore' => $scoreRecord['baseInfoScore'],
+ 'friendCountScore' => $scoreRecord['friendCountScore'],
+ 'dynamicScore' => $dynamicScore,
+ 'frequentPenalty' => $dynamicScoreData['frequentPenalty'],
+ 'noFrequentBonus' => $dynamicScoreData['noFrequentBonus'],
+ 'banPenalty' => $dynamicScoreData['banPenalty'],
+ 'maxAddFriendPerDay' => $maxAddFriendPerDay
+ ];
+
+ Log::debug("健康分计算完成,返回结果: " . json_encode($result));
+ return $result;
+
+ } catch (\PDOException $e) {
+ // 数据库异常
+ $errorMsg = "数据库操作失败,accountId: {$accountId}, 错误: " . $e->getMessage();
+ Log::error($errorMsg);
+ throw new Exception($errorMsg, $e->getCode(), $e);
+ } catch (\Throwable $e) {
+ // 其他所有异常
+ $errorMsg = "计算健康分失败,accountId: {$accountId}, 错误: " . $e->getMessage();
+ Log::error($errorMsg);
+ throw new Exception($errorMsg, $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * 获取或创建评分记录
+ *
+ * @param int $accountId 账号ID
+ * @param string $wechatId 微信ID
+ * @return array 评分记录
+ */
+ private function getOrCreateScoreRecord($accountId, $wechatId)
+ {
+ // 尝试获取现有记录
+ $record = $this->getScoreRecord($accountId);
+
+ // 如果记录不存在,创建新记录
+ if (empty($record)) {
+ Log::info("为账号 {$accountId} 创建新的评分记录");
+
+ // 创建新记录
+ $data = [
+ 'accountId' => $accountId,
+ 'wechatId' => $wechatId,
+ 'baseScore' => 0,
+ 'baseScoreCalculated' => 0,
+ 'baseInfoScore' => 0,
+ 'friendCountScore' => 0,
+ 'dynamicScore' => 0,
+ 'frequentCount' => 0,
+ 'consecutiveNoFrequentDays' => 0,
+ 'healthScore' => 0,
+ 'maxAddFriendPerDay' => 0,
+ 'createTime' => time(),
+ 'updateTime' => time()
+ ];
+
+ Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE)->insert($data);
+
+ return $data;
+ }
+
+ return $record;
+ }
+
+ /**
+ * 获取评分记录
+ *
+ * @param int $accountId 账号ID
+ * @param bool $useCache 是否使用缓存(默认true)
+ * @return array 评分记录,如果不存在则返回空数组
+ */
+ private function getScoreRecord($accountId, $useCache = true)
+ {
+ // 生成缓存键
+ $cacheKey = self::CACHE_PREFIX . 'score:' . $accountId;
+
+ // 如果使用缓存且缓存存在,则直接返回缓存数据
+ if ($useCache && Cache::has($cacheKey)) {
+ $cachedData = Cache::get($cacheKey);
+ Log::debug("从缓存获取评分记录,accountId: {$accountId}");
+ return $cachedData ?: [];
+ }
+
+ // 从数据库获取记录
+ $record = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE)
+ ->where('accountId', $accountId)
+ ->find();
+
+ // 如果记录存在且使用缓存,则缓存记录
+ if ($record && $useCache) {
+ Cache::set($cacheKey, $record, self::CACHE_TTL);
+ Log::debug("缓存评分记录,accountId: {$accountId}");
+ }
+
+ return $record ?: [];
+ }
+
+ /**
+ * 计算基础分(只计算一次)
+ * 基础分 = 默认60分 + 基础信息分(10分) + 好友数量分(3-12分)
+ *
+ * @param array $accountData 账号数据
+ * @param array $scoreRecord 现有评分记录
+ * @return array 基础分数据
+ */
+ private function calculateBaseScore($accountData, $scoreRecord = [])
+ {
+ $baseScore = self::DEFAULT_BASE_SCORE;
+
+ // 基础信息分(已修改微信号得10分)
+ $baseInfoScore = $this->getBaseInfoScore($accountData);
+ $baseScore += $baseInfoScore;
+
+ // 好友数量分(特殊处理:使用快照值,避免同步问题)
+ $friendCountScore = 0;
+ $friendCount = 0;
+ $friendCountSource = 'manual';
+
+ // 如果已有评分记录且好友数量分已计算,使用历史值
+ if (!empty($scoreRecord['friendCountScore']) && $scoreRecord['friendCountScore'] > 0) {
+ $friendCountScore = $scoreRecord['friendCountScore'];
+ $friendCount = $scoreRecord['friendCount'] ?? 0;
+ $friendCountSource = $scoreRecord['friendCountSource'] ?? 'manual';
+ } else {
+ // 首次计算:使用当前好友数量,但标记为手动计算
+ $totalFriend = $accountData['totalFriend'] ?? 0;
+ $friendCountScore = $this->getFriendCountScore($totalFriend);
+ $friendCount = $totalFriend;
+ $friendCountSource = 'manual';
+ }
+
+ $baseScore += $friendCountScore;
+
+ // 检查是否已修改微信号
+ $isModifiedAlias = $this->checkIsModifiedAlias($accountData);
+
+ return [
+ 'baseScore' => $baseScore,
+ 'baseInfoScore' => $baseInfoScore,
+ 'friendCountScore' => $friendCountScore,
+ 'friendCount' => $friendCount,
+ 'friendCountSource' => $friendCountSource,
+ 'isModifiedAlias' => $isModifiedAlias ? 1 : 0,
+ 'baseScoreCalculated' => 1,
+ 'baseScoreCalcTime' => time()
+ ];
+ }
+
+ /**
+ * 更新基础分
+ *
+ * @param int $accountId 账号ID
+ * @param array $baseScoreData 基础分数据
+ * @return bool 更新是否成功
+ */
+ private function updateBaseScore($accountId, $baseScoreData)
+ {
+ try {
+ $result = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE)
+ ->where('accountId', $accountId)
+ ->update($baseScoreData);
+
+ Log::debug("更新基础分,accountId: {$accountId}, 结果: " . ($result ? "成功" : "失败"));
+
+ // 更新成功后,清除缓存
+ if ($result !== false) {
+ $this->clearScoreCache($accountId);
+ }
+
+ return $result !== false;
+ } catch (Exception $e) {
+ Log::error("更新基础分失败,accountId: {$accountId}, 错误: " . $e->getMessage());
+ return false;
+ }
+ }
+
+ /**
+ * 清除评分记录缓存
+ *
+ * @param int $accountId 账号ID
+ * @return bool 是否成功清除缓存
+ */
+ private function clearScoreCache($accountId)
+ {
+ $cacheKey = self::CACHE_PREFIX . 'score:' . $accountId;
+ $result = Cache::rm($cacheKey);
+ Log::debug("清除评分记录缓存,accountId: {$accountId}, 结果: " . ($result ? "成功" : "失败"));
+ return $result;
+ }
+
+ /**
+ * 获取基础信息分
+ * 已修改微信号:10分
+ *
+ * @param array $accountData 账号数据
+ * @return int 基础信息分
+ */
+ private function getBaseInfoScore($accountData)
+ {
+ if ($this->checkIsModifiedAlias($accountData)) {
+ return self::BASE_INFO_SCORE;
+ }
+ return 0;
+ }
+
+ /**
+ * 检查是否已修改微信号
+ * 判断标准:wechatId和alias不一致且都不为空,则认为已修改微信号
+ * 注意:这里只用于评分,不修复数据
+ *
+ * @param array $accountData 账号数据
+ * @return bool
+ */
+ private function checkIsModifiedAlias($accountData)
+ {
+ $wechatId = trim($accountData['wechatId'] ?? '');
+ $alias = trim($accountData['alias'] ?? '');
+
+ // 如果wechatId和alias不一致且都不为空,则认为已修改微信号(用于评分)
+ if (!empty($wechatId) && !empty($alias) && $wechatId !== $alias) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 获取好友数量分
+ * 根据好友数量区间得分(最高12分)
+ *
+ * @param int $totalFriend 总好友数
+ * @return int 好友数量分
+ */
+ private function getFriendCountScore($totalFriend)
+ {
+ if ($totalFriend <= 50) {
+ return self::FRIEND_COUNT_SCORE_0_50;
+ } elseif ($totalFriend <= 500) {
+ return self::FRIEND_COUNT_SCORE_51_500;
+ } elseif ($totalFriend <= 3000) {
+ return self::FRIEND_COUNT_SCORE_501_3000;
+ } else {
+ return self::FRIEND_COUNT_SCORE_3001_PLUS;
+ }
+ }
+
+ /**
+ * 手动更新好友数量分(用于处理同步问题)
+ *
+ * @param int $accountId 账号ID
+ * @param int $friendCount 好友数量
+ * @param string $source 来源(manual=手动,sync=同步)
+ * @return bool 更新是否成功
+ * @throws Exception 如果参数无效或更新过程中出现错误
+ */
+ public function updateFriendCountScore($accountId, $friendCount, $source = 'manual')
+ {
+ // 参数验证
+ if (empty($accountId) || !is_numeric($accountId)) {
+ $errorMsg = "无效的账号ID: " . (is_scalar($accountId) ? $accountId : gettype($accountId));
+ Log::error($errorMsg);
+ throw new Exception($errorMsg);
+ }
+
+ if (!is_numeric($friendCount) || $friendCount < 0) {
+ $errorMsg = "无效的好友数量: {$friendCount}";
+ Log::error($errorMsg);
+ throw new Exception($errorMsg);
+ }
+
+ if (!in_array($source, ['manual', 'sync'])) {
+ $errorMsg = "无效的来源: {$source},必须是 'manual' 或 'sync'";
+ Log::error($errorMsg);
+ throw new Exception($errorMsg);
+ }
+
+ try {
+ $scoreRecord = $this->getScoreRecord($accountId);
+
+ // 如果基础分已计算,不允许修改好友数量分(除非是手动更新)
+ if (!empty($scoreRecord['baseScoreCalculated']) && $source === 'sync') {
+ // 同步数据不允许修改已计算的基础分
+ Log::warning("同步数据不允许修改已计算的基础分,accountId: {$accountId}");
+ return false;
+ }
+ }
+ catch (\Exception $e) {
+ $errorMsg = "获取评分记录失败,accountId: {$accountId}, 错误: " . $e->getMessage();
+ Log::error($errorMsg);
+ throw new Exception($errorMsg, $e->getCode(), $e);
+ }
+
+ $friendCountScore = $this->getFriendCountScore($friendCount);
+
+ // 重新计算基础分
+ $oldBaseScore = $scoreRecord['baseScore'] ?? self::DEFAULT_BASE_SCORE;
+ $oldFriendCountScore = $scoreRecord['friendCountScore'] ?? 0;
+ $baseInfoScore = $scoreRecord['baseInfoScore'] ?? 0;
+
+ $newBaseScore = self::DEFAULT_BASE_SCORE + $baseInfoScore + $friendCountScore;
+
+ $updateData = [
+ 'friendCountScore' => $friendCountScore,
+ 'friendCount' => $friendCount,
+ 'friendCountSource' => $source,
+ 'baseScore' => $newBaseScore,
+ 'updateTime' => time()
+ ];
+
+ // 如果基础分已计算,需要更新总分
+ if (!empty($scoreRecord['baseScoreCalculated'])) {
+ $dynamicScore = $scoreRecord['dynamicScore'] ?? 0;
+ $healthScore = $newBaseScore + $dynamicScore;
+ $healthScore = max(0, min(100, $healthScore));
+ $updateData['healthScore'] = $healthScore;
+ $updateData['maxAddFriendPerDay'] = $this->getMaxAddFriendPerDay($healthScore);
+ }
+
+ try {
+ $result = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE)
+ ->where('accountId', $accountId)
+ ->update($updateData);
+
+ // 更新成功后,清除缓存
+ if ($result !== false) {
+ $this->clearScoreCache($accountId);
+ $this->clearHealthScoreCache($accountId);
+ Log::info("更新好友数量分成功,accountId: {$accountId}, friendCount: {$friendCount}, source: {$source}");
+ } else {
+ Log::warning("更新好友数量分失败,accountId: {$accountId}, friendCount: {$friendCount}, source: {$source}");
+ }
+
+ return $result !== false;
+ } catch (\PDOException $e) {
+ $errorMsg = "数据库操作失败,accountId: {$accountId}, 错误: " . $e->getMessage();
+ Log::error($errorMsg);
+ throw new Exception($errorMsg, $e->getCode(), $e);
+ } catch (\Throwable $e) {
+ $errorMsg = "更新好友数量分失败,accountId: {$accountId}, 错误: " . $e->getMessage();
+ Log::error($errorMsg);
+ throw new Exception($errorMsg, $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * 计算动态分
+ * 动态分 = 扣分 + 加分
+ * 如果添加好友记录表没有记录,则动态分为0
+ *
+ * @param array $accountData 账号数据
+ * @param array $scoreRecord 现有评分记录
+ * @return array 动态分数据
+ */
+ private function calculateDynamicScore($accountData, $scoreRecord)
+ {
+ $accountId = $accountData['id'] ?? 0;
+ $wechatId = $accountData['wechatId'] ?? '';
+
+ Log::debug("开始计算动态分,accountId: {$accountId}, wechatId: {$wechatId}");
+
+ $result = [
+ 'total' => 0,
+ 'frequentPenalty' => 0,
+ 'noFrequentBonus' => 0,
+ 'banPenalty' => 0,
+ 'lastFrequentTime' => null,
+ 'frequentCount' => 0,
+ 'lastNoFrequentTime' => null,
+ 'consecutiveNoFrequentDays' => 0,
+ 'isBanned' => 0
+ ];
+
+ if (empty($accountId) || empty($wechatId)) {
+ Log::warning("计算动态分失败: accountId或wechatId为空");
+ return $result;
+ }
+
+ // 计算30天前的时间戳(在多个方法中使用)
+ $thirtyDaysAgo = time() - (30 * 24 * 3600);
+
+ // 检查添加好友记录表是否有记录,如果没有记录则动态分为0
+ // 使用EXISTS子查询优化性能,只检查是否存在记录,不需要计数
+ $hasFriendTask = Db::table(self::TABLE_FRIEND_TASK)
+ ->where('wechatAccountId', $accountId)
+ ->where(function($query) use ($wechatId) {
+ if (!empty($wechatId)) {
+ $query->where('wechatId', $wechatId);
+ }
+ })
+ ->value('id'); // 只获取ID,比count()更高效
+
+ // 如果添加好友记录表没有记录,则动态分为0
+ if (empty($hasFriendTask)) {
+ Log::info("账号没有添加好友记录,动态分为0,accountId: {$accountId}");
+ return $result;
+ }
+
+ Log::debug("账号有添加好友记录,继续计算动态分,accountId: {$accountId}");
+
+ // 继承现有数据
+ if (!empty($scoreRecord)) {
+ $result['lastFrequentTime'] = $scoreRecord['lastFrequentTime'] ?? null;
+ $result['frequentCount'] = $scoreRecord['frequentCount'] ?? 0;
+ $result['lastNoFrequentTime'] = $scoreRecord['lastNoFrequentTime'] ?? null;
+ $result['consecutiveNoFrequentDays'] = $scoreRecord['consecutiveNoFrequentDays'] ?? 0;
+ $result['frequentPenalty'] = $scoreRecord['frequentPenalty'] ?? 0;
+ $result['noFrequentBonus'] = $scoreRecord['noFrequentBonus'] ?? 0;
+ $result['banPenalty'] = $scoreRecord['banPenalty'] ?? 0;
+ }
+
+ // 1. 检查频繁记录(从s2_friend_task表查询,只统计近30天)
+ $frequentData = $this->checkFrequentFromFriendTask($accountId, $wechatId, $scoreRecord, $thirtyDaysAgo);
+ $result['lastFrequentTime'] = $frequentData['lastFrequentTime'] ?? null;
+ $result['frequentCount'] = $frequentData['frequentCount'] ?? 0;
+ $result['frequentPenalty'] = $frequentData['frequentPenalty'] ?? 0;
+
+ // 2. 检查封号记录(从s2_wechat_message表查询)
+ $banData = $this->checkBannedFromMessage($accountId, $wechatId, $thirtyDaysAgo);
+ if (!empty($banData)) {
+ $result['isBanned'] = $banData['isBanned'];
+ $result['banPenalty'] = $banData['banPenalty'];
+ }
+
+ // 3. 计算不频繁加分(基于近30天的频繁记录,反向参考频繁规则)
+ $noFrequentData = $this->calculateNoFrequentBonus($accountId, $wechatId, $frequentData, $thirtyDaysAgo);
+ $result['noFrequentBonus'] = $noFrequentData['bonus'] ?? 0;
+ $result['consecutiveNoFrequentDays'] = $noFrequentData['consecutiveDays'] ?? 0;
+ $result['lastNoFrequentTime'] = $noFrequentData['lastNoFrequentTime'] ?? null;
+
+ // 计算总分
+ $result['total'] = $result['frequentPenalty'] + $result['noFrequentBonus'] + $result['banPenalty'];
+
+ Log::debug("动态分计算结果,accountId: {$accountId}, frequentPenalty: {$result['frequentPenalty']}, " .
+ "noFrequentBonus: {$result['noFrequentBonus']}, banPenalty: {$result['banPenalty']}, " .
+ "total: {$result['total']}");
+
+ return $result;
+ }
+
+ /**
+ * 从s2_friend_task表检查频繁记录
+ * extra字段包含"操作过于频繁"即需要扣分
+ * 只统计近30天的数据
+ *
+ * @param int $accountId 账号ID
+ * @param string $wechatId 微信ID
+ * @param array $scoreRecord 现有评分记录
+ * @param int $thirtyDaysAgo 30天前的时间戳(可选,如果已计算则传入以避免重复计算)
+ * @return array|null
+ */
+ private function checkFrequentFromFriendTask($accountId, $wechatId, $scoreRecord, $thirtyDaysAgo = null)
+ {
+ // 如果没有传入30天前的时间戳,则计算
+ if ($thirtyDaysAgo === null) {
+ $thirtyDaysAgo = time() - (30 * 24 * 3600);
+ }
+
+ // 查询包含"操作过于频繁"的记录(只统计近30天)
+ // extra字段可能是文本或JSON格式,使用LIKE查询
+ // 优化查询:只查询必要的字段,减少数据传输量
+ $frequentTasks = Db::table(self::TABLE_FRIEND_TASK)
+ ->where('wechatAccountId', $accountId)
+ ->where('createTime', '>=', $thirtyDaysAgo)
+ ->where(function($query) use ($wechatId) {
+ if (!empty($wechatId)) {
+ $query->where('wechatId', $wechatId);
+ }
+ })
+ ->where(function($query) {
+ // 检查extra字段是否包含"操作过于频繁"(可能是文本或JSON)
+ $query->where('extra', 'like', '%操作过于频繁%')
+ ->whereOr('extra', 'like', '%"当前账号存在安全风险"%');
+ })
+ ->order('createTime', 'desc')
+ ->field('id, createTime, extra')
+ ->select();
+
+ // 获取最新的频繁时间
+ $latestFrequentTime = !empty($frequentTasks) ? $frequentTasks[0]['createTime'] : null;
+
+ // 计算频繁次数(统计近30天内包含"操作过于频繁"的记录)
+ $frequentCount = count($frequentTasks);
+
+ // 如果30天内没有频繁记录,清除扣分
+ if (empty($frequentTasks)) {
+ return [
+ 'lastFrequentTime' => null,
+ 'frequentCount' => 0,
+ 'frequentPenalty' => 0
+ ];
+ }
+
+ // 根据30天内的频繁次数计算扣分
+ $penalty = 0;
+ if ($frequentCount == 1) {
+ $penalty = self::PENALTY_FIRST_FREQUENT; // 首次频繁-15分
+ } elseif ($frequentCount >= 2) {
+ $penalty = self::PENALTY_SECOND_FREQUENT; // 再次频繁-25分
+ }
+
+ return [
+ 'lastFrequentTime' => $latestFrequentTime,
+ 'frequentCount' => $frequentCount,
+ 'frequentPenalty' => $penalty
+ ];
+ }
+
+ /**
+ * 从s2_wechat_message表检查封号记录
+ * content包含"你的账号被限制"且msgType为10000
+ * 只统计近30天的数据
+ *
+ * @param int $accountId 账号ID
+ * @param string $wechatId 微信ID
+ * @param int $thirtyDaysAgo 30天前的时间戳(可选,如果已计算则传入以避免重复计算)
+ * @return array|null
+ */
+ private function checkBannedFromMessage($accountId, $wechatId, $thirtyDaysAgo = null)
+ {
+ // 如果没有传入30天前的时间戳,则计算
+ if ($thirtyDaysAgo === null) {
+ $thirtyDaysAgo = time() - (30 * 24 * 3600);
+ }
+
+ // 查询封号消息(只统计近30天)
+ // 优化查询:只查询必要的字段,减少数据传输量
+ $banMessage = Db::table(self::TABLE_WECHAT_MESSAGE)
+ ->where('wechatAccountId', $accountId)
+ ->where('msgType', 10000)
+ ->where('content', 'like', '%你的账号被限制%')
+ ->where('isDeleted', 0)
+ ->where('createTime', '>=', $thirtyDaysAgo)
+ ->field('id, createTime') // 只查询必要的字段
+ ->order('createTime', 'desc')
+ ->find();
+
+ if (!empty($banMessage)) {
+ return [
+ 'isBanned' => 1,
+ 'banPenalty' => self::PENALTY_BANNED // 封号-60分
+ ];
+ }
+
+ return [
+ 'isBanned' => 0,
+ 'banPenalty' => 0
+ ];
+ }
+
+ /**
+ * 计算不频繁加分
+ * 反向参考频繁规则:查询近30天的频繁记录,计算连续不频繁天数
+ * 规则:30天内连续不频繁的,只要有一次频繁就得重新计算(重置连续不频繁天数)
+ * 如果连续3天没有频繁,则每天+5分
+ *
+ * @param int $accountId 账号ID
+ * @param string $wechatId 微信ID
+ * @param array $frequentData 频繁数据(包含lastFrequentTime和frequentCount)
+ * @param int $thirtyDaysAgo 30天前的时间戳(可选,如果已计算则传入以避免重复计算)
+ * @return array 包含bonus、consecutiveDays、lastNoFrequentTime
+ */
+ private function calculateNoFrequentBonus($accountId, $wechatId, $frequentData, $thirtyDaysAgo = null)
+ {
+ $result = [
+ 'bonus' => 0,
+ 'consecutiveDays' => 0,
+ 'lastNoFrequentTime' => null
+ ];
+
+ if (empty($accountId) || empty($wechatId)) {
+ return $result;
+ }
+
+ // 如果没有传入30天前的时间戳,则计算
+ if ($thirtyDaysAgo === null) {
+ $thirtyDaysAgo = time() - (30 * 24 * 3600);
+ }
+ $currentTime = time();
+
+ // 获取最后一次频繁时间(30天内最后一次频繁的时间)
+ $lastFrequentTime = $frequentData['lastFrequentTime'] ?? null;
+
+ // 规则:30天内连续不频繁的,只要有一次频繁就得重新计算(重置连续不频繁天数)
+ if (empty($lastFrequentTime)) {
+ // 情况1:30天内没有频繁记录,说明30天内连续不频繁
+ // 计算从30天前到现在的连续不频繁天数(最多30天)
+ $consecutiveDays = min(30, floor(($currentTime - $thirtyDaysAgo) / 86400));
+ } else {
+ // 情况2:30天内有频繁记录,从最后一次频繁时间开始重新计算连续不频繁天数
+ // 只要有一次频繁,连续不频繁天数就从最后一次频繁时间开始重新计算
+ // 计算从最后一次频繁时间到现在,连续多少天没有频繁
+ $timeDiff = $currentTime - $lastFrequentTime;
+ $consecutiveDays = floor($timeDiff / 86400); // 向下取整,得到完整的天数
+
+ // 边界情况:如果最后一次频繁时间在30天前(理论上不应该发生,因为查询已经限制了30天),则按30天处理
+ if ($lastFrequentTime < $thirtyDaysAgo) {
+ $consecutiveDays = min(30, floor(($currentTime - $thirtyDaysAgo) / 86400));
+ }
+ }
+
+ // 如果连续3天或以上没有频繁,则每天+5分
+ if ($consecutiveDays >= 3) {
+ $bonus = $consecutiveDays * self::BONUS_NO_FREQUENT_PER_DAY;
+ $result['bonus'] = $bonus;
+ $result['consecutiveDays'] = $consecutiveDays;
+ $result['lastNoFrequentTime'] = $currentTime;
+ } else {
+ $result['consecutiveDays'] = $consecutiveDays;
+ }
+
+ return $result;
+ }
+
+ /**
+ * 根据健康分计算每日最大加人次数
+ * 公式:每日最大加人次数 = 健康分 * 0.2
+ *
+ * @param int $healthScore 健康分
+ * @return int 每日最大加人次数
+ */
+ public function getMaxAddFriendPerDay($healthScore)
+ {
+ return (int)floor($healthScore * 0.2);
+ }
+
+ /**
+ * 批量计算并更新多个账号的健康分
+ *
+ * @param array $accountIds 账号ID数组(为空则处理所有账号)
+ * @param int $batchSize 每批处理数量
+ * @param bool $forceRecalculateBase 是否强制重新计算基础分
+ * @return array 处理结果统计
+ * @throws Exception 如果参数无效或批量处理过程中出现严重错误
+ */
+ public function batchCalculateAndUpdate($accountIds = [], $batchSize = 100, $forceRecalculateBase = false)
+ {
+ // 参数验证
+ if (!is_array($accountIds)) {
+ $errorMsg = "无效的账号ID数组: " . gettype($accountIds);
+ Log::error($errorMsg);
+ throw new Exception($errorMsg);
+ }
+
+ if (!is_numeric($batchSize) || $batchSize <= 0) {
+ $errorMsg = "无效的批处理大小: {$batchSize}";
+ Log::error($errorMsg);
+ throw new Exception($errorMsg);
+ }
+
+ try {
+ $startTime = microtime(true);
+ Log::info("开始批量计算健康分,batchSize: {$batchSize}, forceRecalculateBase: " . ($forceRecalculateBase ? 'true' : 'false'));
+
+ $stats = [
+ 'total' => 0,
+ 'success' => 0,
+ 'failed' => 0,
+ 'errors' => []
+ ];
+
+ // 如果没有指定账号ID,则处理所有账号
+ if (empty($accountIds)) {
+ Log::info("未指定账号ID,处理所有未删除账号");
+ $accountIds = Db::table(self::TABLE_WECHAT_ACCOUNT)
+ ->where('isDeleted', 0)
+ ->column('id');
+ }
+
+ $stats['total'] = count($accountIds);
+ Log::info("需要处理的账号总数: {$stats['total']}");
+
+ // 分批处理
+ $batches = array_chunk($accountIds, $batchSize);
+ $batchCount = count($batches);
+ Log::info("分批处理,共 {$batchCount} 批");
+
+ foreach ($batches as $batchIndex => $batch) {
+ $batchStartTime = microtime(true);
+ Log::info("开始处理第 " . ($batchIndex + 1) . " 批,共 " . count($batch) . " 个账号");
+
+ foreach ($batch as $accountId) {
+ try {
+ $this->calculateAndUpdate($accountId, null, $forceRecalculateBase);
+ $stats['success']++;
+ } catch (Exception $e) {
+ $stats['failed']++;
+ $stats['errors'][] = [
+ 'accountId' => $accountId,
+ 'error' => $e->getMessage()
+ ];
+ Log::error("账号 {$accountId} 计算失败: " . $e->getMessage());
+ }
+ }
+
+ $batchEndTime = microtime(true);
+ $batchDuration = round($batchEndTime - $batchStartTime, 2);
+ Log::info("第 " . ($batchIndex + 1) . " 批处理完成,耗时: {$batchDuration}秒," .
+ "成功: {$stats['success']},失败: {$stats['failed']}");
+ }
+
+ $endTime = microtime(true);
+ $totalDuration = round($endTime - $startTime, 2);
+ Log::info("批量计算健康分完成,总耗时: {$totalDuration}秒,成功: {$stats['success']},失败: {$stats['failed']}");
+
+ return $stats;
+ } catch (\PDOException $e) {
+ $errorMsg = "批量计算健康分过程中数据库操作失败: " . $e->getMessage();
+ Log::error($errorMsg);
+ throw new Exception($errorMsg, $e->getCode(), $e);
+ } catch (\Throwable $e) {
+ $errorMsg = "批量计算健康分过程中发生严重错误: " . $e->getMessage();
+ Log::error($errorMsg);
+ throw new Exception($errorMsg, $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * 记录频繁事件(已废弃,改为从s2_friend_task表自动检测)
+ * 保留此方法以兼容旧代码,实际频繁检测在calculateDynamicScore中完成
+ *
+ * @param int $accountId 账号ID
+ * @return bool
+ */
+ public function recordFrequent($accountId)
+ {
+ // 频繁检测已改为从s2_friend_task表自动检测
+ // 直接重新计算健康分即可
+ try {
+ $this->calculateAndUpdate($accountId);
+ return true;
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * 记录不频繁事件(用于加分)
+ *
+ * @param int $accountId 账号ID
+ * @return bool
+ */
+ public function recordNoFrequent($accountId)
+ {
+ $scoreRecord = $this->getScoreRecord($accountId);
+
+ if (empty($scoreRecord)) {
+ // 如果记录不存在,先创建
+ $accountData = Db::table(self::TABLE_WECHAT_ACCOUNT)
+ ->where('id', $accountId)
+ ->find();
+
+ if (empty($accountData)) {
+ return false;
+ }
+
+ $this->getOrCreateScoreRecord($accountId, $accountData['wechatId']);
+ $scoreRecord = $this->getScoreRecord($accountId);
+ }
+
+ $lastNoFrequentTime = $scoreRecord['lastNoFrequentTime'] ?? null;
+ $consecutiveNoFrequentDays = $scoreRecord['consecutiveNoFrequentDays'] ?? 0;
+ $currentTime = time();
+
+ // 如果上次不频繁时间是昨天或更早,则增加连续天数
+ if (empty($lastNoFrequentTime) || ($currentTime - $lastNoFrequentTime) >= 86400) {
+ // 如果间隔超过2天,重置为1天
+ if (!empty($lastNoFrequentTime) && ($currentTime - $lastNoFrequentTime) > 86400 * 2) {
+ $consecutiveNoFrequentDays = 1;
+ } else {
+ $consecutiveNoFrequentDays++;
+ }
+ }
+
+ // 计算加分(连续3天及以上才加分)
+ $bonus = 0;
+ if ($consecutiveNoFrequentDays >= 3) {
+ $bonus = $consecutiveNoFrequentDays * self::BONUS_NO_FREQUENT_PER_DAY;
+ }
+
+ $updateData = [
+ 'lastNoFrequentTime' => $currentTime,
+ 'consecutiveNoFrequentDays' => $consecutiveNoFrequentDays,
+ 'noFrequentBonus' => $bonus,
+ 'updateTime' => $currentTime
+ ];
+
+ Db::table('s2_wechat_account_score')
+ ->where('accountId', $accountId)
+ ->update($updateData);
+
+ // 重新计算健康分
+ $this->calculateAndUpdate($accountId);
+
+ return true;
+ }
+
+ /**
+ * 获取账号健康分信息
+ *
+ * @param int $accountId 账号ID
+ * @param bool $useCache 是否使用缓存(默认true)
+ * @param bool $forceRecalculate 是否强制重新计算(默认false)
+ * @return array|null
+ */
+ public function getHealthScore($accountId, $useCache = true, $forceRecalculate = false)
+ {
+ // 如果强制重新计算,则不使用缓存
+ if ($forceRecalculate) {
+ Log::info("强制重新计算健康分,accountId: {$accountId}");
+ return $this->calculateAndUpdate($accountId, null, false);
+ }
+
+ // 生成缓存键
+ $cacheKey = self::CACHE_PREFIX . 'health:' . $accountId;
+
+ // 如果使用缓存且缓存存在,则直接返回缓存数据
+ if ($useCache && !$forceRecalculate && Cache::has($cacheKey)) {
+ $cachedData = Cache::get($cacheKey);
+ Log::debug("从缓存获取健康分信息,accountId: {$accountId}");
+ return $cachedData;
+ }
+
+ // 从数据库获取记录
+ $scoreRecord = $this->getScoreRecord($accountId, $useCache);
+
+ if (empty($scoreRecord)) {
+ return null;
+ }
+
+ $healthScoreInfo = [
+ 'accountId' => $scoreRecord['accountId'],
+ 'wechatId' => $scoreRecord['wechatId'],
+ 'healthScore' => $scoreRecord['healthScore'] ?? 0,
+ 'baseScore' => $scoreRecord['baseScore'] ?? 0,
+ 'baseInfoScore' => $scoreRecord['baseInfoScore'] ?? 0,
+ 'friendCountScore' => $scoreRecord['friendCountScore'] ?? 0,
+ 'friendCount' => $scoreRecord['friendCount'] ?? 0,
+ 'dynamicScore' => $scoreRecord['dynamicScore'] ?? 0,
+ 'frequentPenalty' => $scoreRecord['frequentPenalty'] ?? 0,
+ 'noFrequentBonus' => $scoreRecord['noFrequentBonus'] ?? 0,
+ 'banPenalty' => $scoreRecord['banPenalty'] ?? 0,
+ 'maxAddFriendPerDay' => $scoreRecord['maxAddFriendPerDay'] ?? 0,
+ 'baseScoreCalculated' => $scoreRecord['baseScoreCalculated'] ?? 0,
+ 'lastFrequentTime' => $scoreRecord['lastFrequentTime'] ?? null,
+ 'frequentCount' => $scoreRecord['frequentCount'] ?? 0,
+ 'isBanned' => $scoreRecord['isBanned'] ?? 0
+ ];
+
+ // 如果使用缓存,则缓存健康分信息
+ if ($useCache) {
+ Cache::set($cacheKey, $healthScoreInfo, self::CACHE_TTL);
+ Log::debug("缓存健康分信息,accountId: {$accountId}");
+ }
+
+ return $healthScoreInfo;
+ }
+
+ /**
+ * 清除健康分信息缓存
+ *
+ * @param int $accountId 账号ID
+ * @return bool 是否成功清除缓存
+ */
+ public function clearHealthScoreCache($accountId)
+ {
+ $cacheKey = self::CACHE_PREFIX . 'health:' . $accountId;
+ $result = Cache::rm($cacheKey);
+
+ // 同时清除评分记录缓存
+ $this->clearScoreCache($accountId);
+
+ Log::debug("清除健康分信息缓存,accountId: {$accountId}, 结果: " . ($result ? "成功" : "失败"));
+ return $result;
+ }
+}
diff --git a/Server/application/cunkebao/controller/ContentLibraryController.php b/Server/application/cunkebao/controller/ContentLibraryController.php
index d77c12d8..69291123 100644
--- a/Server/application/cunkebao/controller/ContentLibraryController.php
+++ b/Server/application/cunkebao/controller/ContentLibraryController.php
@@ -601,12 +601,14 @@ class ContentLibraryController extends Controller
$item['urls'] = json_decode($item['urls'] ?: '[]', true);
// 格式化时间
- if ($item['createMomentTime']) {
- $item['time'] = date('Y-m-d H:i:s', $item['createMomentTime']);
- } elseif ($item['createMessageTime']) {
- $item['time'] = date('Y-m-d H:i:s', $item['createMessageTime']);
+ if (!empty($item['createMomentTime']) && is_numeric($item['createMomentTime'])) {
+ $item['time'] = date('Y-m-d H:i:s', (int)$item['createMomentTime']);
+ } elseif (!empty($item['createMessageTime']) && is_numeric($item['createMessageTime'])) {
+ $item['time'] = date('Y-m-d H:i:s', (int)$item['createMessageTime']);
+ } elseif (!empty($item['createTime']) && is_numeric($item['createTime'])) {
+ $item['time'] = date('Y-m-d H:i:s', (int)$item['createTime']);
} else {
- $item['time'] = date('Y-m-d H:i:s', $item['createTime']);
+ $item['time'] = date('Y-m-d H:i:s'); // 如果没有有效的时间戳,使用当前时间
}
// 设置发送者信息
@@ -1068,7 +1070,7 @@ class ContentLibraryController extends Controller
->select()->toArray();
if (empty($libraries)) {
- return json(['code' => 200, 'msg' => '没有可用的内容库配置']);
+ return json_encode(['code' => 200, 'msg' => '没有可用的内容库配置'],256);
}
$successCount = 0;
@@ -1157,7 +1159,7 @@ class ContentLibraryController extends Controller
}
// 返回采集结果
- return json([
+ return json_encode([
'code' => 200,
'msg' => '采集任务执行完成',
'data' => [
@@ -1167,7 +1169,7 @@ class ContentLibraryController extends Controller
'skipped' => $totalLibraries - $successCount - $failCount,
'results' => $results
]
- ]);
+ ],256);
}
/**
@@ -1204,7 +1206,7 @@ class ContentLibraryController extends Controller
->whereIn('id', $friendIds)
->where('isDeleted', 0)
->select();
-
+
if (empty($friends)) {
return [
'status' => 'failed',
diff --git a/Server/application/cunkebao/controller/WorkbenchController.php b/Server/application/cunkebao/controller/WorkbenchController.php
index 62e17116..11681d9d 100644
--- a/Server/application/cunkebao/controller/WorkbenchController.php
+++ b/Server/application/cunkebao/controller/WorkbenchController.php
@@ -2816,6 +2816,8 @@ class WorkbenchController extends Controller
$limit = $this->request->param('limit', 10);
$workbenchId = $this->request->param('workbenchId', 0);
$keyword = $this->request->param('keyword', '');
+ $pushType = $this->request->param('pushType', ''); // 推送类型筛选:''=全部, 'friend'=好友消息, 'group'=群消息, 'announcement'=群公告
+ $status = $this->request->param('status', ''); // 状态筛选:''=全部, 'success'=已完成, 'progress'=进行中, 'failed'=失败
$userId = $this->request->userInfo['id'];
// 构建工作台查询条件
@@ -2840,10 +2842,11 @@ class WorkbenchController extends Controller
$workbenchWhere[] = ['w.id', '=', $workbenchId];
}
- // 按内容ID、工作台ID和时间分组,统计每次推送
- $query = Db::name('workbench_group_push_item')
+ // 1. 先查询所有已执行的推送记录(按推送时间分组)
+ $pushHistoryQuery = Db::name('workbench_group_push_item')
->alias('wgpi')
->join('workbench w', 'w.id = wgpi.workbenchId', 'left')
+ ->join('workbench_group_push wgp', 'wgp.workbenchId = wgpi.workbenchId', 'left')
->join('content_item ci', 'ci.id = wgpi.contentId', 'left')
->join('content_library cl', 'cl.id = ci.libraryId', 'left')
->where($workbenchWhere)
@@ -2853,52 +2856,57 @@ class WorkbenchController extends Controller
'wgpi.contentId',
'FROM_UNIXTIME(wgpi.createTime, "%Y-%m-%d %H:00:00") as pushTime',
'wgpi.targetType',
+ 'wgp.groupPushSubType',
'MIN(wgpi.createTime) as createTime',
'COUNT(DISTINCT wgpi.id) as totalCount',
'cl.name as contentLibraryName'
])
- ->group('wgpi.workbenchId, wgpi.contentId, pushTime, wgpi.targetType');
+ ->group('wgpi.workbenchId, wgpi.contentId, pushTime, wgpi.targetType, wgp.groupPushSubType');
if (!empty($keyword)) {
- $query->where('w.name|cl.name|ci.content', 'like', '%' . $keyword . '%');
+ $pushHistoryQuery->where('w.name|cl.name|ci.content', 'like', '%' . $keyword . '%');
}
- // 获取分页数据
- $list = $query->order('createTime', 'desc')
- ->page($page, $limit)
- ->select();
-
- // 对于有 group by 的查询,统计总数需要重新查询
- $totalQuery = Db::name('workbench_group_push_item')
- ->alias('wgpi')
- ->join('workbench w', 'w.id = wgpi.workbenchId', 'left')
- ->join('content_item ci', 'ci.id = wgpi.contentId', 'left')
- ->join('content_library cl', 'cl.id = ci.libraryId', 'left')
- ->where($workbenchWhere);
-
- if (!empty($keyword)) {
- $totalQuery->where('w.name|cl.name|ci.content', 'like', '%' . $keyword . '%');
- }
-
- // 统计分组后的记录数(使用子查询)
- $subQuery = $totalQuery
+ $pushHistoryList = $pushHistoryQuery->order('createTime', 'desc')->select();
+
+ // 2. 查询所有任务(包括未执行的)
+ $allTasksQuery = Db::name('workbench')
+ ->alias('w')
+ ->join('workbench_group_push wgp', 'wgp.workbenchId = w.id', 'left')
+ ->where($workbenchWhere)
->field([
- 'wgpi.workbenchId',
- 'wgpi.contentId',
- 'FROM_UNIXTIME(wgpi.createTime, "%Y-%m-%d %H:00:00") as pushTime',
- 'wgpi.targetType'
- ])
- ->group('wgpi.workbenchId, wgpi.contentId, pushTime, wgpi.targetType')
- ->buildSql();
-
- $total = Db::table('(' . $subQuery . ') as temp')->count();
+ 'w.id as workbenchId',
+ 'w.name as workbenchName',
+ 'w.createTime',
+ 'wgp.targetType',
+ 'wgp.groupPushSubType',
+ 'wgp.groups',
+ 'wgp.friends',
+ 'wgp.trafficPools'
+ ]);
- // 处理每条记录
- foreach ($list as &$item) {
+ if (!empty($keyword)) {
+ $allTasksQuery->where('w.name', 'like', '%' . $keyword . '%');
+ }
+
+ $allTasks = $allTasksQuery->select();
+
+ // 3. 合并数据:已执行的推送记录 + 未执行的任务
+ $resultList = [];
+ $executedWorkbenchIds = [];
+
+ // 处理已执行的推送记录
+ foreach ($pushHistoryList as $item) {
$itemWorkbenchId = $item['workbenchId'];
$contentId = $item['contentId'];
$pushTime = $item['pushTime'];
$targetType = intval($item['targetType']);
+ $groupPushSubType = isset($item['groupPushSubType']) ? intval($item['groupPushSubType']) : 1;
+
+ // 标记该工作台已有执行记录
+ if (!in_array($itemWorkbenchId, $executedWorkbenchIds)) {
+ $executedWorkbenchIds[] = $itemWorkbenchId;
+ }
// 将时间字符串转换为时间戳范围(小时级别)
$pushTimeStart = strtotime($pushTime);
@@ -2937,23 +2945,149 @@ class WorkbenchController extends Controller
$failCount = 0; // 简化处理,实际需要从发送状态获取
// 状态判断
- $status = $successCount > 0 ? 'success' : 'failed';
+ $itemStatus = $successCount > 0 ? 'success' : 'failed';
if ($failCount > 0 && $successCount > 0) {
- $status = 'partial';
+ $itemStatus = 'partial';
}
- $item['pushType'] = $targetType == 1 ? '群推送' : '好友推送';
- $item['pushTypeCode'] = $targetType;
- $item['targetCount'] = $targetCount;
- $item['successCount'] = $successCount;
- $item['failCount'] = $failCount;
- $item['status'] = $status;
- $item['statusText'] = $status == 'success' ? '成功' : ($status == 'partial' ? '部分成功' : '失败');
- $item['createTime'] = date('Y-m-d H:i:s', $item['createTime']);
- // 任务名称(工作台名称)
- $item['taskName'] = $item['workbenchName'] ?? '';
+ // 推送类型判断
+ $pushTypeText = '';
+ $pushTypeCode = '';
+ if ($targetType == 1) {
+ // 群推送
+ if ($groupPushSubType == 2) {
+ $pushTypeText = '群公告';
+ $pushTypeCode = 'announcement';
+ } else {
+ $pushTypeText = '群消息';
+ $pushTypeCode = 'group';
+ }
+ } else {
+ // 好友推送
+ $pushTypeText = '好友消息';
+ $pushTypeCode = 'friend';
+ }
+
+ $resultList[] = [
+ 'workbenchId' => $itemWorkbenchId,
+ 'taskName' => $item['workbenchName'] ?? '',
+ 'pushType' => $pushTypeText,
+ 'pushTypeCode' => $pushTypeCode,
+ 'targetCount' => $targetCount,
+ 'successCount' => $successCount,
+ 'failCount' => $failCount,
+ 'status' => $itemStatus,
+ 'statusText' => $this->getStatusText($itemStatus),
+ 'createTime' => date('Y-m-d H:i:s', $item['createTime']),
+ 'contentLibraryName' => $item['contentLibraryName'] ?? ''
+ ];
}
- unset($item);
+
+ // 处理未执行的任务
+ foreach ($allTasks as $task) {
+ $taskWorkbenchId = $task['workbenchId'];
+
+ // 如果该任务已有执行记录,跳过(避免重复)
+ if (in_array($taskWorkbenchId, $executedWorkbenchIds)) {
+ continue;
+ }
+
+ $targetType = isset($task['targetType']) ? intval($task['targetType']) : 1;
+ $groupPushSubType = isset($task['groupPushSubType']) ? intval($task['groupPushSubType']) : 1;
+
+ // 计算目标数量(从配置中获取)
+ $targetCount = 0;
+ if ($targetType == 1) {
+ // 群推送:统计配置的群数量
+ $groups = json_decode($task['groups'] ?? '[]', true);
+ $targetCount = is_array($groups) ? count($groups) : 0;
+ } else {
+ // 好友推送:统计配置的好友数量或流量池数量
+ $friends = json_decode($task['friends'] ?? '[]', true);
+ $trafficPools = json_decode($task['trafficPools'] ?? '[]', true);
+ $friendCount = is_array($friends) ? count($friends) : 0;
+ $poolCount = is_array($trafficPools) ? count($trafficPools) : 0;
+ // 如果配置了流量池,目标数量暂时显示为流量池数量(实际数量需要从流量池中统计)
+ $targetCount = $friendCount > 0 ? $friendCount : $poolCount;
+ }
+
+ // 推送类型判断
+ $pushTypeText = '';
+ $pushTypeCode = '';
+ if ($targetType == 1) {
+ // 群推送
+ if ($groupPushSubType == 2) {
+ $pushTypeText = '群公告';
+ $pushTypeCode = 'announcement';
+ } else {
+ $pushTypeText = '群消息';
+ $pushTypeCode = 'group';
+ }
+ } else {
+ // 好友推送
+ $pushTypeText = '好友消息';
+ $pushTypeCode = 'friend';
+ }
+
+ $resultList[] = [
+ 'workbenchId' => $taskWorkbenchId,
+ 'taskName' => $task['workbenchName'] ?? '',
+ 'pushType' => $pushTypeText,
+ 'pushTypeCode' => $pushTypeCode,
+ 'targetCount' => $targetCount,
+ 'successCount' => 0,
+ 'failCount' => 0,
+ 'status' => 'pending',
+ 'statusText' => '进行中',
+ 'createTime' => date('Y-m-d H:i:s', $task['createTime']),
+ 'contentLibraryName' => ''
+ ];
+ }
+
+ // 应用筛选条件
+ $filteredList = [];
+ foreach ($resultList as $item) {
+ // 推送类型筛选
+ if (!empty($pushType)) {
+ if ($pushType === 'friend' && $item['pushTypeCode'] !== 'friend') {
+ continue;
+ }
+ if ($pushType === 'group' && $item['pushTypeCode'] !== 'group') {
+ continue;
+ }
+ if ($pushType === 'announcement' && $item['pushTypeCode'] !== 'announcement') {
+ continue;
+ }
+ }
+
+ // 状态筛选
+ if (!empty($status)) {
+ if ($status === 'success' && $item['status'] !== 'success') {
+ continue;
+ }
+ if ($status === 'progress') {
+ // 进行中:包括 partial 和 pending
+ if ($item['status'] !== 'partial' && $item['status'] !== 'pending') {
+ continue;
+ }
+ }
+ if ($status === 'failed' && $item['status'] !== 'failed') {
+ continue;
+ }
+ }
+
+ $filteredList[] = $item;
+ }
+
+ // 按创建时间倒序排序
+ usort($filteredList, function($a, $b) {
+ return strtotime($b['createTime']) - strtotime($a['createTime']);
+ });
+
+ // 分页处理
+ $total = count($filteredList);
+ $offset = ($page - 1) * $limit;
+ $list = array_slice($filteredList, $offset, $limit);
return json([
'code' => 200,
@@ -2967,5 +3101,21 @@ class WorkbenchController extends Controller
]);
}
+ /**
+ * 获取状态文本
+ * @param string $status 状态码
+ * @return string 状态文本
+ */
+ private function getStatusText($status)
+ {
+ $statusMap = [
+ 'success' => '已完成',
+ 'partial' => '进行中',
+ 'pending' => '进行中',
+ 'failed' => '失败'
+ ];
+ return $statusMap[$status] ?? '未知';
+ }
+
}
\ No newline at end of file
diff --git a/Server/application/http/middleware/Jwt.php b/Server/application/http/middleware/Jwt.php
new file mode 100644
index 00000000..4f2fb7d1
--- /dev/null
+++ b/Server/application/http/middleware/Jwt.php
@@ -0,0 +1,49 @@
+ 401,
+ 'msg' => '未授权访问,缺少有效的身份凭证',
+ 'data' => null
+ ])->header(['Content-Type' => 'application/json; charset=utf-8']);
+ }
+
+ $payload = JwtUtil::verifyToken($token);
+ if (!$payload) {
+ return json([
+ 'code' => 401,
+ 'msg' => '授权已过期或无效',
+ 'data' => null
+ ])->header(['Content-Type' => 'application/json; charset=utf-8']);
+ }
+
+ // 将用户信息附加到请求中
+ $request->userInfo = $payload;
+
+ // 写入日志
+ Log::info('JWT认证通过', ['user_id' => $payload['id'] ?? 0, 'username' => $payload['username'] ?? '']);
+
+ return $next($request);
+ }
+}
diff --git a/Server/composer.json b/Server/composer.json
index 61d3fa7c..26ba92b7 100644
--- a/Server/composer.json
+++ b/Server/composer.json
@@ -1,33 +1,72 @@
{
- "name": "topthink/think",
- "description": "the new thinkphp framework",
- "type": "project",
- "keywords": [
- "framework",
- "thinkphp",
- "ORM"
- ],
- "homepage": "http://thinkphp.cn/",
- "license": "Apache-2.0",
- "authors": [
- {
- "name": "liu21st",
- "email": "liu21st@gmail.com"
- }
- ],
- "require": {
- "php": ">=5.4.0",
- "topthink/framework": "5.0.*"
+ "name": "topthink/think",
+ "description": "the new thinkphp framework",
+ "type": "project",
+ "keywords": [
+ "framework",
+ "thinkphp",
+ "ORM"
+ ],
+ "homepage": "http://thinkphp.cn/",
+ "license": "Apache-2.0",
+ "authors": [
+ {
+ "name": "liu21st",
+ "email": "liu21st@gmail.com"
},
- "autoload": {
- "psr-4": {
- "app\\": "application"
- }
- },
- "extra": {
- "think-path": "thinkphp"
- },
- "config": {
- "preferred-install": "dist"
+ {
+ "name": "yunwuxin",
+ "email": "448901948@qq.com"
}
+ ],
+ "require": {
+ "php": ">=5.6.0",
+ "topthink/framework": "5.1.41",
+ "topthink/think-installer": "2.*",
+ "topthink/think-captcha": "^2.0",
+ "topthink/think-helper": "^3.0",
+ "topthink/think-image": "^1.0",
+ "topthink/think-queue": "^2.0",
+ "topthink/think-worker": "^2.0",
+ "textalk/websocket": "^1.5",
+ "aliyuncs/oss-sdk-php": "^2.6",
+ "monolog/monolog": "^1.27",
+ "guzzlehttp/guzzle": "^6.5",
+ "overtrue/wechat": "~4.6",
+ "endroid/qr-code": "^3.9",
+ "phpoffice/phpspreadsheet": "^1.29",
+ "workerman/workerman": "^3.5",
+ "workerman/gateway-worker": "^3.0",
+ "hashids/hashids": "^2.0",
+ "khanamiryan/qrcode-detector-decoder": "^1.0",
+ "lizhichao/word": "^2.0",
+ "adbario/php-dot-notation": "^2.2"
+ },
+ "require-dev": {
+ "symfony/var-dumper": "^3.4|^4.4",
+ "topthink/think-migration": "^2.0",
+ "phpunit/phpunit": "^5.0|^6.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "app\\": "application",
+ "Eison\\": "extend/Eison"
+ },
+ "files": [
+ "application/common.php"
+ ],
+ "classmap": []
+ },
+ "extra": {
+ "think-path": "thinkphp"
+ },
+ "config": {
+ "preferred-install": "dist",
+ "allow-plugins": {
+ "topthink/think-installer": true,
+ "easywechat-composer/easywechat-composer": true
+ }
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true
}
diff --git a/Server/config/middleware.php b/Server/config/middleware.php
index 132ab645..b221b8a1 100644
--- a/Server/config/middleware.php
+++ b/Server/config/middleware.php
@@ -23,7 +23,8 @@ return [
// 全局中间件
'alias' => [
- 'cors' => 'app\\common\\middleware\\AllowCrossDomain'
+ 'cors' => 'app\\common\\middleware\\AllowCrossDomain',
+ 'jwt' => 'app\\http\\middleware\\Jwt'
],
// 应用中间件
diff --git a/Server/crontab_tasks.md b/Server/crontab_tasks.md
index f5849517..f7d1dfc8 100644
--- a/Server/crontab_tasks.md
+++ b/Server/crontab_tasks.md
@@ -81,6 +81,8 @@
# 消息提醒
*/1 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think kf:notice >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/kf_notice.log 2>&1
+# 客服评分
+0 2 * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think wechat:calculate-score >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/calculate_score.log 2>&1
@@ -107,4 +109,10 @@
```bash
crontab -l
+```
+
+```bash
+- 本地: php think worker:server
+- 线上: php think worker:server -d (自带守护进程,无需搭配Supervisor 之类的工具)
+- php think worker:server stop php think worker:server status
```
\ No newline at end of file
diff --git a/Server/extend/Eison/Utils/Helper/ArrHelper.php b/Server/extend/Eison/Utils/Helper/ArrHelper.php
new file mode 100644
index 00000000..a659c82d
--- /dev/null
+++ b/Server/extend/Eison/Utils/Helper/ArrHelper.php
@@ -0,0 +1,125 @@
+select();
$taskData = array_merge($taskData, $tasks);
}
-
if ($taskData) {
foreach ($taskData as $task) {
$task_id = $task['task_id'];
$task_info = $this->getCustomerAcquisitionTask($task_id);
-
if (empty($task_info['status']) || empty($task_info['reqConf']) || empty($task_info['reqConf']['device'])) {
continue;
}
@@ -213,9 +212,86 @@ class Adapter implements WeChatServiceInterface
continue;
}
- // 判断24h内加的好友数量,friend_task 先固定10个人 getLast24hAddedFriendsCount
+ // 根据健康分判断24h内加的好友数量限制
+ $healthScoreService = new WechatAccountHealthScoreService();
+ $healthScoreInfo = $healthScoreService->getHealthScore($accountId);
+
+ // 如果健康分记录不存在,先计算一次
+ if (empty($healthScoreInfo)) {
+ try {
+ $healthScoreService->calculateAndUpdate($accountId);
+ $healthScoreInfo = $healthScoreService->getHealthScore($accountId);
+ } catch (\Exception $e) {
+ Log::error("计算健康分失败 (accountId: {$accountId}): " . $e->getMessage());
+ // 如果计算失败,使用默认值5作为兜底
+ $maxAddFriendPerDay = 5;
+ }
+ }
+
+ // 获取每日最大加人次数(基于健康分)
+ $maxAddFriendPerDay = $healthScoreInfo['maxAddFriendPerDay'] ?? 5;
+
+ // 如果健康分为0或很低,不允许添加好友
+ if ($maxAddFriendPerDay <= 0) {
+ Log::info("账号健康分过低,不允许添加好友 (accountId: {$accountId}, wechatId: {$wechatId}, healthScore: " . ($healthScoreInfo['healthScore'] ?? 0) . ")");
+ continue;
+ }
+
+ // 检查频繁暂停限制:首次频繁或再次频繁,暂停24小时
+ $lastFrequentTime = $healthScoreInfo['lastFrequentTime'] ?? null;
+ $frequentCount = $healthScoreInfo['frequentCount'] ?? 0;
+ if (!empty($lastFrequentTime) && $frequentCount > 0) {
+ $frequentPauseHours = 24; // 频繁暂停24小时
+ $frequentPauseTime = $lastFrequentTime + ($frequentPauseHours * 3600);
+ $currentTime = time();
+
+ if ($currentTime < $frequentPauseTime) {
+ $remainingHours = ceil(($frequentPauseTime - $currentTime) / 3600);
+ Log::info("账号频繁,暂停添加好友 (accountId: {$accountId}, wechatId: {$wechatId}, frequentCount: {$frequentCount}, 剩余暂停时间: {$remainingHours}小时)");
+ continue;
+ }
+ }
+
+ // 检查封号暂停限制:封号暂停72小时
+ $isBanned = $healthScoreInfo['isBanned'] ?? 0;
+ if ($isBanned == 1) {
+ // 查询封号时间(从s2_wechat_message表查询最近一次封号消息)
+ $banMessage = Db::table('s2_wechat_message')
+ ->where('wechatAccountId', $accountId)
+ ->where('msgType', 10000)
+ ->where('content', 'like', '%你的账号被限制%')
+ ->where('isDeleted', 0)
+ ->order('createTime', 'desc')
+ ->find();
+
+ if (!empty($banMessage)) {
+ $banTime = $banMessage['createTime'] ?? 0;
+ $banPauseHours = 72; // 封号暂停72小时
+ $banPauseTime = $banTime + ($banPauseHours * 3600);
+ $currentTime = time();
+
+ if ($currentTime < $banPauseTime) {
+ $remainingHours = ceil(($banPauseTime - $currentTime) / 3600);
+ Log::info("账号封号,暂停添加好友 (accountId: {$accountId}, wechatId: {$wechatId}, 剩余暂停时间: {$remainingHours}小时)");
+ continue;
+ }
+ }
+ }
+
+ // 判断今天添加的好友数量,使用健康分计算的每日最大加人次数
+ // 优先使用今天添加的好友数量(更符合"每日"限制)
+ $todayAddedFriendsCount = $this->getTodayAddedFriendsCount($wechatId);
+ if ($todayAddedFriendsCount >= $maxAddFriendPerDay) {
+ Log::info("今天添加好友数量已达上限 (accountId: {$accountId}, wechatId: {$wechatId}, count: {$todayAddedFriendsCount}, max: {$maxAddFriendPerDay}, healthScore: " . ($healthScoreInfo['healthScore'] ?? 0) . ")");
+ continue;
+ }
+
+ // 如果今天添加数量未达上限,再检查24小时内的数量(作为额外保护)
$last24hAddedFriendsCount = $this->getLast24hAddedFriendsCount($wechatId);
- if ($last24hAddedFriendsCount >= 20) {
+ // 24小时内的限制可以稍微宽松一些,设置为每日限制的1.2倍(防止跨天累积)
+ $max24hLimit = (int)ceil($maxAddFriendPerDay * 1.2);
+ if ($last24hAddedFriendsCount >= $max24hLimit) {
+ Log::info("24小时内添加好友数量已达上限 (accountId: {$accountId}, wechatId: {$wechatId}, count: {$last24hAddedFriendsCount}, max24h: {$max24hLimit}, maxDaily: {$maxAddFriendPerDay})");
continue;
}
@@ -828,6 +904,7 @@ class Adapter implements WeChatServiceInterface
if (empty($deviceIds)) {
return [];
}
+
$records = Db::table('s2_wechat_account')
->where('deviceAlive', 1)
->where('wechatAlive', 1)
diff --git a/Server/sql.sql b/Server/sql.sql
new file mode 100644
index 00000000..b49716bc
--- /dev/null
+++ b/Server/sql.sql
@@ -0,0 +1,2301 @@
+/*
+ Navicat Premium Data Transfer
+
+ Source Server : kr_存客宝
+ Source Server Type : MySQL
+ Source Server Version : 50736
+ Source Host : 56b4c23f6853c.gz.cdb.myqcloud.com:14413
+ Source Schema : cunkebao_v3
+
+ Target Server Type : MySQL
+ Target Server Version : 50736
+ File Encoding : 65001
+
+ Date: 12/11/2025 11:05:39
+*/
+
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+-- ----------------------------
+-- Table structure for ck_administrator_permissions
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_administrator_permissions`;
+CREATE TABLE `ck_administrator_permissions` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自动ID',
+ `adminId` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '超管用户ID',
+ `permissions` json NULL COMMENT '权限对象',
+ `createTime` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '更新时间',
+ `deleteTime` int(10) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '超级管理员权限配置表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_administrators
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_administrators`;
+CREATE TABLE `ck_administrators` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `username` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '管理员名字',
+ `account` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '登录账号',
+ `password` char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '登录密码',
+ `status` tinyint(3) UNSIGNED NULL DEFAULT 1 COMMENT '1->可用,0->禁用',
+ `lastLoginTime` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '最近登录时间',
+ `lastLoginIp` char(18) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '最近登录ip',
+ `authId` int(10) UNSIGNED NULL DEFAULT 0 COMMENT '权限id',
+ `createTime` int(10) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(10) NULL DEFAULT NULL COMMENT '更新时间',
+ `deleteTime` int(11) NULL DEFAULT 0 COMMENT '删除时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '超级管理员表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_ai_knowledge_base
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_ai_knowledge_base`;
+CREATE TABLE `ck_ai_knowledge_base` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `typeId` int(11) NULL DEFAULT 1 COMMENT '类型id',
+ `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称',
+ `label` json NULL COMMENT '标签',
+ `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id',
+ `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID',
+ `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间',
+ `isDel` tinyint(2) NOT NULL DEFAULT 0 COMMENT '是否删除',
+ `delTime` int(11) NOT NULL DEFAULT 0 COMMENT '删除时间',
+ `documentId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '知识库文件id',
+ `fileUrl` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件地址',
+ `size` int(10) NULL DEFAULT NULL COMMENT '文件大小',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ai知识库' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_ai_knowledge_base_type
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_ai_knowledge_base_type`;
+CREATE TABLE `ck_ai_knowledge_base_type` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `type` tinyint(2) NULL DEFAULT 1 COMMENT '类型 0系统 1用户创建',
+ `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称',
+ `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述',
+ `label` json NULL COMMENT '标签',
+ `prompt` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '提示词',
+ `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id',
+ `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID',
+ `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间',
+ `isDel` tinyint(2) NOT NULL DEFAULT 0 COMMENT '是否删除',
+ `delTime` int(11) NOT NULL DEFAULT 0 COMMENT '删除时间',
+ `status` tinyint(2) NULL DEFAULT 1 COMMENT '状态 1启用 0禁用',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ai知识库类型' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_ai_settings
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_ai_settings`;
+CREATE TABLE `ck_ai_settings` (
+ `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id',
+ `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID',
+ `config` json NULL COMMENT '配置信息',
+ `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间',
+ `isRelease` tinyint(2) NULL DEFAULT 0 COMMENT '是否发布 0未发布 1已发布',
+ `releaseTime` int(11) NULL DEFAULT NULL COMMENT '发布时间',
+ `botId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '智能体id',
+ `datasetId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '知识库id',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'AI配置' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_app_version
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_app_version`;
+CREATE TABLE `ck_app_version` (
+ `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `forceUpdate` tinyint(2) NULL DEFAULT 0 COMMENT '是否强制更新',
+ `version` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `downloadUrl` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `updateContent` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `createTime` int(11) NULL DEFAULT NULL,
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_attachments
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_attachments`;
+CREATE TABLE `ck_attachments` (
+ `id` int(10) NOT NULL AUTO_INCREMENT COMMENT '自增长ID',
+ `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '资源名',
+ `hash_key` char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '资源hash校验值',
+ `server` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '存储服务商',
+ `source` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '资源地址',
+ `dl_count` int(10) NULL DEFAULT 0 COMMENT '下载次数',
+ `size` int(10) NULL DEFAULT 0 COMMENT '资源大小',
+ `suffix` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '资源类型',
+ `scene` tinyint(3) NOT NULL COMMENT '引用场景,获客海报1',
+ `create_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
+ `update_at` timestamp(0) NULL DEFAULT NULL COMMENT '修改时间',
+ `delete_at` timestamp(0) NULL DEFAULT NULL COMMENT '删除时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `idx_hash_key`(`hash_key`) USING BTREE,
+ INDEX `idx_server`(`server`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 481 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '附件表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_call_recording
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_call_recording`;
+CREATE TABLE `ck_call_recording` (
+ `id` int(11) NOT NULL,
+ `phone` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号',
+ `isCallOut` tinyint(2) NULL DEFAULT NULL COMMENT '是否外呼',
+ `companyId` int(11) NULL DEFAULT NULL,
+ `callType` tinyint(2) NULL DEFAULT NULL,
+ `beginTime` int(11) NULL DEFAULT NULL,
+ `endTime` int(11) NULL DEFAULT NULL,
+ `createTime` int(11) NULL DEFAULT NULL,
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE INDEX `uk_id_phone_isCallOut_companyId`(`id`, `phone`, `isCallOut`, `companyId`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_company
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_company`;
+CREATE TABLE `ck_company` (
+ `id` int(11) UNSIGNED NOT NULL COMMENT '项目真实ID,非自增',
+ `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '项目名称',
+ `status` tinyint(1) UNSIGNED NULL DEFAULT 1 COMMENT '状态',
+ `tenantId` int(11) UNSIGNED NULL DEFAULT 242 COMMENT '触客宝租户ID',
+ `companyId` int(11) UNSIGNED NOT NULL COMMENT '触客宝部门ID',
+ `memo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
+ `createTime` int(11) NULL DEFAULT NULL,
+ `updateTime` int(11) NULL DEFAULT NULL,
+ `deleteTime` int(11) UNSIGNED NULL DEFAULT 0,
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '部门表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_content_item
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_content_item`;
+CREATE TABLE `ck_content_item` (
+ `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `libraryId` int(11) NOT NULL COMMENT '所属内容库ID',
+ `type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'moment' COMMENT '内容类型(moment:朋友圈)',
+ `contentType` tinyint(1) NULL DEFAULT 0 COMMENT '0:未知 1:图片 2:链接 3:视频 4:文本 5:小程序 ',
+ `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '内容标题',
+ `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '文本内容',
+ `contentAi` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '文本内容_Ai版',
+ `contentData` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '完整内容数据(JSON格式)',
+ `snsId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '朋友圈唯一标识',
+ `msgId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群消息唯一标识',
+ `wechatId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信ID',
+ `friendId` int(11) NULL DEFAULT NULL COMMENT '微信好友ID',
+ `createMomentTime` bigint(20) NULL DEFAULT 0 COMMENT '朋友圈创建时间',
+ `createTime` int(11) NULL DEFAULT NULL COMMENT '记录创建时间',
+ `updateTime` int(11) NULL DEFAULT NULL COMMENT '记录更新时间',
+ `coverImage` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '封面图片URL',
+ `resUrls` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '资源URL列表(JSON格式)',
+ `urls` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '相对路径URL列表(JSON格式)',
+ `location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '地理位置名称',
+ `lat` decimal(10, 6) NULL DEFAULT 0.000000 COMMENT '纬度',
+ `lng` decimal(10, 6) NULL DEFAULT 0.000000 COMMENT '经度',
+ `status` tinyint(1) NULL DEFAULT 1 COMMENT '状态(0:禁用,1:启用)',
+ `isDel` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除(0:否,1:是)',
+ `delTime` int(11) NULL DEFAULT 0 COMMENT '删除时间',
+ `wechatChatroomId` int(11) NULL DEFAULT NULL,
+ `senderNickname` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `createMessageTime` int(11) NULL DEFAULT NULL,
+ `comment` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '评论',
+ `sendTime` int(11) NULL DEFAULT 0 COMMENT '预计发布时间',
+ `sendTimes` int(11) NULL DEFAULT 0 COMMENT '实际发布时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `idx_library`(`libraryId`) USING BTREE,
+ INDEX `idx_snsid`(`snsId`) USING BTREE,
+ INDEX `idx_wechatid`(`wechatId`) USING BTREE,
+ INDEX `idx_friendid`(`friendId`) USING BTREE,
+ INDEX `idx_create_time`(`createTime`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 5876 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '内容项目表-存储朋友圈采集数据' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_content_library
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_content_library`;
+CREATE TABLE `ck_content_library` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `sourceType` tinyint(2) NOT NULL DEFAULT 1 COMMENT '类型 1好友 2群 3自定义',
+ `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '内容库名称',
+ `devices` json NULL COMMENT '设备列表,JSON格式:[{\"id\":1,\"name\":\"设备1\"},{\"id\":2,\"name\":\"设备2\"}]',
+ `catchType` json NULL COMMENT '采集类型',
+ `sourceFriends` json NULL COMMENT '选择的微信好友',
+ `sourceGroups` json NULL COMMENT '选择的微信群',
+ `groupMembers` json NULL COMMENT '选择的微信群的群成员',
+ `keywordInclude` json NULL COMMENT '包含的关键词',
+ `keywordExclude` json NULL COMMENT '排除的关键词',
+ `aiEnabled` tinyint(1) NULL DEFAULT 0 COMMENT '是否启用AI:0=禁用,1=启用',
+ `aiPrompt` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT 'AI提示词',
+ `timeEnabled` tinyint(1) NULL DEFAULT 0 COMMENT '是否启用时间限制:0=禁用,1=启用',
+ `timeStart` int(11) NULL DEFAULT NULL COMMENT '开始时间',
+ `timeEnd` int(11) NULL DEFAULT NULL COMMENT '结束时间',
+ `status` tinyint(1) NULL DEFAULT 0 COMMENT '状态:0=禁用,1=启用',
+ `userId` int(11) NOT NULL COMMENT '用户ID',
+ `companyId` int(11) NOT NULL COMMENT '公司ID',
+ `createTime` int(11) NULL DEFAULT 0 COMMENT '创建时间',
+ `updateTime` int(11) NULL DEFAULT 0 COMMENT '更新时间',
+ `isDel` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除',
+ `deleteTime` int(11) NULL DEFAULT NULL COMMENT '删除时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 87 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '内容库表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_coze_conversation
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_coze_conversation`;
+CREATE TABLE `ck_coze_conversation` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
+ `userId` int(11) NOT NULL DEFAULT 0 COMMENT '用户id',
+ `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id',
+ `conversation_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '对话ID',
+ `bot_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '机器人ID',
+ `created_at` int(11) NOT NULL DEFAULT 0 COMMENT '会话创建时间戳',
+ `meta_data` json NULL COMMENT '元数据',
+ `create_time` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间戳',
+ `update_time` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间戳',
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE INDEX `idx_conversation_id`(`conversation_id`) USING BTREE,
+ INDEX `idx_bot_id`(`bot_id`) USING BTREE,
+ INDEX `idx_create_time`(`create_time`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 39 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'Coze AI 会话表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_coze_message
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_coze_message`;
+CREATE TABLE `ck_coze_message` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `chat_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '消息ID',
+ `conversation_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '会话ID',
+ `bot_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '机器人ID',
+ `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '消息内容',
+ `content_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'text' COMMENT '内容类型',
+ `role` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色',
+ `type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '消息类型',
+ `created_at` int(11) NOT NULL COMMENT '消息创建时间',
+ `updated_at` int(11) NOT NULL COMMENT '消息更新时间',
+ `create_time` int(11) NOT NULL COMMENT '记录创建时间',
+ `update_time` int(11) NOT NULL COMMENT '记录更新时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE INDEX `uk_chat_id`(`chat_id`) USING BTREE,
+ INDEX `idx_conversation_id`(`conversation_id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 184 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '消息记录表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_coze_workspace
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_coze_workspace`;
+CREATE TABLE `ck_coze_workspace` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `workspace_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '工作区ID',
+ `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '工作区名称',
+ `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '工作区描述',
+ `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
+ `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE INDEX `workspace_id`(`workspace_id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'Coze空间表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_customer_acquisition_task
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_customer_acquisition_task`;
+CREATE TABLE `ck_customer_acquisition_task` (
+ `id` int(10) NOT NULL AUTO_INCREMENT COMMENT '自增长ID',
+ `name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '计划名称',
+ `sceneId` int(11) NULL DEFAULT 1 COMMENT '场景ID',
+ `sceneConf` json NULL COMMENT '场景具体配置信息',
+ `reqConf` json NULL COMMENT '好友申请设置',
+ `msgConf` json NULL COMMENT '消息设置',
+ `tagConf` json NULL COMMENT '标签设置',
+ `userId` int(11) NULL DEFAULT 0 COMMENT '创建者',
+ `companyId` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '公司ID',
+ `status` tinyint(3) NOT NULL DEFAULT 0 COMMENT '状态 0禁用 1启用',
+ `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间',
+ `updateTime` int(11) NULL DEFAULT NULL COMMENT '修改时间',
+ `deleteTime` int(11) NULL DEFAULT 0 COMMENT '删除时间',
+ `apiKey` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 162 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '获客计划表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_device
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_device`;
+CREATE TABLE `ck_device` (
+ `id` int(11) UNSIGNED NOT NULL COMMENT '设备真实ID,非自增',
+ `memo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '设备名称',
+ `imei` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '设备IMEI',
+ `deviceImei` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '设备本地IMEI',
+ `phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号',
+ `operatingSystem` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作系统版本',
+ `model` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '型号',
+ `brand` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '品牌',
+ `rooted` tinyint(1) NULL DEFAULT 0 COMMENT '是否root',
+ `xPosed` tinyint(1) NULL DEFAULT 0 COMMENT '是否安装xposed',
+ `softwareVersion` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '软件版本',
+ `extra` json NULL COMMENT '额外信息JSON',
+ `alive` tinyint(1) NULL DEFAULT 0 COMMENT '是否在线',
+ `companyId` int(11) NOT NULL COMMENT '公司ID',
+ `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
+ `deleteTime` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE INDEX `uni_id_imei`(`imei`, `id`) USING BTREE,
+ INDEX `idx_group`(`companyId`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_device_handle_log
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_device_handle_log`;
+CREATE TABLE `ck_device_handle_log` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `content` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作说明',
+ `deviceId` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '设备id',
+ `userId` int(11) NULL DEFAULT NULL COMMENT '用户id',
+ `companyId` int(11) NULL DEFAULT NULL COMMENT '租户id',
+ `createTime` int(11) NULL DEFAULT NULL COMMENT '操作时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 304 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_device_taskconf
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_device_taskconf`;
+CREATE TABLE `ck_device_taskconf` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
+ `deviceId` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '设备ID',
+ `autoLike` tinyint(3) NULL DEFAULT 0 COMMENT '自动点赞',
+ `momentsSync` tinyint(3) UNSIGNED NULL DEFAULT 0 COMMENT '朋友圈同步',
+ `autoCustomerDev` tinyint(3) UNSIGNED NULL DEFAULT 0 COMMENT '自动开发客户',
+ `groupMessageDeliver` tinyint(3) UNSIGNED NULL DEFAULT 0 COMMENT '群消息推送',
+ `autoGroup` tinyint(3) UNSIGNED NULL DEFAULT 0 COMMENT '自动建群',
+ `autoAddFriend` tinyint(3) NULL DEFAULT 0 COMMENT '自动加好友',
+ `contentSync` tinyint(255) UNSIGNED NULL DEFAULT 0 COMMENT '朋友圈同步',
+ `aiChat` tinyint(3) UNSIGNED NULL DEFAULT 0 COMMENT 'AI 会话',
+ `autoReply` tinyint(3) UNSIGNED NULL DEFAULT 0 COMMENT '自动回复',
+ `companyId` int(10) NULL DEFAULT NULL COMMENT '公司ID',
+ `createTime` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '更新时间',
+ `deleteTime` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 29 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备任务配置表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_device_user
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_device_user`;
+CREATE TABLE `ck_device_user` (
+ `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
+ `companyId` int(11) UNSIGNED NOT NULL COMMENT '公司id',
+ `userId` int(11) UNSIGNED NOT NULL COMMENT '用户id',
+ `deviceId` int(11) UNSIGNED NOT NULL COMMENT '设备id',
+ `deleteTime` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 22 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备跟操盘手的关联关系' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_device_wechat_login
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_device_wechat_login`;
+CREATE TABLE `ck_device_wechat_login` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `deviceId` int(11) NULL DEFAULT NULL COMMENT '设备ID',
+ `wechatId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信ID',
+ `alive` tinyint(3) UNSIGNED NULL DEFAULT 0 COMMENT '微信在线否',
+ `companyId` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '租户ID',
+ `createTime` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '更新时间',
+ `isTips` tinyint(2) NOT NULL DEFAULT 0 COMMENT '是否提示迁移',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `wechatId`(`wechatId`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 309 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备登录微信记录表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_flow_package
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_flow_package`;
+CREATE TABLE `ck_flow_package` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '套餐名称',
+ `tag` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '套餐标签',
+ `originalPrice` decimal(10, 2) NOT NULL DEFAULT 0.00 COMMENT '原价',
+ `price` decimal(10, 2) NOT NULL DEFAULT 0.00 COMMENT '售价',
+ `monthlyFlow` int(11) NOT NULL DEFAULT 0 COMMENT '每月流量(人/月)',
+ `duration` int(11) NOT NULL DEFAULT 1 COMMENT '套餐时长(月)',
+ `privileges` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '套餐特权,多行文本存储',
+ `sort` int(11) NOT NULL DEFAULT 0 COMMENT '排序',
+ `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态: 0=禁用, 1=启用',
+ `isDel` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除: 0=否, 1=是',
+ `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间',
+ `updateTime` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `idx_name`(`name`) USING BTREE,
+ INDEX `idx_tag`(`tag`) USING BTREE,
+ INDEX `idx_is_del`(`isDel`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量套餐表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_flow_package_order
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_flow_package_order`;
+CREATE TABLE `ck_flow_package_order` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `orderNo` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '订单编号',
+ `userId` int(11) NOT NULL DEFAULT 0 COMMENT '用户ID',
+ `packageId` int(11) NOT NULL DEFAULT 0 COMMENT '套餐ID',
+ `packageName` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '套餐名称',
+ `amount` decimal(10, 2) NOT NULL DEFAULT 0.00 COMMENT '订单金额',
+ `duration` int(11) NOT NULL DEFAULT 1 COMMENT '购买时长(月)',
+ `payStatus` tinyint(1) NOT NULL DEFAULT 0 COMMENT '支付状态: 0=未支付, 1=已支付,10=无需支付',
+ `payTime` int(11) NOT NULL DEFAULT 0 COMMENT '支付时间',
+ `payType` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '支付方式: wechat=微信, alipay=支付宝,nopay=无需支付',
+ `transactionId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '第三方支付交易号',
+ `status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '订单状态: 0=待支付, 1=已支付, 2=已取消, 3=已退款',
+ `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '备注',
+ `isDel` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除: 0=否, 1=是',
+ `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间',
+ `updateTime` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE INDEX `uk_order_no`(`orderNo`) USING BTREE,
+ INDEX `idx_user_id`(`userId`) USING BTREE,
+ INDEX `idx_package_id`(`packageId`) USING BTREE,
+ INDEX `idx_status`(`status`) USING BTREE,
+ INDEX `idx_pay_status`(`payStatus`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 37 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '套餐订单表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_flow_usage_record
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_flow_usage_record`;
+CREATE TABLE `ck_flow_usage_record` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `userId` int(11) NOT NULL DEFAULT 0 COMMENT '用户ID',
+ `packageId` int(11) NOT NULL DEFAULT 0 COMMENT '套餐ID',
+ `userPackageId` int(11) NOT NULL DEFAULT 0 COMMENT '用户套餐ID',
+ `taskId` int(11) NOT NULL DEFAULT 0 COMMENT '关联任务ID',
+ `phone` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '微信号',
+ `usageAmount` int(11) NOT NULL DEFAULT 0 COMMENT '使用量(人)',
+ `usageType` tinyint(1) NOT NULL DEFAULT 1 COMMENT '使用类型: 1=添加好友, 2=群发消息, 3=其他',
+ `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '备注',
+ `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间',
+ `updateTime` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `idx_user_id`(`userId`) USING BTREE,
+ INDEX `idx_package_id`(`packageId`) USING BTREE,
+ INDEX `idx_user_package_id`(`userPackageId`) USING BTREE,
+ INDEX `idx_task_id`(`taskId`) USING BTREE,
+ INDEX `idx_phone`(`phone`) USING BTREE,
+ INDEX `idx_create_time`(`createTime`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量使用记录表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_jd_promotion_site
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_jd_promotion_site`;
+CREATE TABLE `ck_jd_promotion_site` (
+ `id` bigint(11) NOT NULL,
+ `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `jdSocialMediaId` bigint(11) NULL DEFAULT NULL,
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_jd_social_media
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_jd_social_media`;
+CREATE TABLE `ck_jd_social_media` (
+ `id` bigint(11) NOT NULL,
+ `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `appkey` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `secretkey` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_kf_ai_push
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_kf_ai_push`;
+CREATE TABLE `ck_kf_ai_push` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '推送名称',
+ `tags` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '目标用户标签(JSON数组格式)',
+ `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '推送内容(支持变量:{客户名称}{产品功能}{核心价值}等)',
+ `pushTiming` tinyint(4) NOT NULL DEFAULT 1 COMMENT '推送时机:1=立即推送,2=最佳时机(AI决定),3=定时推送',
+ `scheduledTime` int(11) NOT NULL DEFAULT 0 COMMENT '定时推送时间(时间戳,仅当pushTiming=3时有效)',
+ `status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '启用状态:0=禁用,1=启用',
+ `successRate` decimal(5, 2) NOT NULL DEFAULT 0.00 COMMENT '成功率(百分比,保留两位小数)',
+ `userId` int(11) NOT NULL DEFAULT 0 COMMENT '用户ID',
+ `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司ID',
+ `isDel` tinyint(4) NOT NULL DEFAULT 0 COMMENT '删除标记:0=未删除,1=已删除',
+ `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间(时间戳)',
+ `updateTime` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间(时间戳)',
+ `delTime` int(11) NOT NULL DEFAULT 0 COMMENT '删除时间(时间戳)',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `idx_company_user`(`companyId`, `userId`) USING BTREE,
+ INDEX `idx_pushTiming`(`pushTiming`) USING BTREE,
+ INDEX `idx_status`(`status`) USING BTREE,
+ INDEX `idx_isDel`(`isDel`) USING BTREE,
+ INDEX `idx_scheduledTime`(`scheduledTime`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'AI智能推送表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_kf_ai_push_record
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_kf_ai_push_record`;
+CREATE TABLE `ck_kf_ai_push_record` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `pushId` int(11) NOT NULL DEFAULT 0 COMMENT '推送ID',
+ `userId` int(11) NOT NULL DEFAULT 0 COMMENT '用户ID',
+ `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司ID',
+ `wechatAccountId` int(11) NOT NULL DEFAULT 0 COMMENT '微信账号ID',
+ `friendIdOrGroupId` int(11) NOT NULL DEFAULT 0 COMMENT '好友ID或群ID',
+ `isSend` tinyint(4) NOT NULL DEFAULT 0 COMMENT '是否发送:0=未发送,1=已发送',
+ `sendTime` int(11) NOT NULL DEFAULT 0 COMMENT '发送时间(时间戳)',
+ `receiveTime` int(11) NOT NULL DEFAULT 0 COMMENT '接收时间(时间戳)',
+ `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间(时间戳)',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `idx_pushId`(`pushId`) USING BTREE,
+ INDEX `idx_company`(`companyId`) USING BTREE,
+ INDEX `idx_user`(`userId`) USING BTREE,
+ INDEX `idx_createTime`(`createTime`) USING BTREE,
+ INDEX `idx_isSend`(`isSend`) USING BTREE,
+ INDEX `idx_wechatAccount`(`wechatAccountId`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'AI智能推送记录表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_kf_auto_greetings
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_kf_auto_greetings`;
+CREATE TABLE `ck_kf_auto_greetings` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '规则名称',
+ `trigger` tinyint(4) NOT NULL DEFAULT 0 COMMENT '触发类型:1=好友首次添加,2=首次发消息,3=时间触发,4=关键词触发,5=生日触发,6=自定义',
+ `condition` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '具体条件(JSON格式)',
+ `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '问候内容',
+ `level` int(11) NOT NULL DEFAULT 0 COMMENT '优先级(数字越小优先级越高)',
+ `status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '启用状态:0=禁用,1=启用',
+ `usageCount` int(11) NOT NULL DEFAULT 0 COMMENT '使用次数',
+ `userId` int(11) NOT NULL DEFAULT 0 COMMENT '用户ID',
+ `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司ID',
+ `is_template` tinyint(4) NOT NULL DEFAULT 0 COMMENT '是否模板:0=否,1=是',
+ `isDel` tinyint(4) NOT NULL DEFAULT 0 COMMENT '删除标记:0=未删除,1=已删除',
+ `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间(时间戳)',
+ `updateTime` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间(时间戳)',
+ `delTime` int(11) NOT NULL DEFAULT 0 COMMENT '删除时间(时间戳)',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `idx_company_user`(`companyId`, `userId`) USING BTREE,
+ INDEX `idx_trigger`(`trigger`) USING BTREE,
+ INDEX `idx_status`(`status`) USING BTREE,
+ INDEX `idx_isDel`(`isDel`) USING BTREE,
+ INDEX `idx_level`(`level`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '问候规则表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_kf_auto_greetings_record
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_kf_auto_greetings_record`;
+CREATE TABLE `ck_kf_auto_greetings_record` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `autoId` int(11) NOT NULL DEFAULT 0 COMMENT '规则ID',
+ `userId` int(11) NOT NULL DEFAULT 0 COMMENT '用户ID',
+ `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司ID',
+ `wechatAccountId` int(11) NOT NULL DEFAULT 0 COMMENT '微信账号ID',
+ `friendIdOrGroupId` int(11) NOT NULL DEFAULT 0 COMMENT '好友ID或群ID',
+ `isSend` tinyint(4) NOT NULL DEFAULT 0 COMMENT '是否发送:0=未发送,1=已发送',
+ `sendTime` int(11) NOT NULL DEFAULT 0 COMMENT '发送时间(时间戳)',
+ `receiveTime` int(11) NOT NULL DEFAULT 0 COMMENT '接收时间(时间戳)',
+ `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间(时间戳)',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `idx_autoId`(`autoId`) USING BTREE,
+ INDEX `idx_company`(`companyId`) USING BTREE,
+ INDEX `idx_user`(`userId`) USING BTREE,
+ INDEX `idx_createTime`(`createTime`) USING BTREE,
+ INDEX `idx_isSend`(`isSend`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '问候规则使用记录表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_kf_follow_up
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_kf_follow_up`;
+CREATE TABLE `ck_kf_follow_up` (
+ `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id',
+ `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID',
+ `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标题',
+ `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述',
+ `friendId` int(12) NULL DEFAULT NULL COMMENT '好友id',
+ `type` tinyint(2) NULL DEFAULT 0 COMMENT '类型 0其他 1电话回访 2发送消息 3安排会议 4发送邮件',
+ `reminderTime` int(12) NULL DEFAULT NULL COMMENT '提醒时间',
+ `isRemind` tinyint(2) NULL DEFAULT 0 COMMENT '是否提醒',
+ `isProcess` tinyint(2) NULL DEFAULT 0 COMMENT '是否处理',
+ `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `idx_companyId`(`companyId`) USING BTREE,
+ INDEX `idx_userId`(`userId`) USING BTREE,
+ INDEX `idx_level`(`type`) USING BTREE,
+ INDEX `idx_isRemind`(`isRemind`) USING BTREE,
+ INDEX `idx_isProcess`(`isProcess`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '跟进提醒' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_kf_friend_settings
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_kf_friend_settings`;
+CREATE TABLE `ck_kf_friend_settings` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id',
+ `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID',
+ `type` tinyint(2) NULL DEFAULT 0 COMMENT '匹配类型 0人工接待 1AI辅助 2AI接管',
+ `wechatAccountId` int(11) NULL DEFAULT NULL COMMENT '客服id',
+ `friendId` int(11) NULL DEFAULT NULL COMMENT '好友id',
+ `conversationId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '会话id',
+ `conversationTime` int(11) NULL DEFAULT NULL COMMENT '会话创建时间',
+ `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `idx_companyId`(`companyId`) USING BTREE,
+ INDEX `idx_userId`(`userId`) USING BTREE,
+ INDEX `idx_wechatAccountId`(`wechatAccountId`) USING BTREE,
+ INDEX `idx_friendId`(`friendId`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 37 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '好友AI配置' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_kf_keywords
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_kf_keywords`;
+CREATE TABLE `ck_kf_keywords` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id',
+ `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID',
+ `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标题',
+ `keywords` json NULL COMMENT '关键词',
+ `type` tinyint(2) NULL DEFAULT NULL COMMENT '匹配类型 0模糊 1精确',
+ `replyType` tinyint(2) NULL DEFAULT NULL COMMENT '回复类型 0素材回复 1自定义',
+ `content` json NULL COMMENT '自定义内容',
+ `metailGroups` json NULL COMMENT '素材id',
+ `status` tinyint(2) NULL DEFAULT NULL COMMENT '状态 0停用 1启用',
+ `level` tinyint(2) NULL DEFAULT 0 COMMENT '等级 0低优先级 1中优先级 2高优先级',
+ `isDel` tinyint(2) NULL DEFAULT 0 COMMENT '是否删除',
+ `delTime` int(12) NULL DEFAULT NULL COMMENT '删除时间',
+ `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '关键词管理' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_kf_material
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_kf_material`;
+CREATE TABLE `ck_kf_material` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id',
+ `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID',
+ `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标题',
+ `content` json NULL COMMENT '内容',
+ `cover` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '封面',
+ `status` tinyint(2) NULL DEFAULT NULL COMMENT '状态 0停用 1启用',
+ `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间',
+ `isDel` tinyint(2) NULL DEFAULT 0 COMMENT '是否删除',
+ `delTime` int(12) NULL DEFAULT NULL COMMENT '删除时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 19 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '素材库管理' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_kf_moments
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_kf_moments`;
+CREATE TABLE `ck_kf_moments` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id',
+ `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID',
+ `sendData` json NULL COMMENT '发送的具体信息',
+ `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间',
+ `isSend` tinyint(2) NULL DEFAULT 0 COMMENT '是否发送 0否 1是',
+ `sendTime` int(11) NULL DEFAULT NULL COMMENT '发送时间',
+ `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间',
+ `isDel` tinyint(2) NULL DEFAULT 0 COMMENT '是否删除',
+ `delTime` int(12) NULL DEFAULT NULL COMMENT '删除时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '客服端发布朋友圈记录' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_kf_moments_settings
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_kf_moments_settings`;
+CREATE TABLE `ck_kf_moments_settings` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id',
+ `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID',
+ `wechatId` int(12) NULL DEFAULT NULL COMMENT '微信客服id',
+ `max` int(11) NULL DEFAULT 5 COMMENT '每日上限',
+ `sendNum` int(11) NULL DEFAULT 0 COMMENT '今日发送次数',
+ `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '客服朋友圈配置信息' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_kf_notice
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_kf_notice`;
+CREATE TABLE `ck_kf_notice` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `type` tinyint(2) NULL DEFAULT NULL COMMENT '通知类型 1代办事项 2跟进提醒 ',
+ `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id',
+ `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID',
+ `bindId` int(11) NULL DEFAULT NULL COMMENT '绑定的id',
+ `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标题',
+ `message` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '通知消息',
+ `isRead` tinyint(2) NULL DEFAULT 0 COMMENT '是否读取',
+ `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间',
+ `readTime` int(12) NULL DEFAULT NULL COMMENT '读取时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 246 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '通知消息' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_kf_questions
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_kf_questions`;
+CREATE TABLE `ck_kf_questions` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id',
+ `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID',
+ `type` tinyint(2) NULL DEFAULT 0 COMMENT '匹配类型 0模糊 1精确',
+ `questions` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '问题',
+ `answers` json NULL COMMENT '答案',
+ `status` tinyint(2) NULL DEFAULT 1 COMMENT '状态 0禁用 1启用',
+ `isDel` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除',
+ `deleteTime` int(12) NULL DEFAULT NULL COMMENT '删除时间',
+ `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `idx_companyId`(`companyId`) USING BTREE,
+ INDEX `idx_userId`(`userId`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'AI问答' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_kf_reply
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_kf_reply`;
+CREATE TABLE `ck_kf_reply` (
+ `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `groupId` int(11) NULL DEFAULT NULL,
+ `userId` int(11) NULL DEFAULT NULL,
+ `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `msgType` tinyint(2) NULL DEFAULT NULL,
+ `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `createTime` int(11) NULL DEFAULT NULL,
+ `lastUpdateTime` int(11) NULL DEFAULT NULL,
+ `sortIndex` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间',
+ `isDel` tinyint(2) NULL DEFAULT 0 COMMENT '是否删除',
+ `delTime` int(12) NULL DEFAULT NULL COMMENT '删除时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 130746 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '快捷回复' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_kf_reply_group
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_kf_reply_group`;
+CREATE TABLE `ck_kf_reply_group` (
+ `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `userId` int(11) NULL DEFAULT 0,
+ `companyId` int(11) NULL DEFAULT 0,
+ `groupName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `sortIndex` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `parentId` int(11) NULL DEFAULT NULL,
+ `replyType` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `replys` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间',
+ `isDel` tinyint(2) NULL DEFAULT 0 COMMENT '是否删除',
+ `delTime` int(12) NULL DEFAULT NULL COMMENT '删除时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 21898 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '快捷回复分组' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_kf_sensitive_word
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_kf_sensitive_word`;
+CREATE TABLE `ck_kf_sensitive_word` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id',
+ `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID',
+ `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标题',
+ `keywords` json NULL COMMENT '关键词',
+ `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '替换内容/警告内容',
+ `operation` tinyint(2) NULL DEFAULT NULL COMMENT '操作 0不操作 1替换 2删除 3警告 4禁止发送',
+ `status` tinyint(2) NULL DEFAULT NULL COMMENT '状态 0停用 1启用',
+ `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间',
+ `isDel` tinyint(2) NULL DEFAULT 0 COMMENT '是否删除',
+ `delTime` int(12) NULL DEFAULT NULL COMMENT '删除时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '敏感词管理' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_kf_to_do
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_kf_to_do`;
+CREATE TABLE `ck_kf_to_do` (
+ `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id',
+ `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID',
+ `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标题',
+ `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述',
+ `friendId` int(12) NULL DEFAULT NULL COMMENT '好友id',
+ `level` tinyint(2) NULL DEFAULT 0 COMMENT '提示等级 0低优先级 1中优先级 2高优先级 3紧急',
+ `reminderTime` int(12) NULL DEFAULT NULL COMMENT '提醒时间',
+ `isRemind` tinyint(2) NULL DEFAULT 0 COMMENT '是否提醒',
+ `isProcess` tinyint(2) NULL DEFAULT 0 COMMENT '是否处理',
+ `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `idx_companyId`(`companyId`) USING BTREE,
+ INDEX `idx_userId`(`userId`) USING BTREE,
+ INDEX `idx_level`(`level`) USING BTREE,
+ INDEX `idx_isRemind`(`isRemind`) USING BTREE,
+ INDEX `idx_isProcess`(`isProcess`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '待办事项' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_menus
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_menus`;
+CREATE TABLE `ck_menus` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
+ `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单名称',
+ `path` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '路由路径',
+ `icon` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '图标名称',
+ `parentId` int(11) NOT NULL DEFAULT 0 COMMENT '父菜单ID,0表示顶级菜单',
+ `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态:1启用,0禁用',
+ `sort` int(11) NOT NULL DEFAULT 0 COMMENT '排序,数值越小越靠前',
+ `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `idx_parent_id`(`parentId`) USING BTREE,
+ INDEX `idx_status`(`status`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统菜单表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_order
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_order`;
+CREATE TABLE `ck_order` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `mchId` int(11) NULL DEFAULT NULL COMMENT '门店号',
+ `companyId` int(11) UNSIGNED NOT NULL,
+ `userId` int(11) NULL DEFAULT NULL,
+ `orderType` tinyint(2) NULL DEFAULT NULL COMMENT '订单类型 1购买算力',
+ `status` tinyint(1) UNSIGNED NULL DEFAULT 0 COMMENT '支付状态 0待支付 1已付款 2已退款 3付款失败',
+ `goodsId` int(11) NULL DEFAULT 0 COMMENT '商品id',
+ `goodsName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商品名称',
+ `goodsSpecs` json NULL COMMENT '商品规格',
+ `money` int(11) NULL DEFAULT 0 COMMENT '金额 单位分',
+ `orderNo` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '订单号',
+ `ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `nonceStr` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '随机字符串',
+ `createTime` int(11) NULL DEFAULT NULL,
+ `payType` tinyint(2) NULL DEFAULT NULL COMMENT '支付类型 1微信 2支付宝',
+ `payTime` int(11) NULL DEFAULT NULL,
+ `payInfo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '错误信息',
+ `deleteTime` int(11) UNSIGNED NULL DEFAULT NULL,
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 79 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_plan_scene
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_plan_scene`;
+CREATE TABLE `ck_plan_scene` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
+ `name` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '场景名称',
+ `description` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述',
+ `image` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '图片icon',
+ `status` tinyint(3) NULL DEFAULT NULL COMMENT '状态',
+ `sort` tinyint(3) NULL DEFAULT NULL,
+ `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(11) NULL DEFAULT NULL COMMENT '修改时间',
+ `deleteTime` int(11) NULL DEFAULT 0 COMMENT '删除时间',
+ `scenarioTags` json NULL COMMENT '标签',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '获客场景' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_plan_tags
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_plan_tags`;
+CREATE TABLE `ck_plan_tags` (
+ `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
+ `tagName` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标签名',
+ `companyId` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '部门ID',
+ `createTime` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '创建时间',
+ `deleteTime` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量标签表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_task_customer
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_task_customer`;
+CREATE TABLE `ck_task_customer` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `task_id` int(11) NOT NULL,
+ `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '客户姓名',
+ `source` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '来源',
+ `phone` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
+ `remark` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
+ `tags` json NULL,
+ `siteTags` json NULL COMMENT '站内标签',
+ `processed_wechat_ids` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
+ `status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '0-未处理,1-已处理/添加中,2-~~添加成功~~ ~~已添加~~添加任务成功 3-添加失败 4-已通过-已发消息',
+ `fail_reason` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
+ `addTime` int(11) NOT NULL DEFAULT 0 COMMENT '添加时间',
+ `passTime` int(11) NOT NULL DEFAULT 0 COMMENT '通过时间',
+ `createTime` int(11) NOT NULL DEFAULT 0,
+ `updateTime` int(11) NOT NULL DEFAULT 0,
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `task_id`(`task_id`) USING BTREE,
+ INDEX `addTime`(`addTime`) USING BTREE,
+ INDEX `passTime`(`passTime`) USING BTREE,
+ INDEX `updateTime`(`updateTime`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 24192 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_tokens_company
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_tokens_company`;
+CREATE TABLE `ck_tokens_company` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id',
+ `tokens` bigint(100) NULL DEFAULT NULL,
+ `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '公司算力账户' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_tokens_package
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_tokens_package`;
+CREATE TABLE `ck_tokens_package` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称',
+ `tokens` int(12) NULL DEFAULT NULL,
+ `price` int(12) NULL DEFAULT NULL COMMENT '售价 单位分',
+ `originalPrice` int(12) NULL DEFAULT NULL COMMENT '原价 单位分',
+ `description` json NULL COMMENT '描述',
+ `sort` int(12) NULL DEFAULT 50 COMMENT '排序',
+ `isTrial` tinyint(2) NULL DEFAULT 0 COMMENT '是否试用',
+ `isRecommend` tinyint(2) NULL DEFAULT 0 COMMENT '是否推荐',
+ `isHot` tinyint(2) NULL DEFAULT 0 COMMENT '是否热门',
+ `isVip` tinyint(2) NULL DEFAULT 0 COMMENT '是否VIP',
+ `status` tinyint(2) NULL DEFAULT 0 COMMENT '状态 0停用 1启用',
+ `isDel` tinyint(2) NULL DEFAULT 0 COMMENT '是否删除',
+ `delTime` int(12) NULL DEFAULT NULL COMMENT '删除时间',
+ `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'token套餐' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_tokens_record
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_tokens_record`;
+CREATE TABLE `ck_tokens_record` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id',
+ `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID',
+ `wechatAccountId` int(11) NULL DEFAULT NULL COMMENT '客服id',
+ `friendIdOrGroupId` int(11) NULL DEFAULT NULL COMMENT '好友id或者群id',
+ `form` tinyint(2) NULL DEFAULT 0 COMMENT '来源 0未知 1好友聊天 2群聊天 3群公告 4商家 5充值',
+ `type` tinyint(2) NULL DEFAULT 0 COMMENT '类型 0减少 1增加',
+ `tokens` int(11) NULL DEFAULT NULL COMMENT '消耗tokens',
+ `balanceTokens` int(11) NULL DEFAULT NULL COMMENT '剩余tokens',
+ `remarks` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
+ `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 236 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '算力明细记录' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_traffic_order
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_traffic_order`;
+CREATE TABLE `ck_traffic_order` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `companyId` int(10) UNSIGNED NULL DEFAULT NULL,
+ `identifier` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '流量池用户',
+ `createTime` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '创建时间',
+ `isDel` tinyint(2) NULL DEFAULT 0,
+ `deleteTime` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间',
+ `orderno` varchar(0) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '订单编号',
+ `userId` int(11) NULL DEFAULT NULL,
+ `storeId` int(11) NULL DEFAULT NULL COMMENT '门店id',
+ `goddsName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商品价格',
+ `price` int(10) NULL DEFAULT NULL COMMENT '商品价格',
+ `actualPay` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '实际支付',
+ `ownerWechatId` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_traffic_pool
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_traffic_pool`;
+CREATE TABLE `ck_traffic_pool` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
+ `identifier` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '流量标识,可以是手机号、微信号',
+ `mobile` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号',
+ `wechatId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信ID',
+ `createTime` int(10) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(10) NULL DEFAULT NULL COMMENT '修改时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE INDEX `uni_identifier`(`identifier`) USING BTREE,
+ INDEX `idx_wechatId`(`wechatId`) USING BTREE,
+ INDEX `idx_mobile`(`mobile`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 959687 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量池' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_traffic_profile
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_traffic_profile`;
+CREATE TABLE `ck_traffic_profile` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
+ `identifier` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '流量标识,可以是手机号、微信号',
+ `nickname` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '平台昵称',
+ `avatar` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '平台头像',
+ `gender` tinyint(3) NULL DEFAULT 0 COMMENT '平台性别',
+ `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '平台手机号',
+ `platformId` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '平台Id',
+ `createTime` int(10) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(10) NULL DEFAULT NULL COMMENT '修改时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE INDEX `uni_identifier`(`identifier`) USING BTREE,
+ INDEX `idx_mobile`(`phone`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 196606 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量池用户个人信息' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_traffic_source
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_traffic_source`;
+CREATE TABLE `ck_traffic_source` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
+ `type` tinyint(2) NULL DEFAULT 1 COMMENT '流量来源 0其他 1好友 2群 3场景',
+ `identifier` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '流量标识',
+ `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称',
+ `status` tinyint(3) NULL DEFAULT 1 COMMENT '1待处理,2处理中,3已通过,4已拒绝,5已过期,6已取消 -3已删除(同步 tk_friend_task 表的 status)',
+ `sourceId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '来源id(微信id或群id)',
+ `fromd` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '流量来源(群聊名称)',
+ `sceneId` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '场景ID',
+ `companyId` int(11) NULL DEFAULT 0 COMMENT '账号所属项目id',
+ `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(11) NULL DEFAULT NULL COMMENT '修改时间',
+ `R` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0',
+ `F` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0',
+ `M` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0',
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE INDEX `uk_identifier_sourceId_sceneId`(`identifier`, `sourceId`, `sceneId`) USING BTREE,
+ INDEX `idx_identifier`(`identifier`) USING BTREE,
+ INDEX `idx_companyId`(`companyId`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 564508 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量来源' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_traffic_source_package
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_traffic_source_package`;
+CREATE TABLE `ck_traffic_source_package` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `userId` int(10) NULL DEFAULT NULL COMMENT '用户id',
+ `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称',
+ `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述',
+ `pic` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '图标',
+ `companyId` int(11) NULL DEFAULT NULL COMMENT '账号所属项目id',
+ `matchingRules` json NULL COMMENT '匹配规则',
+ `isSys` tinyint(2) NULL DEFAULT 0 COMMENT '是否系统只有',
+ `isDel` tinyint(2) NULL DEFAULT 0 COMMENT '是否删除',
+ `updateTime` int(11) NULL DEFAULT NULL,
+ `createTime` int(11) NULL DEFAULT 0 COMMENT '创建时间',
+ `deleteTime` int(11) NULL DEFAULT 0 COMMENT '删除时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `companyId`(`companyId`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量池包' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_traffic_source_package_item
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_traffic_source_package_item`;
+CREATE TABLE `ck_traffic_source_package_item` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `packageId` int(10) NULL DEFAULT NULL COMMENT '流量包id',
+ `companyId` int(11) NULL DEFAULT NULL COMMENT '账号所属项目id',
+ `identifier` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '流量标识,可以是手机号、微信号',
+ `isDel` tinyint(2) NULL DEFAULT 0 COMMENT '是否删除',
+ `createTime` int(11) NULL DEFAULT 0 COMMENT '创建时间',
+ `deleteTime` int(10) NULL DEFAULT NULL COMMENT '创建时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE INDEX `uk_packageId_companyId_identifier_isDel`(`packageId`, `companyId`, `identifier`, `isDel`) USING BTREE,
+ INDEX `packageId`(`packageId`) USING BTREE,
+ INDEX `companyId`(`companyId`) USING BTREE,
+ INDEX `identifier`(`identifier`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 34 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_traffic_tag
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_traffic_tag`;
+CREATE TABLE `ck_traffic_tag` (
+ `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
+ `tagId` int(11) NULL DEFAULT NULL,
+ `tagName` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标签名',
+ `tagType` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标签值',
+ `tagValue` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标签值',
+ `companyId` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '部门ID',
+ `trafficPoolId` int(10) NULL DEFAULT NULL COMMENT '流量池用户id traffic_pool的主键',
+ `createTime` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '创建时间',
+ `isDel` tinyint(2) NULL DEFAULT NULL,
+ `deleteTime` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量标签表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_user_flow_package
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_user_flow_package`;
+CREATE TABLE `ck_user_flow_package` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `userId` int(11) NOT NULL DEFAULT 0 COMMENT '用户ID',
+ `packageId` int(11) NOT NULL DEFAULT 0 COMMENT '套餐ID',
+ `orderId` int(11) NOT NULL DEFAULT 0 COMMENT '关联订单ID',
+ `duration` int(11) NOT NULL DEFAULT 1 COMMENT '套餐时长(月)',
+ `totalFlow` int(11) NOT NULL DEFAULT 0 COMMENT '总流量(人)',
+ `usedFlow` int(11) NOT NULL DEFAULT 0 COMMENT '已使用流量(人)',
+ `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态: 0=无效, 1=有效',
+ `startTime` int(11) NOT NULL DEFAULT 0 COMMENT '开始时间',
+ `expireTime` int(11) NOT NULL DEFAULT 0 COMMENT '到期时间',
+ `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间',
+ `updateTime` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `idx_user_id`(`userId`) USING BTREE,
+ INDEX `idx_package_id`(`packageId`) USING BTREE,
+ INDEX `idx_order_id`(`orderId`) USING BTREE,
+ INDEX `idx_expire_time`(`expireTime`) USING BTREE,
+ INDEX `idx_status`(`status`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户流量套餐表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_user_log
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_user_log`;
+CREATE TABLE `ck_user_log` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `userId` int(11) NOT NULL DEFAULT 0 COMMENT '用户ID',
+ `userName` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名',
+ `action` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '操作类型',
+ `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '操作描述',
+ `ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT 'IP地址',
+ `userAgent` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户设备信息',
+ `requestMethod` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求方法',
+ `requestUrl` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求URL',
+ `requestData` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '请求数据',
+ `responseCode` int(11) NULL DEFAULT 0 COMMENT '响应状态码',
+ `responseMsg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '响应消息',
+ `createTime` int(11) NULL DEFAULT 0 COMMENT '创建时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `idx_user_id`(`userId`) USING BTREE,
+ INDEX `idx_user_name`(`userName`) USING BTREE,
+ INDEX `idx_action`(`action`) USING BTREE,
+ INDEX `idx_create_time`(`createTime`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 45 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户操作日志表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_user_portrait
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_user_portrait`;
+CREATE TABLE `ck_user_portrait` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `type` tinyint(2) NULL DEFAULT 0 COMMENT '类型 0浏览 1点击 2下单/购买 3注册 4互动',
+ `companyId` int(11) NULL DEFAULT 0,
+ `trafficPoolId` int(10) NULL DEFAULT NULL COMMENT '流量池用户id traffic_pool的主键',
+ `source` tinyint(2) NULL DEFAULT 0 COMMENT '来源 0本站 1老油条 2老坑爹',
+ `uniqueId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '来源网站唯一id',
+ `sourceData` json NULL COMMENT '来源网站数据',
+ `remark` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
+ `count` int(10) NULL DEFAULT 1 COMMENT '统计次数(半小时内)',
+ `createTime` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(11) NULL DEFAULT NULL COMMENT '修改时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 17718 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户画像' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_users
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_users`;
+CREATE TABLE `ck_users` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `account` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号',
+ `username` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '昵称',
+ `phone` char(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '登录手机号',
+ `passwordMd5` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码',
+ `passwordLocal` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '本地密码',
+ `avatar` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'https://img.icons8.com/color/512/circled-user-male-skin-type-7.png' COMMENT '头像',
+ `isAdmin` tinyint(3) NULL DEFAULT 0 COMMENT '是否管理身份 1->是 0->否',
+ `companyId` int(10) UNSIGNED NOT NULL COMMENT '账号所属项目id',
+ `typeId` tinyint(3) NOT NULL DEFAULT -1 COMMENT '类型:运营后台/操盘手 传1 、 门店传2',
+ `status` tinyint(3) NULL DEFAULT 0 COMMENT '1->可用,0->禁用',
+ `s2_accountId` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'S2的用户账号id',
+ `balance` int(11) NULL DEFAULT 0 COMMENT '余额',
+ `tokens` int(11) NULL DEFAULT 0 COMMENT '算力余额',
+ `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间',
+ `updateTime` int(11) NULL DEFAULT NULL COMMENT '修改时间',
+ `deleteTime` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1652 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_vendor_order
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_vendor_order`;
+CREATE TABLE `ck_vendor_order` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '订单ID',
+ `orderNo` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '订单编号',
+ `userId` int(10) UNSIGNED NOT NULL COMMENT '用户ID',
+ `packageId` int(10) UNSIGNED NOT NULL COMMENT '套餐ID',
+ `packageName` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '套餐名称',
+ `totalAmount` decimal(10, 2) NOT NULL COMMENT '订单总额',
+ `payAmount` decimal(10, 2) NOT NULL COMMENT '支付金额',
+ `advancePayment` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '预付款',
+ `status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '状态:0=待支付,1=已支付,2=已完成,3=已取消',
+ `payTime` int(11) NULL DEFAULT 0 COMMENT '支付时间',
+ `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
+ `createTime` int(11) NOT NULL COMMENT '创建时间',
+ `updateTime` int(11) NOT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE INDEX `orderNo`(`orderNo`) USING BTREE,
+ INDEX `userId`(`userId`) USING BTREE,
+ INDEX `packageId`(`packageId`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '供应商订单表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_vendor_package
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_vendor_package`;
+CREATE TABLE `ck_vendor_package` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '套餐ID',
+ `userId` int(11) NULL DEFAULT NULL COMMENT '用户id',
+ `companyId` int(11) NULL DEFAULT NULL COMMENT '公司id',
+ `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '套餐名称',
+ `originalPrice` decimal(10, 2) NOT NULL COMMENT '原价',
+ `price` decimal(10, 2) NOT NULL COMMENT '售价',
+ `discount` decimal(4, 2) NULL DEFAULT 0.00 COMMENT '折扣',
+ `advancePayment` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '预付款',
+ `tags` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标签',
+ `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '套餐描述',
+ `cover` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '封面图片',
+ `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态:0=下架,1=上架',
+ `createTime` int(11) NOT NULL COMMENT '创建时间',
+ `updateTime` int(11) NOT NULL COMMENT '更新时间',
+ `isDel` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '供应商套餐表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_vendor_project
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_vendor_project`;
+CREATE TABLE `ck_vendor_project` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '项目ID',
+ `packageId` int(10) UNSIGNED NOT NULL COMMENT '套餐ID',
+ `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '项目名称',
+ `originalPrice` decimal(10, 2) NOT NULL COMMENT '原价',
+ `price` decimal(10, 2) NOT NULL COMMENT '售价',
+ `duration` int(11) NULL DEFAULT 0 COMMENT '项目时长(分钟)',
+ `image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '项目图片',
+ `detail` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '项目详情',
+ `createTime` int(11) NOT NULL COMMENT '创建时间',
+ `updateTime` int(11) NOT NULL COMMENT '更新时间',
+ `isDel` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `packageId`(`packageId`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '供应商套餐项目表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_wechat_account
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_wechat_account`;
+CREATE TABLE `ck_wechat_account` (
+ `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
+ `s2_wechatAccountId` int(11) NULL DEFAULT NULL COMMENT '微信账号id',
+ `wechatId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信ID',
+ `alias` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信号',
+ `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '昵称',
+ `pyInitial` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '拼音首字母',
+ `quanPin` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '全拼',
+ `avatar` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像URL',
+ `gender` tinyint(1) NULL DEFAULT 0 COMMENT '性别 0->保密;1->男;2->女',
+ `region` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '地区',
+ `signature` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '个性签名',
+ `phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '电话',
+ `country` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '国家',
+ `privince` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '省份',
+ `city` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '城市',
+ `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE INDEX `uni_wechatId`(`wechatId`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 3097959 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信账号表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_wechat_customer
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_wechat_customer`;
+CREATE TABLE `ck_wechat_customer` (
+ `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增id',
+ `wechatId` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信id',
+ `basic` json NULL COMMENT '保存基础信息',
+ `weight` json NULL COMMENT '保存权重信息',
+ `activity` json NULL COMMENT '保存账号活跃信息',
+ `friendShip` json NULL COMMENT '保存朋友关系信息',
+ `companyId` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '公司id',
+ `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE INDEX `uni_wechatId`(`wechatId`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 153 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信客服信息' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_wechat_friendship
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_wechat_friendship`;
+CREATE TABLE `ck_wechat_friendship` (
+ `id` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '好友id',
+ `wechatId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '微信ID',
+ `tags` json NULL COMMENT '好友标签',
+ `memo` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '好友备注',
+ `ownerWechatId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '所有者微信ID',
+ `companyId` int(11) NULL DEFAULT NULL COMMENT '公司ID',
+ `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
+ `deleteTime` int(11) NULL DEFAULT NULL COMMENT '删除时间',
+ UNIQUE INDEX `uk_owner_wechat_account`(`ownerWechatId`, `wechatId`) USING BTREE,
+ INDEX `idx_wechat_id`(`wechatId`) USING BTREE,
+ INDEX `idx_owner_wechat_id`(`ownerWechatId`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信好友表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_wechat_group
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_wechat_group`;
+CREATE TABLE `ck_wechat_group` (
+ `id` int(11) UNSIGNED NOT NULL COMMENT 'S2微信群id',
+ `wechatAccountId` int(11) NULL DEFAULT NULL COMMENT '微信账号ID',
+ `chatroomId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信群聊id',
+ `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群名称',
+ `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群头像',
+ `companyId` int(11) NULL DEFAULT NULL COMMENT '项目id',
+ `ownerWechatId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '所有者微信ID',
+ `identifier` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群主(流量标识,可以是手机号、微信号)',
+ `createTime` int(11) UNSIGNED NULL DEFAULT NULL,
+ `updateTime` int(11) UNSIGNED NULL DEFAULT NULL,
+ `deleteTime` int(11) UNSIGNED NULL DEFAULT NULL,
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE INDEX `uk_owner_chatroomId`(`chatroomId`, `ownerWechatId`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信群' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_wechat_group_member
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_wechat_group_member`;
+CREATE TABLE `ck_wechat_group_member` (
+ `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
+ `identifier` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群成员(流量标识,可以是手机号、微信号)',
+ `chatroomId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群真实id',
+ `customerIs` tinyint(3) NULL DEFAULT 0 COMMENT '是否客服',
+ `companyId` int(11) NULL DEFAULT NULL COMMENT '项目id',
+ `groupId` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '所属群ID',
+ `createTime` int(11) UNSIGNED NULL DEFAULT 0,
+ `deleteTime` int(11) UNSIGNED NULL DEFAULT 0,
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE INDEX `uk_identifier_chatroomId_groupId`(`identifier`, `chatroomId`, `groupId`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 549847 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信群成员' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_wechat_restricts
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_wechat_restricts`;
+CREATE TABLE `ck_wechat_restricts` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `taskId` int(11) NULL DEFAULT NULL COMMENT '任务id',
+ `level` tinyint(3) UNSIGNED NULL DEFAULT 1 COMMENT '风险类型 1 普通 2 警告 3 错误',
+ `reason` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '风险原因',
+ `memo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '记录更详细的风险信息',
+ `wechatId` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信id',
+ `companyId` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '项目id',
+ `restrictTime` int(11) NULL DEFAULT NULL COMMENT '限制日期',
+ `recoveryTime` int(11) NULL DEFAULT NULL COMMENT '恢复日期',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1302 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信风险受限记录' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_wechat_tag
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_wechat_tag`;
+CREATE TABLE `ck_wechat_tag` (
+ `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
+ `tags` json NULL COMMENT '标签JSON',
+ `wechatId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '微信ID',
+ `companyId` int(11) NULL DEFAULT NULL COMMENT '公司ID',
+ `createTime` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `idx_wechatId`(`wechatId`) USING BTREE,
+ INDEX `idx_companyId`(`companyId`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 123366 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信账号表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_workbench
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_workbench`;
+CREATE TABLE `ck_workbench` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `userId` int(11) NOT NULL COMMENT '创建用户ID',
+ `companyId` int(11) NULL DEFAULT 0 COMMENT '公司id',
+ `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '工作台名称',
+ `type` tinyint(1) NOT NULL DEFAULT 1 COMMENT '工作台类型:1=自动点赞,2=朋友圈同步,3=群消息推送,4=自动建群,5=流量分发,6=通讯录导入',
+ `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态:0=禁用,1=启用',
+ `autoStart` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否自动启动:0=否,1=是',
+ `createTime` int(11) NOT NULL COMMENT '创建时间',
+ `updateTime` int(11) NOT NULL COMMENT '更新时间',
+ `isDel` tinyint(1) NULL DEFAULT 0,
+ `deleteTime` int(11) NULL DEFAULT NULL COMMENT '删除时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `idx_user_id`(`userId`) USING BTREE,
+ INDEX `idx_type`(`type`) USING BTREE,
+ INDEX `idx_status`(`status`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 275 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '工作台主表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_workbench_auto_like
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_workbench_auto_like`;
+CREATE TABLE `ck_workbench_auto_like` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `workbenchId` int(11) NOT NULL COMMENT '工作台ID',
+ `interval` int(11) NOT NULL DEFAULT 60 COMMENT '点赞间隔(秒)',
+ `maxLikes` int(11) NOT NULL DEFAULT 100 COMMENT '最大点赞数',
+ `startTime` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '00:00:00' COMMENT '开始时间',
+ `endTime` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '23:59:59' COMMENT '结束时间',
+ `contentTypes` json NULL COMMENT '内容类型',
+ `devices` json NULL COMMENT '设备列表,JSON格式:[{\"id\":1,\"name\":\"设备1\"},{\"id\":2,\"name\":\"设备2\"}]',
+ `friends` json NULL COMMENT '用户列表',
+ `createTime` int(11) NOT NULL COMMENT '创建时间',
+ `updateTime` int(11) NOT NULL COMMENT '更新时间',
+ `targetGroups` json NULL COMMENT '目标用户组列表,JSON格式:[{\"id\":1,\"name\":\"用户组1\"},{\"id\":2,\"name\":\"用户组2\"}] 废除',
+ `tagOperator` tinyint(1) NULL DEFAULT 2 COMMENT '标签匹配规则 1:and 2:or 废除',
+ `friendMaxLikes` int(10) NULL DEFAULT NULL COMMENT '好友最大点赞数',
+ `friendTags` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '好友标签',
+ `enableFriendTags` tinyint(1) NULL DEFAULT 0 COMMENT '启用好友标签',
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE INDEX `uk_workbench_id`(`workbenchId`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 54 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '自动点赞配置表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_workbench_auto_like_item
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_workbench_auto_like_item`;
+CREATE TABLE `ck_workbench_auto_like_item` (
+ `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `workbenchId` int(11) NOT NULL COMMENT '工作台ID',
+ `deviceId` int(11) NULL DEFAULT 0 COMMENT '设备id',
+ `snsId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '自动点赞id',
+ `wechatFriendId` int(11) NULL DEFAULT NULL COMMENT '好友id',
+ `wechatAccountId` int(11) NULL DEFAULT NULL COMMENT '客服id',
+ `momentsId` int(11) NULL DEFAULT NULL COMMENT '朋友圈id',
+ `createTime` int(11) NOT NULL COMMENT '创建时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `workbenchId`(`workbenchId`) USING BTREE,
+ INDEX `wechatFriendId`(`wechatFriendId`) USING BTREE,
+ INDEX `wechatAccountId`(`wechatAccountId`) USING BTREE,
+ INDEX `momentsId`(`momentsId`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 4639 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '工作台-自动点赞记录' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_workbench_group_create
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_workbench_group_create`;
+CREATE TABLE `ck_workbench_group_create` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `workbenchId` int(11) NOT NULL COMMENT '计划ID',
+ `devices` json NULL COMMENT '目标设备/客服(JSON数组)',
+ `poolGroups` json NULL COMMENT '流量池JSON',
+ `wechatGroups` json NULL COMMENT '微信客服JSON',
+ `startTime` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '开始时间',
+ `endTime` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '结束时间',
+ `groupSizeMin` int(10) NULL DEFAULT NULL COMMENT '群好友最小人数',
+ `groupSizeMax` int(10) NULL DEFAULT NULL COMMENT '群好友最大人数',
+ `maxGroupsPerDay` int(10) NULL DEFAULT NULL COMMENT '每日建群最大数量',
+ `groupNameTemplate` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群模板信息',
+ `groupDescription` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群描述',
+ `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 26 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_workbench_group_create_item
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_workbench_group_create_item`;
+CREATE TABLE `ck_workbench_group_create_item` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `workbenchId` int(11) NOT NULL COMMENT '工作台ID',
+ `friendId` int(11) NULL DEFAULT NULL,
+ `wechatId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '微信id',
+ `groupId` int(10) NULL DEFAULT NULL COMMENT '群id',
+ `wechatAccountId` int(11) NULL DEFAULT NULL COMMENT '客服id',
+ `createTime` int(11) NOT NULL COMMENT '创建时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 46 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_workbench_group_push
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_workbench_group_push`;
+CREATE TABLE `ck_workbench_group_push` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `workbenchId` int(11) NOT NULL COMMENT '工作台ID',
+ `pushType` tinyint(1) NOT NULL DEFAULT 1 COMMENT '推送方式 0定时 1立即',
+ `targetType` tinyint(1) NOT NULL DEFAULT 1 COMMENT '推送目标类型:1=群推送,2=好友推送',
+ `startTime` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '推送开始时间',
+ `endTime` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '推送结束时间',
+ `maxPerDay` int(11) NULL DEFAULT 0 COMMENT '每日推送条数',
+ `pushOrder` tinyint(1) NULL DEFAULT 1 COMMENT '推送顺序 1最早 2最新',
+ `isLoop` tinyint(1) NULL DEFAULT 0 COMMENT '是否循环推送 0否 1是',
+ `status` tinyint(1) NULL DEFAULT 1 COMMENT '是否启用 0否 1是',
+ `groups` json NULL COMMENT '推送微信群组(JSON)',
+ `friends` json NULL COMMENT '推送好友列表(JSON)',
+ `ownerWechatIds` json NULL COMMENT '所属微信id',
+ `contentLibraries` json NULL COMMENT '内容库(JSON)',
+ `friendIntervalMin` int(11) NOT NULL DEFAULT 10 COMMENT '好友间最小间隔时间(秒)',
+ `friendIntervalMax` int(11) NOT NULL DEFAULT 20 COMMENT '好友间最大间隔时间(秒)',
+ `messageIntervalMin` int(11) NOT NULL DEFAULT 1 COMMENT '消息间最小间隔时间(秒)',
+ `messageIntervalMax` int(11) NOT NULL DEFAULT 12 COMMENT '消息间最大间隔时间(秒)',
+ `isRandomTemplate` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否随机选择话术组(0=否,1=是)',
+ `postPushTags` json NOT NULL COMMENT '推送完成后打标签',
+ `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
+ `socialMediaId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '京东导购媒体',
+ `promotionSiteId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '京东广告位',
+ `trafficPools` json NULL COMMENT '流量池',
+ `devices` json NULL,
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `idx_workbench_id`(`workbenchId`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 32 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群消息推送扩展表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_workbench_group_push_item
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_workbench_group_push_item`;
+CREATE TABLE `ck_workbench_group_push_item` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `workbenchId` int(11) NOT NULL COMMENT '工作台ID',
+ `targetType` tinyint(1) NOT NULL DEFAULT 1 COMMENT '推送目标类型:1=群,2=好友',
+ `contentId` int(11) NULL DEFAULT 0 COMMENT '内容库is',
+ `groupId` int(10) NULL DEFAULT NULL COMMENT '群id',
+ `friendId` int(11) NULL DEFAULT NULL COMMENT '好友ID(当targetType=2时使用)',
+ `wechatAccountId` int(11) NULL DEFAULT NULL COMMENT '客服id',
+ `isLoop` tinyint(2) NULL DEFAULT 0 COMMENT '是否循环完成',
+ `createTime` int(11) NOT NULL COMMENT '创建时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 302 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_workbench_import_contact
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_workbench_import_contact`;
+CREATE TABLE `ck_workbench_import_contact` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `workbenchId` int(11) NOT NULL COMMENT '工作台ID',
+ `devices` json NULL COMMENT '设备id',
+ `pools` json NULL COMMENT '流量池',
+ `num` int(11) NULL DEFAULT NULL COMMENT '分配数量',
+ `clearContact` tinyint(2) NULL DEFAULT 0 COMMENT '是否清除现有联系人',
+ `remarkType` tinyint(2) NOT NULL DEFAULT 0 COMMENT '备注类型 0不备注 1年月日 2月日 3自定义',
+ `remark` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
+ `startTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '开始时间',
+ `endTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '结束时间',
+ `createTime` int(11) NOT NULL COMMENT '创建时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_workbench_import_contact_item
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_workbench_import_contact_item`;
+CREATE TABLE `ck_workbench_import_contact_item` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `workbenchId` int(11) NOT NULL COMMENT '工作台ID',
+ `deviceId` int(11) NULL DEFAULT NULL COMMENT '设备id',
+ `packageId` int(11) NULL DEFAULT 0 COMMENT '流量包id',
+ `poolId` int(11) NULL DEFAULT NULL COMMENT '流量id',
+ `createTime` int(11) NOT NULL COMMENT '创建时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 140 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_workbench_moments_sync
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_workbench_moments_sync`;
+CREATE TABLE `ck_workbench_moments_sync` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `workbenchId` int(11) NOT NULL COMMENT '工作台ID',
+ `syncInterval` int(11) NOT NULL DEFAULT 1 COMMENT '同步间隔(小时)',
+ `syncCount` int(11) NOT NULL DEFAULT 5 COMMENT '每日同步数量',
+ `syncType` tinyint(1) NOT NULL DEFAULT 1 COMMENT '同步类型:1=文本,2=图片,3=视频,4=链接',
+ `startTime` time(0) NULL DEFAULT '06:00:00' COMMENT '发布开始时间',
+ `endTime` time(0) NULL DEFAULT '23:59:00' COMMENT '发布结束时间',
+ `accountType` tinyint(1) NOT NULL DEFAULT 1 COMMENT '账号类型:1=业务号,2=个人号',
+ `devices` json NOT NULL COMMENT '设备列表,JSON格式',
+ `contentLibraries` json NULL COMMENT '内容库ID列表,JSON格式',
+ `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `idx_workbench_id`(`workbenchId`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 47 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '朋友圈同步配置' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_workbench_moments_sync_item
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_workbench_moments_sync_item`;
+CREATE TABLE `ck_workbench_moments_sync_item` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `workbenchId` int(11) NOT NULL COMMENT '工作台ID',
+ `deviceId` int(11) NULL DEFAULT 0 COMMENT '设备id',
+ `contentId` int(10) NULL DEFAULT NULL COMMENT '内容库id',
+ `wechatAccountId` int(11) NULL DEFAULT NULL COMMENT '客服id',
+ `createTime` int(11) NOT NULL COMMENT '创建时间',
+ `isLoop` tinyint(2) NULL DEFAULT 0,
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1650 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '朋友圈同步配置' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_workbench_traffic_config
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_workbench_traffic_config`;
+CREATE TABLE `ck_workbench_traffic_config` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `workbenchId` int(11) NOT NULL COMMENT '流量分发计划ID',
+ `distributeType` tinyint(1) NOT NULL DEFAULT 1 COMMENT '分配方式 1均分 2优先级 3比例',
+ `maxPerDay` int(11) NOT NULL DEFAULT 0 COMMENT '每日最大分配量',
+ `timeType` tinyint(1) NOT NULL DEFAULT 1 COMMENT '时间限制 1全天 2自定义',
+ `startTime` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '开始时间',
+ `endTime` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '结束时间',
+ `account` json NULL COMMENT '分发的账号',
+ `devices` json NULL COMMENT '目标设备/客服(JSON数组)',
+ `pools` json NULL COMMENT '流量池(JSON数组)',
+ `exp` int(10) NULL DEFAULT 30 COMMENT '有效期 单位天',
+ `createTime` int(11) NOT NULL,
+ `updateTime` int(11) NOT NULL,
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE INDEX `uniq_workbench`(`workbenchId`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 31 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量分发计划扩展表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for ck_workbench_traffic_config_item
+-- ----------------------------
+DROP TABLE IF EXISTS `ck_workbench_traffic_config_item`;
+CREATE TABLE `ck_workbench_traffic_config_item` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `workbenchId` int(11) NOT NULL DEFAULT 0 COMMENT '工作台ID',
+ `deviceId` int(11) NULL DEFAULT 0 COMMENT '设备id',
+ `wechatFriendId` int(10) NULL DEFAULT NULL COMMENT '好友id',
+ `wechatAccountId` int(11) NULL DEFAULT 0 COMMENT '客服id',
+ `expTime` int(11) NULL DEFAULT 0 COMMENT '有效时间',
+ `exp` int(11) NULL DEFAULT 0 COMMENT '有效时间 天',
+ `isRecycle` tinyint(2) NULL DEFAULT 0 COMMENT '是否回收',
+ `recycleTime` int(11) NULL DEFAULT 0 COMMENT '回收时间',
+ `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `workbenchId`(`workbenchId`) USING BTREE,
+ INDEX `deviceId`(`deviceId`) USING BTREE,
+ INDEX `wechatFriendId`(`wechatFriendId`) USING BTREE,
+ INDEX `wechatAccountId`(`wechatAccountId`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 49898 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量分发计划扩展表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for s2_allot_rule
+-- ----------------------------
+DROP TABLE IF EXISTS `s2_allot_rule`;
+CREATE TABLE `s2_allot_rule` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '规则ID',
+ `departmentId` int(11) NULL DEFAULT 0 COMMENT '部门id',
+ `tenantId` int(11) NOT NULL DEFAULT 0 COMMENT '租户ID',
+ `allotType` tinyint(4) NOT NULL DEFAULT 0 COMMENT '分配类型',
+ `allotOnline` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否在线分配',
+ `kefuRange` tinyint(4) NOT NULL DEFAULT 0 COMMENT '客服范围',
+ `wechatRange` tinyint(4) NOT NULL DEFAULT 0 COMMENT '微信范围',
+ `kefuData` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '客服数据JSON',
+ `wechatData` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '微信ID列表JSON',
+ `labels` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '标签JSON',
+ `priorityStrategy` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '优先级策略JSON',
+ `sortIndex` int(11) NOT NULL DEFAULT 0 COMMENT '排序索引',
+ `creatorAccountId` int(11) NOT NULL DEFAULT 0 COMMENT '创建者账号ID',
+ `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间',
+ `ruleName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '规则名称',
+ `isDel` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `idx_tenant`(`tenantId`) USING BTREE,
+ INDEX `idx_sort`(`sortIndex`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 2176 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '分配规则表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for s2_call_recording
+-- ----------------------------
+DROP TABLE IF EXISTS `s2_call_recording`;
+CREATE TABLE `s2_call_recording` (
+ `id` bigint(20) NOT NULL COMMENT '主键ID',
+ `tenantId` bigint(20) NOT NULL DEFAULT 0 COMMENT '租户ID',
+ `deviceOwnerId` bigint(20) NOT NULL DEFAULT 0 COMMENT '设备所有者ID',
+ `userName` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名',
+ `nickname` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '昵称',
+ `realName` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '真实姓名',
+ `deviceMemo` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '设备备注',
+ `fileName` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '文件名',
+ `imei` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '设备IMEI',
+ `phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '电话号码',
+ `isCallOut` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否为呼出电话(0:呼入,1:呼出)',
+ `beginTime` int(11) NOT NULL DEFAULT 0 COMMENT '通话开始时间戳',
+ `endTime` int(11) NOT NULL DEFAULT 0 COMMENT '通话结束时间戳',
+ `audioUrl` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '录音文件URL',
+ `mp3AudioUrl` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'MP3录音文件URL',
+ `callBeginTime` int(11) NOT NULL DEFAULT 0 COMMENT '呼叫开始时间戳',
+ `callLogId` bigint(20) NOT NULL DEFAULT 0 COMMENT '通话日志ID',
+ `callType` int(11) NOT NULL DEFAULT 0 COMMENT '通话类型',
+ `duration` int(11) NOT NULL DEFAULT 0 COMMENT '通话时长(秒)',
+ `skipReason` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '跳过原因',
+ `skipUpload` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否跳过上传',
+ `isDeleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否已删除',
+ `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间戳',
+ `lastUpdateTime` int(11) NOT NULL DEFAULT 0 COMMENT '最后更新时间戳',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `idx_tenant_id`(`tenantId`) USING BTREE,
+ INDEX `idx_device_owner_id`(`deviceOwnerId`) USING BTREE,
+ INDEX `idx_user_name`(`userName`) USING BTREE,
+ INDEX `idx_phone`(`phone`) USING BTREE,
+ INDEX `idx_begin_time`(`beginTime`) USING BTREE,
+ INDEX `idx_end_time`(`endTime`) USING BTREE,
+ INDEX `idx_call_begin_time`(`callBeginTime`) USING BTREE,
+ INDEX `idx_imei`(`imei`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '通话记录表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for s2_company_account
+-- ----------------------------
+DROP TABLE IF EXISTS `s2_company_account`;
+CREATE TABLE `s2_company_account` (
+ `id` int(11) NULL DEFAULT NULL COMMENT 'id',
+ `tenantId` int(11) NULL DEFAULT NULL,
+ `userName` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户名',
+ `realName` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '真实姓名',
+ `nickname` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '昵称',
+ `memo` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '备注',
+ `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '头像',
+ `secret` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '密钥',
+ `accountType` int(11) NULL DEFAULT 0 COMMENT '账户类型',
+ `departmentId` int(11) NULL DEFAULT 0 COMMENT '部门ID',
+ `departmentName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '部门名称',
+ `useGoogleSecretKey` tinyint(1) NULL DEFAULT 0 COMMENT '是否使用谷歌密钥',
+ `hasVerifyGoogleSecret` tinyint(1) NULL DEFAULT 0 COMMENT '是否验证谷歌密钥',
+ `passwordMd5` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT 'MD5加密密码',
+ `passwordLocal` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '本地加密密码',
+ `lastLoginIp` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '最后登录IP',
+ `lastLoginTime` int(11) NULL DEFAULT 0 COMMENT '最后登录时间',
+ `createTime` int(11) NULL DEFAULT 0 COMMENT '创建时间',
+ `updateTime` int(11) NULL DEFAULT 0 COMMENT '更新时间',
+ `privilegeIds` json NULL COMMENT '权限',
+ `alive` tinyint(1) NULL DEFAULT NULL,
+ `creator` int(10) NULL DEFAULT NULL COMMENT '创建者',
+ `creatorRealName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建者真实姓名',
+ `creatorUserName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建者用户名',
+ `status` tinyint(1) NULL DEFAULT 0 COMMENT '状态 0正常 1禁用',
+ UNIQUE INDEX `idx_username`(`userName`) USING BTREE,
+ INDEX `idx_create_time`(`createTime`) USING BTREE,
+ INDEX `idx_update_time`(`updateTime`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '公司账户表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for s2_department
+-- ----------------------------
+DROP TABLE IF EXISTS `s2_department`;
+CREATE TABLE `s2_department` (
+ `id` int(11) NOT NULL,
+ `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称',
+ `memo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
+ `tenantId` int(11) NULL DEFAULT NULL,
+ `isTop` tinyint(1) NULL DEFAULT 0,
+ `level` int(10) NULL DEFAULT 0,
+ `parentId` int(10) NULL DEFAULT 0,
+ `privileges` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL,
+ `createTime` int(11) NULL DEFAULT NULL,
+ `lastUpdateTime` int(11) NULL DEFAULT NULL,
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '部门表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for s2_device
+-- ----------------------------
+DROP TABLE IF EXISTS `s2_device`;
+CREATE TABLE `s2_device` (
+ `id` int(11) NULL DEFAULT NULL COMMENT '设备真实ID',
+ `userName` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名',
+ `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '昵称',
+ `realName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '真实姓名',
+ `groupName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '分组名称',
+ `wechatAccounts` json NULL COMMENT '微信账号列表JSON',
+ `alive` tinyint(1) NULL DEFAULT 0 COMMENT '是否在线',
+ `lastAliveTime` int(11) NULL DEFAULT NULL COMMENT '最后在线时间',
+ `tenantId` int(11) NULL DEFAULT NULL COMMENT '租户ID',
+ `groupId` int(11) NULL DEFAULT NULL COMMENT '分组ID',
+ `currentAccountId` int(11) NULL DEFAULT NULL COMMENT '当前账号ID',
+ `imei` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '设备IMEI',
+ `deviceImei` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '设备本地IMEI',
+ `memo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
+ `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间',
+ `isDeleted` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除',
+ `deletedAndStop` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除并停止',
+ `deleteTime` int(11) NULL DEFAULT NULL COMMENT '删除时间',
+ `rooted` tinyint(1) NULL DEFAULT 0 COMMENT '是否root',
+ `xPosed` tinyint(1) NULL DEFAULT 0 COMMENT '是否安装xposed',
+ `brand` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '品牌',
+ `model` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '型号',
+ `operatingSystem` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作系统版本',
+ `softwareVersion` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '软件版本',
+ `extra` json NULL COMMENT '额外信息JSON',
+ `phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号',
+ `lastUpdateTime` int(11) NULL DEFAULT NULL COMMENT '最后更新时间',
+ `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
+ `taskConfig` json NULL COMMENT '自动化任务开关 \r\nautoLike:自动点赞\r\nmomentsSync:朋友圈同步\r\nautoCustomerDev:自动开发客户\r\ngroupMessageDeliver:群消息推送\r\nautoGroup:自动建群',
+ UNIQUE INDEX `uk_imei`(`imei`) USING BTREE,
+ INDEX `idx_tenant`(`tenantId`) USING BTREE,
+ INDEX `idx_group`(`groupId`) USING BTREE,
+ INDEX `idx_current_account`(`currentAccountId`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for s2_device_group
+-- ----------------------------
+DROP TABLE IF EXISTS `s2_device_group`;
+CREATE TABLE `s2_device_group` (
+ `id` int(11) NOT NULL,
+ `tenantId` int(11) NOT NULL COMMENT '租户ID',
+ `groupName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '分组名称',
+ `groupMemo` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '分组备注',
+ `count` int(11) NULL DEFAULT 0 COMMENT '设备数量',
+ `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
+ INDEX `idx_tenant`(`tenantId`) USING BTREE,
+ INDEX `idx_group_name`(`groupName`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备分组表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for s2_friend_task
+-- ----------------------------
+DROP TABLE IF EXISTS `s2_friend_task`;
+CREATE TABLE `s2_friend_task` (
+ `id` int(11) NOT NULL COMMENT '任务ID',
+ `tenantId` int(11) NULL DEFAULT 0 COMMENT '租户ID',
+ `operatorAccountId` int(11) NULL DEFAULT 0 COMMENT '操作账号ID',
+ `status` int(11) NULL DEFAULT 1 COMMENT '状态:0执行中,1执行成功,2执行失败',
+ `phone` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号/微信号',
+ `msgContent` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '验证消息',
+ `wechatAccountId` int(11) NULL DEFAULT 0 COMMENT '微信账号ID',
+ `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间戳',
+ `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
+ `extra` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '额外数据JSON',
+ `labels` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标签,逗号分隔',
+ `from` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '来源',
+ `alias` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信账号别名',
+ `wechatId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信ID',
+ `wechatAvatar` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信头像',
+ `wechatNickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信昵称',
+ `accountNickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号昵称',
+ `accountRealName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号真实姓名',
+ `accountUsername` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号用户名',
+ `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间戳',
+ UNIQUE INDEX `uk_task_id`(`id`) USING BTREE,
+ INDEX `idx_tenant_id`(`tenantId`) USING BTREE,
+ INDEX `idx_operator_account_id`(`operatorAccountId`) USING BTREE,
+ INDEX `idx_wechat_account_id`(`wechatAccountId`) USING BTREE,
+ INDEX `idx_status`(`status`) USING BTREE,
+ INDEX `idx_phone`(`phone`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '添加好友任务记录表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for s2_moments_item
+-- ----------------------------
+DROP TABLE IF EXISTS `s2_moments_item`;
+CREATE TABLE `s2_moments_item` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `task_id` int(11) NOT NULL COMMENT '朋友圈任务ID',
+ `temp_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '临时ID',
+ `wechat_account_id` int(11) NULL DEFAULT NULL COMMENT '微信账号ID',
+ `execute_count` int(11) NULL DEFAULT 0 COMMENT '执行次数',
+ `executed` tinyint(1) NULL DEFAULT 0 COMMENT '是否已执行',
+ `status` tinyint(1) NULL DEFAULT 0 COMMENT '状态',
+ `extra` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '额外信息',
+ `execute_time` int(11) NULL DEFAULT NULL COMMENT '执行时间',
+ `finished_time` int(11) NULL DEFAULT NULL COMMENT '完成时间',
+ `labels` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '标签',
+ `alt_list` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '替代列表',
+ `comments` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '评论',
+ `moment_content_type` tinyint(1) NULL DEFAULT 0 COMMENT '朋友圈内容类型',
+ `text` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '文本内容',
+ `pic_url_list` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '图片URL列表',
+ `video_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '视频URL',
+ `link` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '链接信息',
+ `is_use_location` tinyint(1) NULL DEFAULT 0 COMMENT '是否使用位置',
+ `lat` decimal(10, 6) NULL DEFAULT 0.000000 COMMENT '纬度',
+ `lng` decimal(10, 6) NULL DEFAULT 0.000000 COMMENT '经度',
+ `poi_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '位置名称',
+ `poi_address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '位置地址',
+ `video_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '视频编号',
+ `created_at` int(11) NULL DEFAULT NULL COMMENT '记录创建时间',
+ `updated_at` int(11) NULL DEFAULT NULL COMMENT '记录更新时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE INDEX `idx_task_temp`(`task_id`, `temp_id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 184 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '朋友圈任务项表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for s2_moments_task
+-- ----------------------------
+DROP TABLE IF EXISTS `s2_moments_task`;
+CREATE TABLE `s2_moments_task` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `task_id` int(11) NOT NULL COMMENT '朋友圈任务ID',
+ `tenant_id` int(11) NULL DEFAULT NULL COMMENT '租户ID',
+ `operator_account_id` int(11) NULL DEFAULT NULL COMMENT '操作人账号ID',
+ `account_username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号用户名',
+ `account_nickname` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号昵称',
+ `account_real_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号真实姓名',
+ `public_mode` tinyint(1) NULL DEFAULT 0 COMMENT '发布模式',
+ `moment_content_type` tinyint(1) NULL DEFAULT 1 COMMENT '朋友圈内容类型:1纯文本,2图片,3视频,4链接',
+ `text` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '文本内容',
+ `pic_url_list` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '图片URL列表',
+ `video_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '视频URL',
+ `link` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '链接信息',
+ `job_status` tinyint(1) NULL DEFAULT 0 COMMENT '任务状态',
+ `job_origin_status` tinyint(1) NULL DEFAULT 0 COMMENT '任务原始状态',
+ `job_group` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '任务组',
+ `job_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '任务名称',
+ `begin_time` int(11) NULL DEFAULT NULL COMMENT '开始时间',
+ `end_time` int(11) NULL DEFAULT NULL COMMENT '结束时间',
+ `timing_time` int(11) NULL DEFAULT NULL COMMENT '定时发布时间',
+ `create_time` int(11) NULL DEFAULT NULL COMMENT '创建时间',
+ `immediately` tinyint(1) NULL DEFAULT 1 COMMENT '是否立即发布',
+ `created_at` int(11) NULL DEFAULT NULL COMMENT '记录创建时间',
+ `updated_at` int(11) NULL DEFAULT NULL COMMENT '记录更新时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE INDEX `idx_task_id`(`task_id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 88 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '朋友圈任务表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for s2_reply
+-- ----------------------------
+DROP TABLE IF EXISTS `s2_reply`;
+CREATE TABLE `s2_reply` (
+ `id` int(11) NOT NULL,
+ `tenantId` int(255) NULL DEFAULT NULL,
+ `groupId` int(11) NULL DEFAULT NULL,
+ `accountId` int(11) NULL DEFAULT NULL,
+ `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `msgType` tinyint(2) NULL DEFAULT NULL,
+ `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `createTime` int(11) NULL DEFAULT NULL,
+ `lastUpdateTime` int(11) NULL DEFAULT NULL,
+ `sortIndex` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '快捷回复' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for s2_reply_group
+-- ----------------------------
+DROP TABLE IF EXISTS `s2_reply_group`;
+CREATE TABLE `s2_reply_group` (
+ `id` int(11) NOT NULL,
+ `groupName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `sortIndex` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `parentId` int(11) NULL DEFAULT NULL,
+ `replyType` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `replys` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `departmentId` int(11) NULL DEFAULT 2130,
+ `accountId` int(11) NULL DEFAULT 5150,
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '快捷回复分组' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for s2_wechat_account
+-- ----------------------------
+DROP TABLE IF EXISTS `s2_wechat_account`;
+CREATE TABLE `s2_wechat_account` (
+ `id` int(11) NOT NULL COMMENT '微信账号ID',
+ `wechatId` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '微信ID',
+ `deviceAccountId` int(11) NULL DEFAULT 0 COMMENT '设备账号ID',
+ `imei` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'IMEI',
+ `deviceMemo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '设备备注',
+ `accountUserName` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号用户名',
+ `accountRealName` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号真实姓名',
+ `accountNickname` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号昵称',
+ `keFuAlive` tinyint(1) NULL DEFAULT 0 COMMENT '客服是否在线',
+ `deviceAlive` tinyint(1) NULL DEFAULT 0 COMMENT '设备是否在线',
+ `wechatAlive` tinyint(1) NULL DEFAULT 0 COMMENT '微信是否在线',
+ `yesterdayMsgCount` int(11) NULL DEFAULT 0 COMMENT '昨日消息数',
+ `sevenDayMsgCount` int(11) NULL DEFAULT 0 COMMENT '7天消息数',
+ `thirtyDayMsgCount` int(11) NULL DEFAULT 0 COMMENT '30天消息数',
+ `totalFriend` int(11) NULL DEFAULT 0 COMMENT '总好友数',
+ `maleFriend` int(11) NULL DEFAULT 0 COMMENT '男性好友数',
+ `unknowFriend` int(11) NULL DEFAULT NULL COMMENT '未知好友数',
+ `femaleFriend` int(11) NULL DEFAULT 0 COMMENT '女性好友数',
+ `wechatGroupName` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信群组名称',
+ `tenantId` int(11) NULL DEFAULT NULL COMMENT '租户ID',
+ `nickname` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '昵称',
+ `alias` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '别名',
+ `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像',
+ `gender` tinyint(1) NULL DEFAULT 0 COMMENT '性别',
+ `region` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '地区',
+ `signature` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '签名',
+ `bindQQ` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '绑定QQ',
+ `bindEmail` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '绑定邮箱',
+ `bindMobile` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '绑定手机',
+ `currentDeviceId` int(11) NULL DEFAULT 0 COMMENT '当前设备ID',
+ `isDeleted` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除',
+ `deleteTime` int(11) NULL DEFAULT NULL COMMENT '删除时间',
+ `groupId` int(11) NULL DEFAULT 0 COMMENT '分组ID',
+ `memo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
+ `wechatVersion` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信版本',
+ `labels` json NULL COMMENT '标签',
+ `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
+ `status` tinyint(3) NULL DEFAULT 1 COMMENT '状态值',
+ INDEX `idx_wechat_id`(`wechatId`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信账号表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for s2_wechat_chatroom
+-- ----------------------------
+DROP TABLE IF EXISTS `s2_wechat_chatroom`;
+CREATE TABLE `s2_wechat_chatroom` (
+ `id` int(11) NOT NULL,
+ `wechatAccountId` int(11) NOT NULL COMMENT '微信账号ID',
+ `wechatAccountAlias` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信账号别名',
+ `wechatAccountWechatId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信账号微信ID',
+ `wechatAccountAvatar` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信账号头像',
+ `wechatAccountNickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信账号昵称',
+ `chatroomId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '群聊ID',
+ `hasMe` tinyint(1) NULL DEFAULT 0 COMMENT '是否包含自己',
+ `chatroomOwnerNickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群主昵称',
+ `chatroomOwnerAvatar` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群主头像',
+ `conRemark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
+ `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群聊名称',
+ `pyInitial` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '拼音首字母',
+ `quanPin` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '全拼',
+ `chatroomAvatar` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群头像',
+ `isDeleted` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除',
+ `deleteTime` int(11) NULL DEFAULT NULL COMMENT '删除时间',
+ `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间',
+ `accountId` int(11) NULL DEFAULT 0 COMMENT '账号ID',
+ `accountUserName` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号用户名',
+ `accountRealName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号真实姓名',
+ `accountNickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号昵称',
+ `groupId` int(11) NULL DEFAULT 0 COMMENT '分组ID',
+ `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
+ UNIQUE INDEX `uk_chatroom_account`(`chatroomId`, `wechatAccountId`) USING BTREE,
+ INDEX `wechatAccountId`(`wechatAccountId`) USING BTREE,
+ INDEX `chatroomId`(`chatroomId`) USING BTREE,
+ INDEX `wechatAccountWechatId`(`wechatAccountWechatId`) USING BTREE,
+ INDEX `idx_account_deleted`(`accountId`, `isDeleted`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信群表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for s2_wechat_chatroom_member
+-- ----------------------------
+DROP TABLE IF EXISTS `s2_wechat_chatroom_member`;
+CREATE TABLE `s2_wechat_chatroom_member` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `chatroomId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '群聊ID',
+ `wechatId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '微信ID',
+ `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '昵称',
+ `avatar` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像',
+ `conRemark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
+ `alias` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '别名',
+ `friendType` tinyint(11) NULL DEFAULT 0 COMMENT '好友类型',
+ `createTime` int(10) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(10) NULL DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE INDEX `uk_chatroom_wechat`(`chatroomId`, `wechatId`) USING BTREE,
+ INDEX `chatroomId`(`chatroomId`) USING BTREE,
+ INDEX `wechatId`(`wechatId`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 495043 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信群成员表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for s2_wechat_friend
+-- ----------------------------
+DROP TABLE IF EXISTS `s2_wechat_friend`;
+CREATE TABLE `s2_wechat_friend` (
+ `id` int(11) NULL DEFAULT NULL COMMENT '好友id',
+ `wechatAccountId` int(11) NOT NULL COMMENT '所有者微信账号ID',
+ `alias` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '好友微信号',
+ `wechatId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '好友微信ID',
+ `conRemark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注名',
+ `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '昵称',
+ `pyInitial` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '拼音首字母',
+ `quanPin` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '全拼',
+ `avatar` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像URL',
+ `gender` tinyint(1) NULL DEFAULT 0 COMMENT '性别',
+ `region` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '地区',
+ `addFrom` int(11) NULL DEFAULT NULL COMMENT '添加来源',
+ `labels` json NULL COMMENT '标签JSON',
+ `siteLabels` json NULL COMMENT '站内标签JSON',
+ `signature` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '个性签名',
+ `isDeleted` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除',
+ `isPassed` tinyint(1) NULL DEFAULT 1 COMMENT '是否通过',
+ `deleteTime` int(11) NULL DEFAULT NULL COMMENT '删除时间',
+ `accountId` int(11) NULL DEFAULT 0 COMMENT '账号ID',
+ `extendFields` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '扩展字段JSON',
+ `accountUserName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号用户名',
+ `accountRealName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号真实姓名',
+ `accountNickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号昵称',
+ `ownerAlias` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '所有者别名',
+ `ownerWechatId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '所有者微信ID',
+ `ownerNickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '所有者昵称',
+ `ownerAvatar` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '所有者头像',
+ `phone` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '电话',
+ `thirdParty` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '第三方数据JSON',
+ `groupId` int(11) NULL DEFAULT 0 COMMENT '分组ID',
+ `passTime` int(11) NULL DEFAULT NULL COMMENT '通过时间',
+ `additionalPicture` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '附加图片',
+ `desc` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '描述',
+ `country` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '国家',
+ `privince` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '省份',
+ `city` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '城市',
+ `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间',
+ `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
+ `R` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0',
+ `F` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0',
+ `M` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0',
+ UNIQUE INDEX `uk_owner_wechat_account`(`ownerWechatId`, `wechatId`, `wechatAccountId`) USING BTREE,
+ INDEX `idx_wechat_account_id`(`wechatAccountId`) USING BTREE,
+ INDEX `idx_wechat_id`(`wechatId`) USING BTREE,
+ INDEX `idx_owner_wechat_id`(`ownerWechatId`) USING BTREE,
+ INDEX `idx_id`(`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信好友表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for s2_wechat_group
+-- ----------------------------
+DROP TABLE IF EXISTS `s2_wechat_group`;
+CREATE TABLE `s2_wechat_group` (
+ `id` int(11) NOT NULL,
+ `tenantId` int(11) NULL DEFAULT NULL,
+ `groupName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `groupMemo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `groupType` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `sortIndex` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `groupOwnerType` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `departmentId` int(11) NULL DEFAULT NULL,
+ `accountId` int(11) NULL DEFAULT NULL,
+ `createTime` int(11) NULL DEFAULT NULL,
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for s2_wechat_message
+-- ----------------------------
+DROP TABLE IF EXISTS `s2_wechat_message`;
+CREATE TABLE `s2_wechat_message` (
+ `id` bigint(20) NOT NULL COMMENT '消息ID',
+ `type` tinyint(1) NOT NULL DEFAULT 1 COMMENT '消息类型 1好友 2群',
+ `wechatFriendId` bigint(20) NULL DEFAULT NULL COMMENT '微信好友ID',
+ `wechatChatroomId` bigint(20) NOT NULL COMMENT '微信群聊ID',
+ `senderNickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '发送者昵称',
+ `senderWechatId` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '发送者微信ID',
+ `senderIsAdmin` tinyint(1) NULL DEFAULT 0 COMMENT '发送者是否管理员',
+ `senderIsDeleted` tinyint(1) NULL DEFAULT 0 COMMENT '发送者是否已删除',
+ `senderChatroomNickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '发送者群昵称',
+ `senderWechatAccountId` bigint(20) NULL DEFAULT NULL COMMENT '发送者微信账号ID',
+ `wechatAccountId` bigint(20) NULL DEFAULT NULL COMMENT '微信账号ID',
+ `tenantId` bigint(20) NULL DEFAULT NULL COMMENT '租户ID',
+ `accountId` bigint(20) NULL DEFAULT NULL COMMENT '账号ID',
+ `synergyAccountId` bigint(20) NULL DEFAULT 0 COMMENT '协同账号ID',
+ `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容',
+ `originalContent` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容(原版)',
+ `msgType` int(11) NULL DEFAULT NULL COMMENT '消息类型 1 文字 3图片 47动态图片 34语言 43视频 42名片 40/20链接 49文件 419430449转账 436207665红包',
+ `msgSubType` int(11) NULL DEFAULT 0 COMMENT '消息子类型',
+ `msgSvrId` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '消息服务器ID',
+ `isSend` tinyint(1) NULL DEFAULT 1 COMMENT '是否发送',
+ `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间',
+ `isDeleted` tinyint(1) NULL DEFAULT 0 COMMENT '是否已删除',
+ `deleteTime` int(11) NULL DEFAULT NULL COMMENT '删除时间',
+ `sendStatus` int(11) NULL DEFAULT 0 COMMENT '发送状态',
+ `wechatTime` int(11) NULL DEFAULT NULL COMMENT '微信时间',
+ `origin` int(11) NULL DEFAULT 0 COMMENT '来源',
+ `msgId` bigint(20) NULL DEFAULT NULL COMMENT '消息ID',
+ `recallId` tinyint(1) NULL DEFAULT 0 COMMENT '撤回ID',
+ `isRead` tinyint(1) NULL DEFAULT 0 COMMENT '是否读取',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `idx_wechatChatroomId`(`wechatChatroomId`) USING BTREE,
+ INDEX `idx_wechatAccountId`(`wechatAccountId`) USING BTREE,
+ INDEX `idx_msgSvrId`(`msgSvrId`) USING BTREE,
+ INDEX `idx_type`(`type`) USING BTREE,
+ INDEX `idx_type_wechatTime`(`type`, `wechatTime`, `id`) USING BTREE,
+ INDEX `idx_friend_time`(`wechatFriendId`, `wechatTime`, `id`) USING BTREE,
+ INDEX `idx_chatroom_time`(`wechatChatroomId`, `wechatTime`, `id`) USING BTREE,
+ INDEX `idx_account_type`(`accountId`, `type`, `wechatTime`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信群聊消息记录表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for s2_wechat_moments
+-- ----------------------------
+DROP TABLE IF EXISTS `s2_wechat_moments`;
+CREATE TABLE `s2_wechat_moments` (
+ `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `wechatAccountId` int(11) NOT NULL COMMENT '微信账号ID',
+ `wechatFriendId` int(11) NULL DEFAULT NULL COMMENT '微信好友ID',
+ `snsId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '朋友圈消息ID',
+ `commentList` json NULL COMMENT '评论列表JSON',
+ `createTime` bigint(20) NULL DEFAULT 0 COMMENT '创建时间戳',
+ `likeList` json NULL COMMENT '点赞列表JSON',
+ `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '朋友圈内容',
+ `lat` decimal(10, 6) NULL DEFAULT 0.000000 COMMENT '纬度',
+ `lng` decimal(10, 6) NULL DEFAULT 0.000000 COMMENT '经度',
+ `location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '位置信息',
+ `picSize` int(11) NULL DEFAULT 0 COMMENT '图片大小',
+ `resUrls` json NULL COMMENT '资源URL列表',
+ `userName` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户名',
+ `type` int(11) NULL DEFAULT 0 COMMENT '朋友圈类型',
+ `create_time` int(11) NULL DEFAULT NULL COMMENT '数据创建时间',
+ `update_time` int(11) NULL DEFAULT NULL COMMENT '数据更新时间',
+ `coverImage` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+ `urls` json NULL,
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE INDEX `idx_sns_account`(`snsId`, `wechatAccountId`) USING BTREE,
+ INDEX `idx_account_friend`(`wechatAccountId`, `wechatFriendId`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 39669 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信朋友圈数据表' ROW_FORMAT = Dynamic;
+
+SET FOREIGN_KEY_CHECKS = 1;
diff --git a/Server/微信健康分规则v2.md b/Server/微信健康分规则v2.md
new file mode 100644
index 00000000..90c63f07
--- /dev/null
+++ b/Server/微信健康分规则v2.md
@@ -0,0 +1,58 @@
+# 微信健康分规则 v2
+
+## 一、定义
+
+当客户收到手机设备后,登录了微信号,我们将对其微信号进行健康分的评估。
+
+**健康分 = 基础分 + 动态分**
+
+健康分只与系统中的"每日自动添加好友次数"这个功能相关联。\
+通过健康分体系来定义一个微信号每日**最佳、最稳定的添加次数**。\
+后期还可将健康分作为标签属性,用于快速筛选微信号。
+
+**公式:每日最大加人次数 = 健康分 × 0.2**
+
+## 二、基础分
+
+基础分为 **60--100 分**。
+
+由 `60 + 40(基础加成分)` 四个维度参数组成,每个参数具有不同权重。
+
+### 基础分组成
+
+ 类型 权重 分数
+ ------------ ------ ------
+ 基础信息 0.2 10
+ 好友数量 0.3 30
+ 默认基础分 --- 60
+
+### 1. 基础信息(权重 0.2,满分 10)
+
+ 类型 权重 分数
+ -------------- ------ ------
+ 已修改微信号 1 10
+
+### 2. 好友数量(权重 0.3,满分 30)
+
+ 好友数量范围 权重 分数
+ -------------- ------ ------
+ 0--50 0.1 3
+ 51--500 0.2 6
+ 501--3000 0.3 8
+ 3001 以上 0.4 12
+
+## 三、动态分规则
+
+### 扣分规则
+
+ 场景 扣分 处罚
+ ---------- ------ --------------
+ 首次频繁 15 暂停 24 小时
+ 再次频繁 25 暂停 24 小时
+ 封号 60 暂停 72 小时
+
+### 加分规则
+
+ 场景 加分
+ --------------------- ------
+ 连续 3 天不触发频繁 5/日
diff --git a/Touchkebao/index.html b/Touchkebao/index.html
index 92656ef9..f1c04fe6 100644
--- a/Touchkebao/index.html
+++ b/Touchkebao/index.html
@@ -11,6 +11,10 @@
+
diff --git a/Touchkebao/src/pages/pc/ckbox/components/NavCommon/Notice.tsx b/Touchkebao/src/pages/pc/ckbox/components/NavCommon/Notice.tsx
new file mode 100644
index 00000000..1249a6d7
--- /dev/null
+++ b/Touchkebao/src/pages/pc/ckbox/components/NavCommon/Notice.tsx
@@ -0,0 +1,431 @@
+import React, { useEffect, useState } from "react";
+import { Drawer, Avatar, Space, Button, Badge, Empty, Tabs, Tag } from "antd";
+import { BellOutlined } from "@ant-design/icons";
+import {
+ noticeList,
+ readMessage,
+ readAll,
+ friendRequestList as fetchFriendRequestListApi,
+} from "./api";
+import styles from "./index.module.scss";
+
+interface MessageItem {
+ id: number;
+ type: number;
+ companyId: number;
+ userId: number;
+ bindId: number;
+ title: string;
+ message: string;
+ isRead: number;
+ createTime: string;
+ readTime: string;
+ friendData: {
+ nickname: string;
+ avatar: string;
+ };
+}
+
+interface FriendRequestItem {
+ taskId: number;
+ phone: string;
+ wechatId: string;
+ adder?: {
+ avatar?: string;
+ nickname?: string;
+ username?: string;
+ accountNickname?: string;
+ accountRealName?: string;
+ };
+ status?: {
+ code?: number;
+ text?: string;
+ };
+ time?: {
+ addTime?: string;
+ addTimeStamp?: number;
+ updateTime?: string;
+ updateTimeStamp?: number;
+ passTime?: string;
+ passTimeStamp?: number;
+ };
+ friend?: {
+ nickname?: string;
+ isPassed?: boolean;
+ };
+ other?: {
+ msgContent?: string;
+ remark?: string;
+ from?: string;
+ labels?: string[];
+ };
+}
+
+const DEFAULT_QUERY = { page: 1, limit: 20 };
+
+const Notice: React.FC = () => {
+ const [messageDrawerVisible, setMessageDrawerVisible] = useState(false);
+ const [activeTab, setActiveTab] = useState("messages");
+ const [messageList, setMessageList] = useState([]);
+ const [messageCount, setMessageCount] = useState(0);
+ const [loading, setLoading] = useState(false);
+ const [friendRequestList, setFriendRequestList] = useState<
+ FriendRequestItem[]
+ >([]);
+ const [friendRequestLoading, setFriendRequestLoading] = useState(false);
+
+ const fetchMessageList = async () => {
+ try {
+ setLoading(true);
+ const response = await noticeList(DEFAULT_QUERY);
+ if (response?.list) {
+ setMessageList(response.list);
+ const unreadCount = response.list.filter(
+ (item: MessageItem) => item.isRead === 0,
+ ).length;
+ setMessageCount(unreadCount);
+ }
+ } catch (error) {
+ console.error("获取消息列表失败:", error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const refreshUnreadCount = async () => {
+ try {
+ const response = await noticeList(DEFAULT_QUERY);
+ if (response && typeof response.noRead === "number") {
+ setMessageCount(response.noRead);
+ }
+ } catch (error) {
+ console.error("获取未读消息数失败:", error);
+ }
+ };
+
+ useEffect(() => {
+ fetchMessageList();
+ const timer = window.setInterval(refreshUnreadCount, 30 * 1000);
+ return () => {
+ window.clearInterval(timer);
+ };
+ }, []);
+
+ const handleMessageClick = () => {
+ setMessageDrawerVisible(true);
+ fetchMessageList();
+ fetchFriendRequestList();
+ };
+
+ const handleTabChange = (key: string) => {
+ setActiveTab(key);
+ if (key === "friendRequests") {
+ fetchFriendRequestList();
+ }
+ };
+
+ const handleMessageDrawerClose = () => {
+ setMessageDrawerVisible(false);
+ };
+
+ const handleReadMessage = async (messageId: number) => {
+ try {
+ await readMessage({ id: messageId });
+ setMessageList(prev => {
+ const updated = prev.map(item =>
+ item.id === messageId ? { ...item, isRead: 1 } : item,
+ );
+ const unreadCount = updated.filter(item => item.isRead === 0).length;
+ setMessageCount(unreadCount);
+ return updated;
+ });
+ } catch (error) {
+ console.error("标记消息已读失败:", error);
+ }
+ };
+
+ const handleReadAll = async () => {
+ try {
+ await readAll();
+ setMessageList(prev => prev.map(item => ({ ...item, isRead: 1 })));
+ setMessageCount(0);
+ } catch (error) {
+ console.error("全部已读失败:", error);
+ }
+ };
+
+ const fetchFriendRequestList = async () => {
+ try {
+ setFriendRequestLoading(true);
+ const response = await fetchFriendRequestListApi(DEFAULT_QUERY);
+ if (response?.list) {
+ setFriendRequestList(response.list);
+ }
+ } catch (error) {
+ console.error("获取好友添加记录失败:", error);
+ } finally {
+ setFriendRequestLoading(false);
+ }
+ };
+
+ const formatTime = (timeStr?: string) => {
+ if (!timeStr) {
+ return "-";
+ }
+ const date = new Date(timeStr);
+ const now = new Date();
+ const diff = now.getTime() - date.getTime();
+ const days = Math.floor(diff / (1000 * 60 * 60 * 24));
+
+ if (days === 0) {
+ return date.toLocaleTimeString("zh-CN", {
+ hour: "2-digit",
+ minute: "2-digit",
+ });
+ } else if (days === 1) {
+ return "昨天";
+ } else if (days < 7) {
+ return `${days}天前`;
+ } else {
+ return date.toLocaleDateString("zh-CN", {
+ month: "2-digit",
+ day: "2-digit",
+ });
+ }
+ };
+
+ const getStatusText = (statusCode?: number, statusText?: string) => {
+ if (statusText) {
+ return statusText;
+ }
+ switch (statusCode) {
+ case 0:
+ return "待处理";
+ case 1:
+ return "已同意";
+ case 2:
+ return "已拒绝";
+ default:
+ return "未知";
+ }
+ };
+
+ const getStatusColor = (statusCode?: number) => {
+ switch (statusCode) {
+ case 0:
+ return "#1890ff";
+ case 1:
+ return "#52c41a";
+ case 2:
+ return "#ff4d4f";
+ default:
+ return "#999";
+ }
+ };
+
+ const getFriendRequestKey = (item: FriendRequestItem) => {
+ return (
+ item.taskId?.toString() ||
+ item.wechatId ||
+ item.phone ||
+ `${item.adder?.username || "unknown"}-${item.time?.addTime || "time"}`
+ );
+ };
+
+ const getAddedUserName = (item: FriendRequestItem) => {
+ return (
+ item.friend?.nickname ||
+ item.phone ||
+ item.wechatId ||
+ item.adder?.nickname ||
+ "未知好友"
+ );
+ };
+
+ const getAdderName = (item: FriendRequestItem) => {
+ return (
+ item.adder?.nickname ||
+ item.adder?.username ||
+ item.adder?.accountNickname ||
+ item.adder?.accountRealName ||
+ "未知添加人"
+ );
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ )
+ }
+ >
+
+
+ {loading ? (
+
+ 加载中...
+
+ ) : messageList.length === 0 ? (
+
+ ) : (
+ messageList.map(item => (
+ handleReadMessage(item.id)}
+ >
+
+
+ {item.friendData?.nickname?.charAt(0) || "U"}
+
+
+
+
+
+ {item.title}
+
+ {item.isRead === 0 && (
+
+ )}
+
+
+ {item.message}
+
+ {item.isRead === 0 && (
+
+ {formatTime(item.createTime)}
+
+
+ )}
+
+
+ ))
+ )}
+
+ ),
+ },
+ {
+ key: "friendRequests",
+ label: "好友添加记录",
+ children: (
+
+ {friendRequestLoading ? (
+
+ 加载中...
+
+ ) : friendRequestList.length === 0 ? (
+
+ ) : (
+ friendRequestList.map(item => (
+
+
+
+ {item.adder?.nickname?.charAt(0) || "U"}
+
+
+
+
+
+ 添加好友:
+ {getAddedUserName(item)}
+
+
+ {getStatusText(
+ item.status?.code,
+ item.status?.text,
+ )}
+
+
+
+ 申请人:{getAdderName(item)}
+
+
+ 验证信息:{item.other?.msgContent || "无"}
+
+
+
+ {item.other?.remark && (
+
+ 备注:{item.other.remark}
+
+ )}
+
+
+ {formatTime(item.time?.addTime)}
+
+
+
+ ))
+ )}
+
+ ),
+ },
+ ]}
+ />
+
+
+ >
+ );
+};
+
+export default Notice;
diff --git a/Touchkebao/src/pages/pc/ckbox/components/NavCommon/api.ts b/Touchkebao/src/pages/pc/ckbox/components/NavCommon/api.ts
index f814b6b9..dd665a02 100644
--- a/Touchkebao/src/pages/pc/ckbox/components/NavCommon/api.ts
+++ b/Touchkebao/src/pages/pc/ckbox/components/NavCommon/api.ts
@@ -14,3 +14,8 @@ export const readMessage = (params: { id: number }) => {
export const readAll = () => {
return request(`/v1/kefu/notice/readAll`, undefined, "PUT");
};
+
+// 好友添加任务列表
+export const friendRequestList = (params: { page: number; limit: number }) => {
+ return request(`/v1/kefu/wechatFriend/addTaskList`, params, "GET");
+};
diff --git a/Touchkebao/src/pages/pc/ckbox/components/NavCommon/index.tsx b/Touchkebao/src/pages/pc/ckbox/components/NavCommon/index.tsx
index 4e3fabcd..dce087d4 100644
--- a/Touchkebao/src/pages/pc/ckbox/components/NavCommon/index.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/components/NavCommon/index.tsx
@@ -1,28 +1,18 @@
-import React, { useState, useEffect } from "react";
-import {
- Layout,
- Drawer,
- Avatar,
- Space,
- Button,
- Badge,
- Dropdown,
- Empty,
-} from "antd";
+import React, { useState } from "react";
+import { Layout, Avatar, Space, Button, Dropdown, message } from "antd";
import {
BarChartOutlined,
UserOutlined,
- BellOutlined,
LogoutOutlined,
ThunderboltOutlined,
SettingOutlined,
- CalendarOutlined,
- RetweetOutlined,
+ SendOutlined,
+ ClearOutlined,
} from "@ant-design/icons";
-import { noticeList, readMessage, readAll } from "./api";
import { useUserStore } from "@/store/module/user";
import { useNavigate, useLocation } from "react-router-dom";
import styles from "./index.module.scss";
+import Notice from "./Notice";
const { Header } = Layout;
@@ -31,39 +21,12 @@ interface NavCommonProps {
onMenuClick?: () => void;
}
-// 消息数据类型
-interface MessageItem {
- id: number;
- type: number;
- companyId: number;
- userId: number;
- bindId: number;
- title: string;
- message: string;
- isRead: number;
- createTime: string;
- readTime: string;
- friendData: {
- nickname: string;
- avatar: string;
- };
-}
-
const NavCommon: React.FC = ({ title = "触客宝" }) => {
- const [messageDrawerVisible, setMessageDrawerVisible] = useState(false);
- const [messageList, setMessageList] = useState([]);
- const [messageCount, setMessageCount] = useState(0);
- const [loading, setLoading] = useState(false);
+ const [clearingCache, setClearingCache] = useState(false);
const navigate = useNavigate();
const location = useLocation();
const { user, logout } = useUserStore();
- // 初始化时获取消息列表
- useEffect(() => {
- fetchMessageList();
- setInterval(IntervalMessageCount, 30 * 1000);
- }, []);
-
// 处理菜单图标点击:在两个路由之间切换
const handleMenuClick = () => {
if (!location.pathname.startsWith("/pc/powerCenter")) {
@@ -72,111 +35,102 @@ const NavCommon: React.FC = ({ title = "触客宝" }) => {
navigate("/pc/weChat");
}
};
-
- const isWeChat = () => {
- return location.pathname.startsWith("/pc/weChat");
- };
-
- // 定时器获取消息条数
- const IntervalMessageCount = async () => {
- try {
- const response = await noticeList({ page: 1, limit: 20 });
- if (response && response.noRead) {
- setMessageCount(response.noRead);
- }
- } catch (error) {
- console.error("获取消息列表失败:", error);
- }
- };
- // 获取消息列表
- const fetchMessageList = async () => {
- try {
- setLoading(true);
- const response = await noticeList({ page: 1, limit: 20 });
- if (response && response.list) {
- setMessageList(response.list);
- // 计算未读消息数量
- const unreadCount = response.list.filter(
- (item: MessageItem) => item.isRead === 0,
- ).length;
- setMessageCount(unreadCount);
- }
- } catch (error) {
- console.error("获取消息列表失败:", error);
- } finally {
- setLoading(false);
- }
- };
-
- // 处理消息中心点击
- const handleMessageClick = () => {
- setMessageDrawerVisible(true);
- fetchMessageList();
- };
-
- // 处理消息抽屉关闭
- const handleMessageDrawerClose = () => {
- setMessageDrawerVisible(false);
- };
-
// 处理退出登录
const handleLogout = () => {
logout(); // 清除localStorage中的token和用户状态
navigate("/login"); // 跳转到登录页面
};
- // 处理消息已读
- const handleReadMessage = async (messageId: number) => {
- try {
- await readMessage({ id: messageId }); // 这里需要根据实际API调整参数
- // 更新本地状态
- setMessageList(prev =>
- prev.map(item =>
- item.id === messageId ? { ...item, isRead: 1 } : item,
- ),
- );
- // 重新计算未读数量
- const unreadCount =
- messageList.filter(item => item.isRead === 0).length - 1;
- setMessageCount(Math.max(0, unreadCount));
- } catch (error) {
- console.error("标记消息已读失败:", error);
- }
+ // 清除所有 IndexedDB 数据库
+ const clearAllIndexedDB = async (): Promise => {
+ return new Promise((resolve, reject) => {
+ if (!window.indexedDB) {
+ resolve();
+ return;
+ }
+
+ // 获取所有数据库名称
+ const databases: string[] = [];
+ const request = indexedDB.databases();
+
+ request
+ .then(dbs => {
+ dbs.forEach(db => {
+ if (db.name) {
+ databases.push(db.name);
+ }
+ });
+
+ // 删除所有数据库
+ const deletePromises = databases.map(dbName => {
+ return new Promise((resolveDelete, rejectDelete) => {
+ const deleteRequest = indexedDB.deleteDatabase(dbName);
+ deleteRequest.onsuccess = () => {
+ resolveDelete();
+ };
+ deleteRequest.onerror = () => {
+ rejectDelete(new Error(`删除数据库 ${dbName} 失败`));
+ };
+ deleteRequest.onblocked = () => {
+ // 如果数据库被阻塞,等待一下再重试
+ setTimeout(() => {
+ const retryRequest = indexedDB.deleteDatabase(dbName);
+ retryRequest.onsuccess = () => resolveDelete();
+ retryRequest.onerror = () =>
+ rejectDelete(new Error(`删除数据库 ${dbName} 失败`));
+ }, 100);
+ };
+ });
+ });
+
+ Promise.all(deletePromises)
+ .then(() => resolve())
+ .catch(reject);
+ })
+ .catch(reject);
+ });
};
- // 处理全部已读
- const handleReadAll = async () => {
+ // 处理清除缓存
+ const handleClearCache = async () => {
try {
- await readAll(); // 这里需要根据实际API调整参数
- // 更新本地状态
- setMessageList(prev => prev.map(item => ({ ...item, isRead: 1 })));
- setMessageCount(0);
+ setClearingCache(true);
+ const hideLoading = message.loading("正在清除缓存...", 0);
+
+ // 1. 清除所有 localStorage
+ try {
+ localStorage.clear();
+ } catch (error) {
+ console.warn("清除 localStorage 失败:", error);
+ }
+
+ // 2. 清除所有 sessionStorage
+ try {
+ sessionStorage.clear();
+ } catch (error) {
+ console.warn("清除 sessionStorage 失败:", error);
+ }
+
+ // 3. 清除所有 IndexedDB 数据库
+ try {
+ await clearAllIndexedDB();
+ } catch (error) {
+ console.warn("清除 IndexedDB 失败:", error);
+ }
+
+ hideLoading();
+ message.success("缓存清除成功");
+
+ // 清除成功后跳转到登录页面
+ setTimeout(() => {
+ logout();
+ navigate("/login");
+ }, 500);
} catch (error) {
- console.error("全部已读失败:", error);
- }
- };
-
- // 格式化时间
- const formatTime = (timeStr: string) => {
- const date = new Date(timeStr);
- const now = new Date();
- const diff = now.getTime() - date.getTime();
- const days = Math.floor(diff / (1000 * 60 * 60 * 24));
-
- if (days === 0) {
- return date.toLocaleTimeString("zh-CN", {
- hour: "2-digit",
- minute: "2-digit",
- });
- } else if (days === 1) {
- return "昨天";
- } else if (days < 7) {
- return `${days}天前`;
- } else {
- return date.toLocaleDateString("zh-CN", {
- month: "2-digit",
- day: "2-digit",
- });
+ console.error("清除缓存失败:", error);
+ message.error("清除缓存失败,请稍后重试");
+ } finally {
+ setClearingCache(false);
}
};
@@ -199,6 +153,13 @@ const NavCommon: React.FC = ({ title = "触客宝" }) => {
navigate("/pc/commonConfig");
},
},
+ {
+ key: "clearCache",
+ icon: ,
+ label: clearingCache ? "清除缓存中..." : "清除缓存",
+ onClick: handleClearCache,
+ disabled: clearingCache,
+ },
{
key: "logout",
icon: ,
@@ -220,10 +181,10 @@ const NavCommon: React.FC = ({ title = "触客宝" }) => {
>
}
+ icon={}
onClick={handleContentManagementClick}
>
- 内容管理
+ 发朋友圈
{title}
@@ -236,11 +197,7 @@ const NavCommon: React.FC = ({ title = "触客宝" }) => {
{user?.tokens}
-
-
-
-
-
+
= ({ title = "触客宝" }) => {
-
-
-
-
- }
- >
-
- {loading ? (
-
- 加载中...
-
- ) : messageList.length === 0 ? (
-
- ) : (
- messageList.map(item => (
-
handleReadMessage(item.id)}
- >
-
-
- {item.friendData?.nickname?.charAt(0) || "U"}
-
-
-
-
-
{item.title}
- {item.isRead === 0 && (
-
- )}
-
-
{item.message}
- {item.isRead === 0 && (
-
- {formatTime(item.createTime)}
-
-
- )}
-
-
- ))
- )}
-
-
>
);
};
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/index.tsx
index fed9b453..97ea2452 100644
--- a/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/index.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/index.tsx
@@ -20,7 +20,7 @@ const ContentManagement: React.FC = () => {
return (
,
- color: "#52c41a",
- tag: "内容管理",
- features: [
- "多库管理与分类",
- "AI调用权限配置",
- "内容检索规则设置",
- "手动内容上传",
- ],
- path: "/pc/powerCenter/content-library",
- },
+ // {
+ // id: "content-library",
+ // title: "AI内容库配置",
+ // description: "管理AI内容库,配置调用权限,优化AI推送效果和内容质量",
+ // icon:
,
+ // color: "#52c41a",
+ // tag: "内容管理",
+ // features: [
+ // "多库管理与分类",
+ // "AI调用权限配置",
+ // "内容检索规则设置",
+ // "手动内容上传",
+ // ],
+ // path: "/pc/powerCenter/content-library",
+ // },
{
id: "message-push-assistant",
title: "消息推送助手",
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/index.tsx
index 51652b1e..87090063 100644
--- a/Touchkebao/src/pages/pc/ckbox/powerCenter/index.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/index.tsx
@@ -39,18 +39,6 @@ const PowerCenter: React.FC = () => {
return (
{/* 页面标题区域 */}
-
-
-
-
- AI智能营销·一站式客户管理·高效业务增长
-
-
-
-
{/* KPI统计区域(置顶,按图展示) */}
@@ -157,9 +145,11 @@ const PowerCenter: React.FC = () => {
{card.features.map((feature, index) => (
{feature}
@@ -186,7 +176,7 @@ const PowerCenter: React.FC = () => {
className={styles.cardIcon}
style={{
backgroundColor: getIconBgColor(
- featureCategories[3].color
+ featureCategories[3].color,
),
}}
>
@@ -212,9 +202,11 @@ const PowerCenter: React.FC = () => {
{featureCategories[3].features.map((feature, index) => (
{feature}
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/api.ts b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/api.ts
index 34fa4bb4..5cc9147a 100644
--- a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/api.ts
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/api.ts
@@ -1,6 +1,9 @@
import request from "@/api/request";
+import type { CreatePushTaskPayload } from "./types";
-// 获取客服列表
-export function queryWorkbenchCreate(params) {
+// 创建推送任务
+export function queryWorkbenchCreate(
+ params: CreatePushTaskPayload,
+): Promise {
return request("/v1/workbench/create", params, "POST");
}
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepPushParams/index.module.scss b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepPushParams/index.module.scss
new file mode 100644
index 00000000..5b3802fd
--- /dev/null
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepPushParams/index.module.scss
@@ -0,0 +1,92 @@
+.stepContent {
+ .step4Content {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ max-width: 600px;
+ }
+
+ .settingsPanel {
+ border: 1px solid #e8e8e8;
+ border-radius: 8px;
+ padding: 20px;
+ background: #fafafa;
+
+ .settingsTitle {
+ font-size: 14px;
+ font-weight: 500;
+ color: #1a1a1a;
+ margin-bottom: 16px;
+ }
+
+ .settingItem {
+ margin-bottom: 20px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+
+ .settingLabel {
+ font-size: 14px;
+ font-weight: 500;
+ color: #1a1a1a;
+ margin-bottom: 12px;
+ }
+
+ .settingControl {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+
+ span {
+ font-size: 14px;
+ color: #666;
+ min-width: 80px;
+ }
+ }
+ }
+ }
+
+ .tagSection {
+ .settingLabel {
+ font-size: 14px;
+ font-weight: 500;
+ color: #1a1a1a;
+ margin-bottom: 12px;
+ }
+ }
+
+ .pushPreview {
+ border: 1px solid #e8e8e8;
+ border-radius: 8px;
+ padding: 20px;
+ background: #f0f7ff;
+
+ .previewTitle {
+ font-size: 14px;
+ font-weight: 500;
+ color: #1a1a1a;
+ margin-bottom: 12px;
+ }
+
+ ul {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+
+ li {
+ font-size: 14px;
+ color: #666;
+ line-height: 1.8;
+ }
+ }
+ }
+}
+
+@media (max-width: 768px) {
+ .stepContent {
+ .step4Content {
+ max-width: 100%;
+ }
+ }
+}
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepPushParams/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepPushParams/index.tsx
new file mode 100644
index 00000000..f15f6411
--- /dev/null
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepPushParams/index.tsx
@@ -0,0 +1,108 @@
+"use client";
+
+import React from "react";
+import { Select, Slider } from "antd";
+import styles from "./index.module.scss";
+
+interface StepPushParamsProps {
+ selectedAccounts: any[];
+ selectedContacts: any[];
+ targetLabel: string;
+ friendInterval: [number, number];
+ onFriendIntervalChange: (value: [number, number]) => void;
+ messageInterval: [number, number];
+ onMessageIntervalChange: (value: [number, number]) => void;
+ selectedTag: string;
+ onSelectedTagChange: (value: string) => void;
+ savedScriptGroups: any[];
+}
+
+const StepPushParams: React.FC = ({
+ selectedAccounts,
+ selectedContacts,
+ targetLabel,
+ friendInterval,
+ onFriendIntervalChange,
+ messageInterval,
+ onMessageIntervalChange,
+ selectedTag,
+ onSelectedTagChange,
+ savedScriptGroups,
+}) => {
+ return (
+
+
+
+
相关设置
+
+
好友间间隔
+
+ 间隔时间(秒)
+
+ onFriendIntervalChange(value as [number, number])
+ }
+ style={{ flex: 1, margin: "0 16px" }}
+ />
+
+ {friendInterval[0]} - {friendInterval[1]}
+
+
+
+
+
消息间间隔
+
+ 间隔时间(秒)
+
+ onMessageIntervalChange(value as [number, number])
+ }
+ style={{ flex: 1, margin: "0 16px" }}
+ />
+
+ {messageInterval[0]} - {messageInterval[1]}
+
+
+
+
+
+
+
完成打标签
+
+
+
+
+
推送预览
+
+ - 推送账号: {selectedAccounts.length}个
+ -
+ 推送{targetLabel}: {selectedContacts.length}个
+
+ - 话术组数: {savedScriptGroups.length}个
+ - 随机推送: 否
+ - 预计耗时: ~1分钟
+
+
+
+
+ );
+};
+
+export default StepPushParams;
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/InputMessage/InputMessage.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/InputMessage/InputMessage.tsx
index baadd6b4..08b8fa38 100644
--- a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/InputMessage/InputMessage.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/InputMessage/InputMessage.tsx
@@ -6,6 +6,7 @@ import { EmojiPicker } from "@/components/EmojiSeclection";
import { EmojiInfo } from "@/components/EmojiSeclection/wechatEmoji";
import SimpleFileUpload from "@/components/Upload/SimpleFileUpload";
import AudioRecorder from "@/components/Upload/AudioRecorder";
+import type { MessageItem } from "../../../types";
import styles from "./index.module.scss";
@@ -17,6 +18,7 @@ interface InputMessageProps {
defaultValue?: string;
onContentChange?: (value: string) => void;
onSend?: (value: string) => void;
+ onAddMessage?: (message: MessageItem) => void; // 新增:支持添加非文本消息
clearOnSend?: boolean;
placeholder?: string;
hint?: React.ReactNode;
@@ -68,6 +70,7 @@ const InputMessage: React.FC = ({
defaultValue = "",
onContentChange,
onSend,
+ onAddMessage,
clearOnSend = false,
placeholder = "输入消息...",
hint,
@@ -169,9 +172,44 @@ const InputMessage: React.FC = ({
msgType,
content,
});
- antdMessage.success("附件上传成功,可在推送时使用");
+
+ // 如果提供了 onAddMessage 回调,则添加到消息列表
+ if (onAddMessage) {
+ let messageItem: MessageItem;
+ if ([FileType.IMAGE].includes(fileType)) {
+ messageItem = {
+ type: "image",
+ content: filePath.url,
+ fileName: filePath.name,
+ };
+ } else if ([FileType.AUDIO].includes(fileType)) {
+ messageItem = {
+ type: "audio",
+ content: filePath.url,
+ fileName: filePath.name,
+ durationMs: filePath.durationMs,
+ };
+ } else if ([FileType.FILE].includes(fileType)) {
+ messageItem = {
+ type: "file",
+ content: filePath.url,
+ fileName: filePath.name,
+ };
+ } else {
+ // 默认作为文本处理
+ messageItem = {
+ type: "text",
+ content: filePath.url,
+ fileName: filePath.name,
+ };
+ }
+ onAddMessage(messageItem);
+ antdMessage.success("已添加消息内容");
+ } else {
+ antdMessage.success("附件上传成功,可在推送时使用");
+ }
},
- [],
+ [onAddMessage],
);
const handleAudioUploaded = useCallback(
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.module.scss b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.module.scss
index 2fe9b07f..0896f840 100644
--- a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.module.scss
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.module.scss
@@ -19,28 +19,27 @@
.step3Content {
display: flex;
+ flex-direction: row;
gap: 24px;
- align-items: flex-start;
.leftColumn {
flex: 1;
display: flex;
flex-direction: column;
gap: 20px;
+ min-width: 0;
}
.rightColumn {
- width: 400px;
flex: 1;
+ max-width: 500px;
display: flex;
flex-direction: column;
gap: 20px;
+ flex-shrink: 0;
}
.previewHeader {
- display: flex;
- justify-content: space-between;
-
.previewHeaderTitle {
font-size: 16px;
font-weight: 600;
@@ -49,78 +48,178 @@
}
.messagePreview {
- border: 2px dashed #52c41a;
+ border: 1px solid #e8e8e8;
border-radius: 8px;
- padding: 15px;
+ padding: 16px;
+ background: #f5f5f5;
+ height: 400px;
+ overflow-y: auto;
- .messageBubble {
- min-height: 100px;
- background: #fff;
- border-radius: 6px;
- color: #666;
+ .messagePlaceholder {
+ color: #999;
font-size: 14px;
- line-height: 1.6;
-
- .currentEditingLabel {
- font-size: 14px;
- color: #52c41a;
- font-weight: bold;
- margin-bottom: 12px;
- }
-
- .messageText {
- color: #1a1a1a;
- white-space: pre-wrap;
- word-break: break-word;
- }
-
- .messagePlaceholder {
- color: #999;
- font-size: 14px;
- }
-
- .messageList {
- display: flex;
- flex-direction: column;
- gap: 0;
- }
-
- .messageItem {
- display: flex;
- gap: 12px;
- align-items: center;
- justify-content: space-between;
- padding: 10px 0;
- border-bottom: 1px solid #f0f0f0;
-
- &:last-child {
- border-bottom: none;
- }
-
- .messageText {
- flex: 1;
- }
-
- .messageAction {
- color: #ff4d4f;
- padding: 0;
- }
- }
+ text-align: center;
+ padding: 40px 20px;
}
- .scriptNameInput {
- margin-top: 12px;
+ .messageList {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ }
+
+ .messageBubbleWrapper {
+ display: flex;
+ flex-direction: column;
+ }
+
+ .messageBubble {
+ display: flex;
+ gap: 10px;
+ align-items: flex-start;
+
+ .messageAvatar {
+ width: 36px;
+ height: 36px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #fff;
+ font-size: 18px;
+ flex-shrink: 0;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ }
+
+ .messageContent {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ min-width: 0;
+
+ .messageBubbleInner {
+ background: #fff;
+ border-radius: 8px;
+ padding: 10px 14px;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+ position: relative;
+ max-width: 100%;
+
+ &::before {
+ content: "";
+ position: absolute;
+ left: -6px;
+ top: 12px;
+ width: 0;
+ height: 0;
+ border-top: 6px solid transparent;
+ border-bottom: 6px solid transparent;
+ border-right: 6px solid #fff;
+ }
+
+ .messageText {
+ color: #1a1a1a;
+ white-space: pre-wrap;
+ word-break: break-word;
+ line-height: 1.6;
+ font-size: 14px;
+ }
+
+ .messageMedia {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+
+ .messageMediaIcon {
+ font-size: 24px;
+ color: #1890ff;
+ margin-bottom: 4px;
+ }
+
+ .messageImage {
+ max-width: 100%;
+ max-height: 200px;
+ border-radius: 4px;
+ object-fit: contain;
+ background: #f5f5f5;
+ }
+
+ .messageFileInfo {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ }
+
+ .messageFileName {
+ color: #1a1a1a;
+ font-size: 14px;
+ font-weight: 500;
+ word-break: break-all;
+ }
+
+ .messageFileSize {
+ color: #999;
+ font-size: 12px;
+ }
+ }
+ }
+
+ .messageActions {
+ display: flex;
+ gap: 8px;
+ justify-content: flex-start;
+ padding-left: 4px;
+ opacity: 0.7;
+ transition: opacity 0.2s;
+
+ &:hover {
+ opacity: 1;
+ }
+
+ .aiRewriteButton {
+ color: #1890ff;
+ padding: 0;
+ font-size: 12px;
+ height: auto;
+ line-height: 1.5;
+
+ &:hover {
+ color: #40a9ff;
+ }
+ }
+
+ .messageAction {
+ color: #ff4d4f;
+ padding: 0;
+ font-size: 12px;
+ height: auto;
+ line-height: 1.5;
+
+ &:hover {
+ color: #ff7875;
+ }
+ }
+ }
+ }
}
}
- .savedScriptGroups {
- .contentLibrarySelector {
- margin-bottom: 20px;
- padding: 16px;
- background: #fff;
- border: 1px solid #e8e8e8;
- border-radius: 8px;
+ .pushContentHeader {
+ .pushContentTitle {
+ font-size: 16px;
+ font-weight: 600;
+ color: #1a1a1a;
+ margin-bottom: 16px;
}
+ }
+
+ .contentLibrarySelector {
+ margin-bottom: 20px;
+ padding: 16px;
+ background: #fff;
+ border: 1px solid #e8e8e8;
+ border-radius: 8px;
.contentLibraryHeader {
display: flex;
@@ -139,7 +238,9 @@
font-size: 12px;
color: #999;
}
+ }
+ .savedScriptGroups {
.scriptGroupHeaderRow {
display: flex;
align-items: center;
@@ -160,7 +261,7 @@
}
.scriptGroupList {
- max-height: 260px;
+ max-height: calc(100vh - 400px);
overflow-y: auto;
}
@@ -241,132 +342,358 @@
}
}
- .messageInputArea {
- .messageInput {
- margin-bottom: 12px;
- }
+ .scriptNameInput {
+ margin-top: 0;
+ }
- .attachmentButtons {
- display: flex;
- gap: 8px;
- margin-bottom: 12px;
- }
+ .createScriptGroupButton {
+ margin-top: 0;
+ }
- .aiRewriteSection {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 8px;
- gap: 12px;
-
- .aiRewriteToggle {
- display: flex;
- align-items: center;
- gap: 8px;
+ // AI改写弹窗样式
+ .aiRewriteModalWrap {
+ :global {
+ .ant-modal {
+ border-radius: 12px;
+ overflow: hidden;
}
- .aiRewriteLabel {
- font-size: 14px;
- color: #1a1a1a;
- }
-
- .aiRewriteInput {
- max-width: 240px;
- }
-
- .aiRewriteActions {
- display: flex;
- align-items: center;
- gap: 8px;
- }
-
- .aiRewriteButton {
- min-width: 96px;
+ .ant-modal-content {
+ border-radius: 12px;
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
}
}
}
- .settingsPanel {
- border: 1px solid #e8e8e8;
- border-radius: 8px;
- padding: 20px;
- background: #fafafa;
-
- .settingsTitle {
- font-size: 14px;
- font-weight: 500;
- color: #1a1a1a;
- margin-bottom: 16px;
- }
-
- .settingItem {
- margin-bottom: 20px;
-
- &:last-child {
- margin-bottom: 0;
+ .aiRewriteModal {
+ :global {
+ .ant-modal-header {
+ border-bottom: 1px solid #f0f0f0;
+ padding: 20px 24px;
+ background: linear-gradient(135deg, #fff 0%, #fafafa 100%);
+ border-radius: 12px 12px 0 0;
}
- .settingLabel {
- font-size: 14px;
- font-weight: 500;
- color: #1a1a1a;
- margin-bottom: 12px;
+ .ant-modal-body {
+ padding: 24px;
+ background: #ffffff;
+
+ // 确保内容区域的样式能够正确应用
+ // 原文消息内容区域
+ [class*="aiRewriteModalOriginalText"] {
+ padding: 20px !important;
+ background: #f5f5f5 !important;
+ min-height: 80px !important;
+ }
+
+ // 改写提示词输入框
+ [class*="aiRewriteModalTextArea"] {
+ textarea.ant-input {
+ background: #f5f5f5 !important;
+ min-height: 80px !important;
+ }
+ }
}
- .settingControl {
+ .ant-modal-footer {
+ border-top: 1px solid #f0f0f0;
+ padding: 16px 24px;
+ background: #fafafa;
display: flex;
- align-items: center;
- gap: 8px;
+ justify-content: flex-end;
+ gap: 12px;
+ border-radius: 0 0 12px 12px;
+ }
- span {
- font-size: 14px;
- color: #666;
- min-width: 80px;
+ .ant-modal-close {
+ color: #8c8c8c;
+ transition: color 0.3s;
+ top: 20px;
+ right: 24px;
+
+ &:hover {
+ color: #1a1a1a;
}
}
}
}
- .tagSection {
- .settingLabel {
- font-size: 14px;
- font-weight: 500;
- color: #1a1a1a;
- margin-bottom: 12px;
+ .aiRewriteModalTitle {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 18px;
+ font-weight: 600;
+ color: #1a1a1a;
+
+ .aiRewriteModalTitleIcon {
+ font-size: 20px;
}
}
- .pushPreview {
- border: 1px solid #e8e8e8;
- border-radius: 8px;
- padding: 20px;
- background: #f0f7ff;
+ .aiRewriteModalContent {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ }
- .previewTitle {
- font-size: 14px;
- font-weight: 500;
- color: #1a1a1a;
- margin-bottom: 12px;
+ .aiRewriteModalCompareSection {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ padding: 24px;
+ background: linear-gradient(135deg, #fafafa 0%, #f5f5f5 100%);
+ border-radius: 12px;
+ border: 1px solid #e8e8e8;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
+ }
+
+ .aiRewriteModalDivider {
+ height: 1px;
+ background: linear-gradient(
+ 90deg,
+ transparent 0%,
+ #d9d9d9 20%,
+ #d9d9d9 80%,
+ transparent 100%
+ );
+ margin: 12px 0;
+ position: relative;
+
+ &::before {
+ content: "";
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ width: 40px;
+ height: 1px;
+ background: #1890ff;
}
- ul {
- list-style: none;
- padding: 0;
- margin: 0;
+ &::after {
+ content: "→";
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ background: #fafafa;
+ padding: 0 8px;
+ color: #1890ff;
+ font-size: 12px;
+ }
+ }
- li {
+ .aiRewriteModalSection {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ margin-bottom: 0;
+ }
+
+ .aiRewriteModalSectionHeader {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 15px;
+ font-weight: 600;
+ color: #1a1a1a;
+ margin-bottom: 4px;
+
+ .aiRewriteModalSectionIcon {
+ font-size: 18px;
+ line-height: 1;
+ }
+
+ .aiRewriteModalLabel {
+ font-size: 15px;
+ font-weight: 600;
+ color: #1a1a1a;
+ letter-spacing: 0.3px;
+ }
+ }
+
+ .aiRewriteModalTextArea {
+ :global {
+ textarea.ant-input {
+ border-radius: 8px;
+ border: 1px solid #d9d9d9;
+ transition: all 0.3s;
font-size: 14px;
- color: #666;
- line-height: 1.8;
+ padding: 12px;
+ background: #f5f5f5 !important;
+ min-height: 80px !important;
+
+ &:hover {
+ border-color: #40a9ff;
+ background: #fafafa !important;
+ }
+
+ &:focus {
+ border-color: #1890ff;
+ box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.1);
+ background: #ffffff !important;
+ }
}
}
}
-}
-@media (max-width: 1200px) {
- .step3Content {
- .rightColumn {
- width: 350px;
+ .aiRewriteModalOriginalText {
+ padding: 20px !important;
+ background: #f5f5f5 !important;
+ border: 1px solid #d9d9d9;
+ border-left: 4px solid #8c8c8c;
+ border-radius: 8px;
+ font-size: 14px;
+ color: #333;
+ line-height: 1.8;
+ white-space: pre-wrap;
+ word-break: break-word;
+ position: relative;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
+ margin-top: 4px;
+ transition: all 0.3s ease;
+ min-height: 80px !important;
+
+ &:hover {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
+ border-color: #bfbfbf;
+ background: #fafafa !important;
+ }
+ }
+
+ .aiRewriteModalLoading {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 32px 20px;
+ background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
+ border: 1px solid #bae6fd;
+ border-radius: 8px;
+ gap: 12px;
+
+ .aiRewriteModalLoadingIcon {
+ font-size: 32px;
+ animation: pulse 1.5s ease-in-out infinite;
+ }
+
+ .aiRewriteModalLoadingText {
+ color: #1890ff;
+ font-size: 14px;
+ font-weight: 500;
+ }
+ }
+
+ @keyframes pulse {
+ 0%,
+ 100% {
+ opacity: 1;
+ transform: scale(1);
+ }
+ 50% {
+ opacity: 0.7;
+ transform: scale(1.05);
+ }
+ }
+
+ .aiRewriteModalResultText {
+ padding: 20px;
+ background: #f0f9ff;
+ border: 1px solid #91d5ff;
+ border-left: 4px solid #1890ff;
+ border-radius: 8px;
+ font-size: 14px;
+ color: #1a1a1a;
+ line-height: 1.8;
+ white-space: pre-wrap;
+ word-break: break-word;
+ max-height: 300px;
+ overflow-y: auto;
+ box-shadow: 0 2px 8px rgba(24, 144, 255, 0.15);
+ position: relative;
+ margin-top: 4px;
+
+ &::-webkit-scrollbar {
+ width: 6px;
+ }
+
+ &::-webkit-scrollbar-track {
+ background: #f0f0f0;
+ border-radius: 3px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: #bfbfbf;
+ border-radius: 3px;
+
+ &:hover {
+ background: #999;
+ }
+ }
+ }
+
+ .aiRewriteExecuteButton {
+ background-color: #ff6b35 !important;
+ border-color: #ff6b35 !important;
+ font-weight: 500;
+ height: 36px;
+ padding: 0 20px;
+ border-radius: 6px;
+ box-shadow: 0 2px 4px rgba(255, 107, 53, 0.2);
+ transition: all 0.3s ease;
+
+ &:hover:not(:disabled) {
+ background-color: #ff5722 !important;
+ border-color: #ff5722 !important;
+ box-shadow: 0 4px 8px rgba(255, 107, 53, 0.3);
+ transform: translateY(-1px);
+ }
+
+ &:active:not(:disabled) {
+ background-color: #e64a19 !important;
+ border-color: #e64a19 !important;
+ transform: translateY(0);
+ box-shadow: 0 2px 4px rgba(255, 107, 53, 0.2);
+ }
+
+ &:disabled {
+ background-color: #d9d9d9 !important;
+ border-color: #d9d9d9 !important;
+ color: #bfbfbf !important;
+ box-shadow: none;
+ }
+ }
+
+ .confirmButton {
+ background-color: #07c160 !important;
+ border-color: #07c160 !important;
+ font-weight: 500;
+ height: 36px;
+ padding: 0 20px;
+ border-radius: 6px;
+ box-shadow: 0 2px 4px rgba(7, 193, 96, 0.2);
+ transition: all 0.3s ease;
+
+ &:hover:not(:disabled) {
+ background-color: #06ad56 !important;
+ border-color: #06ad56 !important;
+ box-shadow: 0 4px 8px rgba(7, 193, 96, 0.3);
+ transform: translateY(-1px);
+ }
+
+ &:active:not(:disabled) {
+ background-color: #059c4d !important;
+ border-color: #059c4d !important;
+ transform: translateY(0);
+ box-shadow: 0 2px 4px rgba(7, 193, 96, 0.2);
+ }
+
+ &:disabled {
+ background-color: #d9d9d9 !important;
+ border-color: #d9d9d9 !important;
+ color: #bfbfbf !important;
+ box-shadow: none;
}
}
}
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.tsx
index 991f3a2a..82f109e7 100644
--- a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.tsx
@@ -5,8 +5,7 @@ import {
Button,
Checkbox,
Input,
- Select,
- Slider,
+ Modal,
Switch,
message as antdMessage,
} from "antd";
@@ -15,11 +14,15 @@ import {
DeleteOutlined,
PlusOutlined,
ReloadOutlined,
+ UserOutlined,
+ PictureOutlined,
+ FileOutlined,
+ SoundOutlined,
} from "@ant-design/icons";
import type { CheckboxChangeEvent } from "antd/es/checkbox";
import styles from "./index.module.scss";
-import { ContactItem, ScriptGroup } from "../../types";
+import { ContactItem, ScriptGroup, MessageItem } from "../../types";
import InputMessage from "./InputMessage/InputMessage";
import ContentLibrarySelector from "./ContentLibrarySelector";
import type { ContentItem } from "@/components/ContentSelection/data";
@@ -36,12 +39,6 @@ interface StepSendMessageProps {
targetLabel: string;
messageContent: string;
onMessageContentChange: (value: string) => void;
- friendInterval: [number, number];
- onFriendIntervalChange: (value: [number, number]) => void;
- messageInterval: [number, number];
- onMessageIntervalChange: (value: [number, number]) => void;
- selectedTag: string;
- onSelectedTagChange: (value: string) => void;
aiRewriteEnabled: boolean;
onAiRewriteToggle: (value: boolean) => void;
aiPrompt: string;
@@ -64,12 +61,6 @@ const StepSendMessage: React.FC = ({
targetLabel,
messageContent,
onMessageContentChange,
- friendInterval,
- onFriendIntervalChange,
- messageInterval,
- onMessageIntervalChange,
- selectedTag,
- onSelectedTagChange,
aiRewriteEnabled,
onAiRewriteToggle,
aiPrompt,
@@ -88,47 +79,111 @@ const StepSendMessage: React.FC = ({
const [savingScriptGroup, setSavingScriptGroup] = useState(false);
const [aiRewriting, setAiRewriting] = useState(false);
const [deletingGroupIds, setDeletingGroupIds] = useState([]);
+ const [aiRewriteModalVisible, setAiRewriteModalVisible] = useState(false);
+ const [aiRewriteModalIndex, setAiRewriteModalIndex] = useState(
+ null,
+ );
+ const [aiRewriteModalPrompt, setAiRewriteModalPrompt] = useState("");
+ const [aiRewritingMessage, setAiRewritingMessage] = useState(false);
+ const [aiRewriteResult, setAiRewriteResult] = useState(null);
+
+ // 将 string[] 转换为 MessageItem[]
+ const messagesToItems = useCallback((messages: string[]): MessageItem[] => {
+ return messages.map(msg => {
+ try {
+ // 尝试解析为 JSON(新格式)
+ const parsed = JSON.parse(msg);
+ if (parsed && typeof parsed === "object" && "type" in parsed) {
+ return parsed as MessageItem;
+ }
+ } catch {
+ // 解析失败,作为文本消息处理
+ }
+ // 旧格式:纯文本
+ return { type: "text", content: msg };
+ });
+ }, []);
+
+ // 将 MessageItem[] 转换为 string[]
+ const itemsToMessages = useCallback((items: MessageItem[]): string[] => {
+ return items.map(item => {
+ // 如果是纯文本消息,直接返回内容(保持向后兼容)
+ if (item.type === "text" && !item.fileName) {
+ return item.content;
+ }
+ // 其他类型序列化为 JSON
+ return JSON.stringify(item);
+ });
+ }, []);
+
+ // 内部维护的 MessageItem[] 状态
+ const [messageItems, setMessageItems] = useState(() =>
+ messagesToItems(currentScriptMessages),
+ );
+
+ // 当 currentScriptMessages 变化时,同步更新 messageItems
+ React.useEffect(() => {
+ setMessageItems(messagesToItems(currentScriptMessages));
+ }, [currentScriptMessages, messagesToItems]);
const handleAddMessage = useCallback(
- (content?: string, showSuccess?: boolean) => {
- const finalContent = (content ?? messageContent).trim();
- if (!finalContent) {
- antdMessage.warning("请输入消息内容");
- return;
+ (content?: string | MessageItem, showSuccess?: boolean) => {
+ let newItem: MessageItem;
+ if (typeof content === "string") {
+ const finalContent = (content || messageContent).trim();
+ if (!finalContent) {
+ antdMessage.warning("请输入消息内容");
+ return;
+ }
+ newItem = { type: "text", content: finalContent };
+ } else if (content && typeof content === "object") {
+ newItem = content;
+ } else {
+ const finalContent = messageContent.trim();
+ if (!finalContent) {
+ antdMessage.warning("请输入消息内容");
+ return;
+ }
+ newItem = { type: "text", content: finalContent };
}
- onCurrentScriptMessagesChange([...currentScriptMessages, finalContent]);
+
+ const newItems = [...messageItems, newItem];
+ setMessageItems(newItems);
+ onCurrentScriptMessagesChange(itemsToMessages(newItems));
onMessageContentChange("");
if (showSuccess) {
antdMessage.success("已添加消息内容");
}
},
[
- currentScriptMessages,
messageContent,
+ messageItems,
onCurrentScriptMessagesChange,
onMessageContentChange,
+ itemsToMessages,
],
);
const handleRemoveMessage = useCallback(
(index: number) => {
- const next = currentScriptMessages.filter((_, idx) => idx !== index);
- onCurrentScriptMessagesChange(next);
+ const next = messageItems.filter((_, idx) => idx !== index);
+ setMessageItems(next);
+ onCurrentScriptMessagesChange(itemsToMessages(next));
},
- [currentScriptMessages, onCurrentScriptMessagesChange],
+ [messageItems, onCurrentScriptMessagesChange, itemsToMessages],
);
const handleSaveScriptGroup = useCallback(async () => {
if (savingScriptGroup) {
return;
}
- if (currentScriptMessages.length === 0) {
+ if (messageItems.length === 0) {
antdMessage.warning("请先添加消息内容");
return;
}
const groupName =
currentScriptName.trim() || `话术组${savedScriptGroups.length + 1}`;
- const messages = [...currentScriptMessages];
+ const messages = itemsToMessages(messageItems);
const params: CreateContentLibraryParams = {
name: groupName,
sourceType: 1,
@@ -155,6 +210,7 @@ const StepSendMessage: React.FC = ({
messages,
};
onSavedScriptGroupsChange([...savedScriptGroups, newGroup]);
+ setMessageItems([]);
onCurrentScriptMessagesChange([]);
onCurrentScriptNameChange("");
onMessageContentChange("");
@@ -169,7 +225,7 @@ const StepSendMessage: React.FC = ({
}, [
aiPrompt,
aiRewriteEnabled,
- currentScriptMessages,
+ messageItems,
currentScriptName,
onCurrentScriptMessagesChange,
onCurrentScriptNameChange,
@@ -177,6 +233,7 @@ const StepSendMessage: React.FC = ({
onSavedScriptGroupsChange,
savedScriptGroups,
savingScriptGroup,
+ itemsToMessages,
]);
const handleAiRewrite = useCallback(async () => {
@@ -272,6 +329,138 @@ const StepSendMessage: React.FC = ({
onMessageContentChange,
]);
+ const handleOpenAiRewriteModal = useCallback((index: number) => {
+ setAiRewriteModalIndex(index);
+ setAiRewriteModalPrompt("");
+ setAiRewriteModalVisible(true);
+ }, []);
+
+ const handleCloseAiRewriteModal = useCallback(() => {
+ setAiRewriteModalVisible(false);
+ setAiRewriteModalIndex(null);
+ setAiRewriteModalPrompt("");
+ setAiRewriteResult(null);
+ }, []);
+
+ // 执行 AI 改写,获取结果但不立即应用
+ const handleAiRewriteExecute = useCallback(async () => {
+ if (aiRewriteModalIndex === null) {
+ return;
+ }
+ const trimmedPrompt = aiRewriteModalPrompt.trim();
+ if (!trimmedPrompt) {
+ antdMessage.warning("请输入改写提示词");
+ return;
+ }
+ const messageToRewrite = messageItems[aiRewriteModalIndex];
+ if (!messageToRewrite) {
+ antdMessage.error("消息不存在");
+ return;
+ }
+ // AI改写只支持文本消息
+ if (messageToRewrite.type !== "text") {
+ antdMessage.warning("AI改写仅支持文本消息");
+ return;
+ }
+ if (aiRewritingMessage) {
+ return;
+ }
+ try {
+ setAiRewritingMessage(true);
+ const response = await aiEditContent({
+ aiPrompt: trimmedPrompt,
+ content: messageToRewrite.content,
+ });
+ const normalizedResponse = response as {
+ content?: string;
+ contentAfter?: string;
+ contentFront?: string;
+ data?:
+ | string
+ | {
+ content?: string;
+ contentAfter?: string;
+ contentFront?: string;
+ };
+ result?: string;
+ };
+ const dataField = normalizedResponse?.data;
+ const dataContent =
+ typeof dataField === "string"
+ ? dataField
+ : (dataField?.content ?? undefined);
+ const dataContentAfter =
+ typeof dataField === "string" ? undefined : dataField?.contentAfter;
+ const dataContentFront =
+ typeof dataField === "string" ? undefined : dataField?.contentFront;
+
+ const primaryAfter =
+ normalizedResponse?.contentAfter ?? dataContentAfter ?? undefined;
+ const primaryFront =
+ normalizedResponse?.contentFront ?? dataContentFront ?? undefined;
+
+ let rewrittenContent = "";
+ if (typeof response === "string") {
+ rewrittenContent = response;
+ } else if (primaryAfter) {
+ rewrittenContent = primaryFront
+ ? `${primaryFront}\n${primaryAfter}`
+ : primaryAfter;
+ } else if (typeof normalizedResponse?.content === "string") {
+ rewrittenContent = normalizedResponse.content;
+ } else if (typeof dataContent === "string") {
+ rewrittenContent = dataContent;
+ } else if (typeof normalizedResponse?.result === "string") {
+ rewrittenContent = normalizedResponse.result;
+ } else if (primaryFront) {
+ rewrittenContent = primaryFront;
+ }
+ if (!rewrittenContent || typeof rewrittenContent !== "string") {
+ antdMessage.error("AI改写失败,请稍后重试");
+ return;
+ }
+ setAiRewriteResult(rewrittenContent.trim());
+ } catch (error) {
+ console.error("AI改写失败:", error);
+ antdMessage.error("AI改写失败,请稍后重试");
+ } finally {
+ setAiRewritingMessage(false);
+ }
+ }, [
+ aiRewriteModalIndex,
+ aiRewriteModalPrompt,
+ messageItems,
+ aiRewritingMessage,
+ ]);
+
+ // 确认并应用 AI 改写结果
+ const handleConfirmAiRewrite = useCallback(() => {
+ if (aiRewriteModalIndex === null || !aiRewriteResult) {
+ return;
+ }
+ const messageToRewrite = messageItems[aiRewriteModalIndex];
+ if (!messageToRewrite) {
+ antdMessage.error("消息不存在");
+ return;
+ }
+ const newItems = [...messageItems];
+ newItems[aiRewriteModalIndex] = {
+ ...messageToRewrite,
+ content: aiRewriteResult,
+ };
+ setMessageItems(newItems);
+ onCurrentScriptMessagesChange(itemsToMessages(newItems));
+ handleCloseAiRewriteModal();
+ antdMessage.success("AI改写完成");
+ }, [
+ aiRewriteModalIndex,
+ aiRewriteResult,
+ messageItems,
+ onCurrentScriptMessagesChange,
+ itemsToMessages,
+ handleCloseAiRewriteModal,
+ ]);
+
const handleApplyGroup = useCallback(
(group: ScriptGroup) => {
onCurrentScriptMessagesChange(group.messages);
@@ -352,61 +541,167 @@ const StepSendMessage: React.FC = ({
+ {/* 1. 模拟推送内容 */}
+
+ {/* 2. 消息列表 */}
+
+ {messageItems.length === 0 ? (
+
+ 开始添加消息内容...
+
+ ) : (
+
+ {messageItems.map((msgItem, index) => (
+
+
+
+
+
+
+
+ {msgItem.type === "text" && (
+
+ {msgItem.content}
+
+ )}
+ {msgItem.type === "image" && (
+
+
+

{
+ const target = e.target as HTMLImageElement;
+ target.style.display = "none";
+ }}
+ />
+ {msgItem.fileName && (
+
+ {msgItem.fileName}
+
+ )}
+
+ )}
+ {msgItem.type === "file" && (
+
+
+
+
+
+
+ {msgItem.fileName || "文件"}
+
+ {msgItem.fileSize && (
+
+ {msgItem.fileSize >= 1024 * 1024
+ ? `${(msgItem.fileSize / 1024 / 1024).toFixed(2)} MB`
+ : `${(msgItem.fileSize / 1024).toFixed(2)} KB`}
+
+ )}
+
+
+ )}
+ {msgItem.type === "audio" && (
+
+
+
+
+
+
+ {msgItem.fileName || "语音消息"}
+
+ {msgItem.durationMs && (
+
+ {Math.floor(msgItem.durationMs / 1000)}秒
+
+ )}
+
+
+ )}
+
+
+ {msgItem.type === "text" && (
+ }
+ onClick={() => handleOpenAiRewriteModal(index)}
+ className={styles.aiRewriteButton}
+ >
+ AI改写
+
+ )}
+ }
+ onClick={() => handleRemoveMessage(index)}
+ className={styles.messageAction}
+ />
+
+
+
+
+ ))}
+
+ )}
+
+
+ {/* 3. 消息输入组件 */}
+
+ handleAddMessage(value)}
+ onAddMessage={message => handleAddMessage(message)}
+ clearOnSend
+ placeholder="请输入内容"
+ hint={`按ENTER发送,按住CTRL+ENTER换行,已配置${savedScriptGroups.length}个话术组,已选择${selectedScriptGroupIds.length}个进行推送,已选${selectedContentLibraries.length}个内容库`}
+ />
+
+
+ {/* 4. 话术组标题 */}
+
+ onCurrentScriptNameChange(event.target.value)}
+ />
+
+
+ {/* 5. 创建话术组按钮 */}
+
}
onClick={handleSaveScriptGroup}
disabled={currentScriptMessages.length === 0 || savingScriptGroup}
loading={savingScriptGroup}
+ block
>
- 保存为话术组
+ 创建为话术组
-
-
-
当前编辑话术
- {currentScriptMessages.length === 0 ? (
-
- 开始添加消息内容...
-
- ) : (
-
- {currentScriptMessages.map((msg, index) => (
-
-
{msg}
-
}
- onClick={() => handleRemoveMessage(index)}
- className={styles.messageAction}
- />
-
- ))}
-
- )}
-
-
-
- onCurrentScriptNameChange(event.target.value)
- }
- />
-
-
+
-
- {/* 内容库选择组件 */}
+
+
+
已保存话术组 ({savedScriptGroups.length})
@@ -451,137 +746,119 @@ const StepSendMessage: React.FC = ({
/>
-
- {group.messages[0]}
- {group.messages.length > 1 && " ..."}
-
))
)}
-
-
-
handleAddMessage(value)}
- clearOnSend
- placeholder="请输入内容"
- hint={`按住CTRL+ENTER换行,已配置${savedScriptGroups.length}个话术组,已选择${selectedScriptGroupIds.length}个进行推送,已选${selectedContentLibraries.length}个内容库`}
- />
-
-
-
-
AI智能话术改写
-
- {aiRewriteEnabled && (
- onAiPromptChange(event.target.value)}
- className={styles.aiRewriteInput}
- />
- )}
-
-
-
-
- }
- onClick={handleAiRewrite}
- disabled={!aiRewriteEnabled}
- loading={aiRewriting}
- className={styles.aiRewriteButton}
- >
- AI改写
-
- }
- onClick={() => handleAddMessage(undefined, true)}
- >
- 添加
-
-
-
-
-
-
-
-
-
相关设置
-
-
好友间间隔
-
- 间隔时间(秒)
-
- onFriendIntervalChange(value as [number, number])
- }
- style={{ flex: 1, margin: "0 16px" }}
- />
-
- {friendInterval[0]} - {friendInterval[1]}
-
-
-
-
-
消息间间隔
-
- 间隔时间(秒)
-
- onMessageIntervalChange(value as [number, number])
- }
- style={{ flex: 1, margin: "0 16px" }}
- />
-
- {messageInterval[0]} - {messageInterval[1]}
-
-
-
-
-
-
-
完成打标签
-
-
-
-
-
推送预览
-
- - 推送账号: {selectedAccounts.length}个
- -
- 推送{targetLabel}: {selectedContacts.length}个
-
- - 话术组数: {savedScriptGroups.length}个
- - 随机推送: 否
- - 预计耗时: ~1分钟
-
-
+
+ {/* AI改写弹窗 */}
+
+ ✨
+ AI智能改写
+
+ }
+ open={aiRewriteModalVisible}
+ onCancel={handleCloseAiRewriteModal}
+ width={680}
+ footer={[
+
,
+
,
+
,
+ ]}
+ className={styles.aiRewriteModal}
+ wrapClassName={styles.aiRewriteModalWrap}
+ >
+
+ {/* 原文和结果对比区域 */}
+
+ {/* 原消息内容区域 */}
+ {aiRewriteModalIndex !== null && (
+
+
+ 📝
+
+ 1原消息内容
+
+
+
+ {messageItems[aiRewriteModalIndex]?.type === "text"
+ ? messageItems[aiRewriteModalIndex].content
+ : "非文本消息不支持AI改写"}
+
+
+ )}
+
+ {/* Loading 状态 */}
+ {aiRewritingMessage && (
+
+
⏳
+
+ AI正在改写中,请稍候...
+
+
+ )}
+
+ {/* 分隔线 */}
+ {aiRewriteModalIndex !== null && aiRewriteResult && (
+
+ )}
+
+ {/* 改写结果区域 */}
+ {aiRewriteResult && (
+
+
+ ✨
+ 改写结果
+
+
+ {aiRewriteResult}
+
+
+ )}
+
+
+ {/* 提示词输入区域 - 放在最下面 */}
+
+
+ 💡
+ 改写提示词
+
+
setAiRewriteModalPrompt(event.target.value)}
+ rows={3}
+ autoFocus
+ disabled={aiRewritingMessage}
+ className={styles.aiRewriteModalTextArea}
+ />
+
+
+
);
};
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/index.tsx
index 5ee5b4bd..6789c6a6 100644
--- a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/index.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/index.tsx
@@ -14,7 +14,13 @@ import { getCustomerList } from "@/pages/pc/ckbox/weChat/api";
import StepSelectAccount from "./components/StepSelectAccount";
import StepSelectContacts from "./components/StepSelectContacts";
import StepSendMessage from "./components/StepSendMessage";
-import { ContactItem, PushType, ScriptGroup } from "./types";
+import StepPushParams from "./components/StepPushParams";
+import {
+ ContactItem,
+ PushType,
+ ScriptGroup,
+ CreatePushTaskPayload,
+} from "./types";
import StepIndicator from "@/components/StepIndicator";
import type { ContentItem } from "@/components/ContentSelection/data";
import type { PoolSelectionItem } from "@/components/PoolSelection/data";
@@ -163,6 +169,20 @@ const CreatePushTask: React.FC = () => {
return;
}
setCurrentStep(3);
+ return;
+ }
+
+ if (currentStep === 3) {
+ // 验证推送内容
+ if (
+ currentScriptMessages.length === 0 &&
+ selectedScriptGroupIds.length === 0 &&
+ selectedContentLibraries.length === 0
+ ) {
+ message.warning("请至少添加一条消息、选择一个话术组或内容库");
+ return;
+ }
+ setCurrentStep(4);
}
};
@@ -184,9 +204,12 @@ const CreatePushTask: React.FC = () => {
if (creatingTask) {
return;
}
+
+ // ========== 1. 数据验证和准备 ==========
const selectedGroups = savedScriptGroups.filter(group =>
selectedScriptGroupIds.includes(group.id),
);
+
if (
currentScriptMessages.length === 0 &&
selectedGroups.length === 0 &&
@@ -195,19 +218,27 @@ const CreatePushTask: React.FC = () => {
message.warning("请添加话术内容、选择话术组或内容库");
return;
}
+
+ // 手动消息处理
const manualMessages = currentScriptMessages
.map(item => item.trim())
.filter(Boolean);
+
if (validPushType === "group-announcement" && manualMessages.length === 0) {
message.warning("请先填写公告内容");
return;
}
- const toNumberId = (value: unknown) => {
+
+ // ID 转换工具函数
+ const toNumberId = (value: unknown): number | null => {
+ if (value === null || value === undefined) return null;
const numeric = Number(value);
return Number.isFinite(numeric) && !Number.isNaN(numeric)
? numeric
: null;
};
+
+ // ========== 2. 内容库ID处理 ==========
const contentGroupIds = Array.from(
new Set(
[
@@ -220,6 +251,7 @@ const CreatePushTask: React.FC = () => {
].filter((id): id is number => id !== null),
),
);
+
if (
manualMessages.length === 0 &&
selectedGroups.length === 0 &&
@@ -228,6 +260,8 @@ const CreatePushTask: React.FC = () => {
message.warning("缺少有效的话术内容,请重新检查");
return;
}
+
+ // ========== 3. 账号ID处理 ==========
const ownerWechatIds = Array.from(
new Set(
selectedAccounts
@@ -235,47 +269,25 @@ const CreatePushTask: React.FC = () => {
.filter((id): id is number => id !== null),
),
);
+
if (ownerWechatIds.length === 0) {
message.error("缺少有效的推送账号信息");
return;
}
+
+ // ========== 4. 联系人ID处理 ==========
const selectedContactIds = Array.from(
new Set(
selectedContacts.map(contact => contact?.id).filter(isValidNumber),
),
);
+
if (selectedContactIds.length === 0) {
message.error("缺少有效的推送对象");
return;
}
- const friendIntervalMin = friendInterval[0];
- const friendIntervalMax = friendInterval[1];
- const messageIntervalMin = messageInterval[0];
- const messageIntervalMax = messageInterval[1];
- const trafficPoolIds = selectedTrafficPools
- .map(pool => pool.id)
- .filter(
- id => id !== undefined && id !== null && String(id).trim() !== "",
- );
- const { startTime, endTime } = DEFAULT_TIME_RANGE[validPushType];
- const maxPerDay =
- selectedContacts.length > 0
- ? selectedContacts.length
- : DEFAULT_MAX_PER_DAY[validPushType];
- const pushOrder = DEFAULT_PUSH_ORDER[validPushType];
- const normalizedPostPushTags =
- selectedTag.trim().length > 0
- ? [
- toNumberId(selectedTag) !== null
- ? (toNumberId(selectedTag) as number)
- : selectedTag,
- ]
- : [];
- const taskName =
- currentScriptName.trim() ||
- selectedGroups[0]?.name ||
- (manualMessages[0] ? manualMessages[0].slice(0, 20) : "") ||
- `推送任务-${Date.now()}`;
+
+ // ========== 5. 设备分组ID处理(好友推送必填) ==========
const deviceGroupIds = Array.from(
new Set(
selectedAccounts
@@ -283,84 +295,191 @@ const CreatePushTask: React.FC = () => {
.filter((id): id is number => id !== null),
),
);
+
if (validPushType === "friend-message" && deviceGroupIds.length === 0) {
message.error("缺少有效的推送设备分组");
return;
}
- const basePayload: Record
= {
- name: taskName,
- type: 3,
- autoStart: DEFAULT_AUTO_START[validPushType],
- status: 1,
- pushType: DEFAULT_PUSH_TYPE[validPushType],
+ // ========== 6. 流量池ID处理 ==========
+ const trafficPoolIds = selectedTrafficPools
+ .map(pool => {
+ const id = pool.id;
+ if (id === undefined || id === null) return null;
+ const strId = String(id).trim();
+ return strId !== "" ? strId : null;
+ })
+ .filter((id): id is string => id !== null);
+
+ // ========== 7. 时间范围 ==========
+ const { startTime, endTime } = DEFAULT_TIME_RANGE[validPushType];
+
+ // ========== 8. 每日最大推送数 ==========
+ const maxPerDay =
+ selectedContacts.length > 0
+ ? selectedContacts.length
+ : DEFAULT_MAX_PER_DAY[validPushType];
+
+ // ========== 9. 推送顺序 ==========
+ const pushOrder = DEFAULT_PUSH_ORDER[validPushType];
+
+ // ========== 10. 推送后标签处理 ==========
+ const postPushTags =
+ selectedTag.trim().length > 0
+ ? (() => {
+ const tagId = toNumberId(selectedTag);
+ return tagId !== null ? [tagId] : [];
+ })()
+ : [];
+
+ // ========== 11. 任务名称 ==========
+ const taskName =
+ currentScriptName.trim() ||
+ selectedGroups[0]?.name ||
+ (manualMessages[0] ? manualMessages[0].slice(0, 20) : "") ||
+ `推送任务-${Date.now()}`;
+
+ // ========== 12. 构建基础载荷 ==========
+ const basePayload: CreatePushTaskPayload = {
+ name: String(taskName).trim(),
+ type: 3, // 固定值:工作台类型
+ autoStart: DEFAULT_AUTO_START[validPushType] ? 1 : 0,
+ status: 1, // 固定值:启用
+ pushType: DEFAULT_PUSH_TYPE[validPushType] ? 1 : 0,
targetType: validPushType === "friend-message" ? 2 : 1,
groupPushSubType: validPushType === "group-announcement" ? 2 : 1,
- startTime,
- endTime,
- maxPerDay,
- pushOrder,
- friendIntervalMin,
- friendIntervalMax,
- messageIntervalMin,
- messageIntervalMax,
+ startTime: String(startTime),
+ endTime: String(endTime),
+ maxPerDay: Number(maxPerDay),
+ pushOrder: Number(pushOrder),
+ friendIntervalMin: Number(friendInterval[0]),
+ friendIntervalMax: Number(friendInterval[1]),
+ messageIntervalMin: Number(messageInterval[0]),
+ messageIntervalMax: Number(messageInterval[1]),
isRandomTemplate: selectedScriptGroupIds.length > 1 ? 1 : 0,
- contentGroups: contentGroupIds,
- postPushTags: normalizedPostPushTags,
- ownerWechatIds,
- enableAiRewrite: aiRewriteEnabled ? 1 : 0,
+ contentGroups: contentGroupIds.length > 0 ? contentGroupIds : [],
+ postPushTags: postPushTags,
+ ownerWechatIds: ownerWechatIds,
};
- if (trafficPoolIds.length > 0) {
- basePayload.trafficPools = trafficPoolIds;
- }
- if (validPushType === "friend-message") {
- basePayload.isLoop = 0;
- basePayload.deviceGroups = deviceGroupIds;
- }
- if (manualMessages.length > 0) {
- basePayload.manualMessages = manualMessages;
- if (currentScriptName.trim()) {
- basePayload.manualScriptName = currentScriptName.trim();
- }
- }
- if (selectedScriptGroupIds.length > 0) {
- basePayload.selectedScriptGroupIds = selectedScriptGroupIds;
- }
- if (aiRewriteEnabled && aiPrompt.trim()) {
- basePayload.aiRewritePrompt = aiPrompt.trim();
- }
- if (selectedGroups.length > 0) {
- basePayload.scriptGroups = selectedGroups.map(group => ({
- id: group.id,
- name: group.name,
- messages: group.messages,
- }));
- }
+
+ // ========== 13. 根据推送类型添加特定字段 ==========
if (validPushType === "friend-message") {
+ // 好友推送特有字段
+ // 注意:wechatFriends 必须是字符串数组,不是数字数组
basePayload.wechatFriends = Array.from(
new Set(
selectedContacts
- .map(contact => toNumberId(contact?.id))
- .filter((id): id is number => id !== null),
+ .map(contact => {
+ const id = toNumberId(contact?.id);
+ return id !== null ? String(id) : null;
+ })
+ .filter((id): id is string => id !== null),
),
);
- basePayload.targetType = 2;
+ basePayload.deviceGroups = deviceGroupIds; // 必填,数字数组
+ basePayload.isLoop = 0; // 固定值
+ basePayload.targetType = 2; // 确保是好友类型
+ basePayload.groupPushSubType = 1; // 固定为群群发
} else {
+ // 群推送特有字段
const groupIds = Array.from(
new Set(
selectedContacts
- .map(contact => toNumberId(contact.groupId ?? contact.id))
+ .map(contact => {
+ // 优先使用 groupId,如果没有则使用 id
+ const id = contact.groupId ?? contact.id;
+ return toNumberId(id);
+ })
.filter((id): id is number => id !== null),
),
);
- basePayload.wechatGroups = groupIds;
+
+ basePayload.wechatGroups = groupIds; // 数字数组
+ basePayload.targetType = 1; // 群类型
basePayload.groupPushSubType =
validPushType === "group-announcement" ? 2 : 1;
- basePayload.targetType = 1;
+
+ // 群公告特有字段
if (validPushType === "group-announcement") {
basePayload.announcementContent = manualMessages.join("\n");
}
}
+
+ // ========== 14. 可选字段处理 ==========
+ // 流量池(如果存在)
+ if (trafficPoolIds.length > 0) {
+ basePayload.trafficPools = trafficPoolIds; // 字符串数组
+ }
+
+ // 手动消息(如果存在)
+ if (manualMessages.length > 0) {
+ basePayload.manualMessages = manualMessages;
+ if (currentScriptName.trim()) {
+ basePayload.manualScriptName = String(currentScriptName.trim());
+ }
+ }
+
+ // 选中的话术组ID(如果存在)
+ if (selectedScriptGroupIds.length > 0) {
+ basePayload.selectedScriptGroupIds = selectedScriptGroupIds.map(id =>
+ String(id),
+ );
+ }
+
+ // AI改写相关(如果启用)
+ if (aiRewriteEnabled) {
+ basePayload.enableAiRewrite = 1;
+ if (aiPrompt.trim()) {
+ basePayload.aiRewritePrompt = String(aiPrompt.trim());
+ }
+ } else {
+ basePayload.enableAiRewrite = 0;
+ }
+
+ // 话术组对象(如果存在)
+ if (selectedGroups.length > 0) {
+ basePayload.scriptGroups = selectedGroups.map(group => ({
+ id: String(group.id),
+ name: String(group.name || ""),
+ messages: Array.isArray(group.messages)
+ ? group.messages.map(msg => String(msg))
+ : [],
+ }));
+ }
+
+ // ========== 15. 数据验证和提交 ==========
+ // 最终验证:确保必填字段存在
+ if (validPushType === "friend-message") {
+ if (
+ !Array.isArray(basePayload.deviceGroups) ||
+ basePayload.deviceGroups.length === 0
+ ) {
+ message.error("好友推送必须选择设备分组");
+ return;
+ }
+ if (
+ !Array.isArray(basePayload.wechatFriends) ||
+ basePayload.wechatFriends.length === 0
+ ) {
+ message.error("好友推送必须选择好友");
+ return;
+ }
+ } else {
+ if (
+ !Array.isArray(basePayload.wechatGroups) ||
+ basePayload.wechatGroups.length === 0
+ ) {
+ message.error("群推送必须选择群");
+ return;
+ }
+ }
+
+ // 提交前打印日志(开发环境)
+ if (process.env.NODE_ENV === "development") {
+ console.log("提交数据:", JSON.stringify(basePayload, null, 2));
+ }
+
+ // ========== 16. 提交请求 ==========
let hideLoading: ReturnType | undefined;
try {
setCreatingTask(true);
@@ -386,7 +505,7 @@ const CreatePushTask: React.FC = () => {
-
+
{
onBackClick={handleClose}
/>
-
+
+
+
>
}
footer={
@@ -434,6 +560,12 @@ const CreatePushTask: React.FC = () => {
{selectedContacts.length}个
)}
+ {currentStep === 4 && (
+
+ 推送账号: {selectedAccounts.length}个, 推送{step2Title}:{" "}
+ {selectedContacts.length}个
+
+ )}
{currentStep === 1 && (
@@ -458,6 +590,14 @@ const CreatePushTask: React.FC = () => {
>
)}
{currentStep === 3 && (
+ <>
+
+
+ >
+ )}
+ {currentStep === 4 && (
<>
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/types.ts b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/types.ts
index bd5eb0f1..f51d5ed4 100644
--- a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/types.ts
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/types.ts
@@ -20,8 +20,67 @@ export interface ContactItem {
extendFields?: Record;
}
+// 消息类型定义
+export type MessageType = "text" | "image" | "file" | "audio";
+
+export interface MessageItem {
+ type: MessageType;
+ content: string; // 文本内容或文件URL
+ // 文件相关字段
+ fileName?: string; // 文件名
+ fileSize?: number; // 文件大小(字节)
+ // 语音相关字段
+ durationMs?: number; // 语音时长(毫秒)
+}
+
export interface ScriptGroup {
id: string;
name: string;
- messages: string[];
+ messages: string[]; // 保持向后兼容,但实际应该使用 MessageItem[]
+}
+
+// 接口请求载荷类型定义
+export interface CreatePushTaskPayload {
+ // 基础字段
+ name: string;
+ type: 3; // 固定值:工作台类型
+ autoStart: 0 | 1;
+ status: 1; // 固定值:启用
+ pushType: 0 | 1; // 0=定时,1=立即
+ targetType: 1 | 2; // 1=群推送,2=好友推送
+ groupPushSubType: 1 | 2; // 1=群群发,2=群公告
+ startTime: string; // "HH:mm" 格式
+ endTime: string; // "HH:mm" 格式
+ maxPerDay: number;
+ pushOrder: 1 | 2; // 1=最早优先,2=最新优先
+ friendIntervalMin: number;
+ friendIntervalMax: number;
+ messageIntervalMin: number;
+ messageIntervalMax: number;
+ isRandomTemplate: 0 | 1;
+ contentGroups: number[]; // 内容库ID数组
+ postPushTags: number[]; // 推送后标签ID数组
+ ownerWechatIds: number[]; // 客服ID数组
+
+ // 好友推送特有字段
+ wechatFriends?: string[]; // 好友ID列表(字符串数组)
+ deviceGroups?: number[]; // 设备分组ID数组(好友推送时必填)
+ isLoop?: 0; // 固定值(好友推送时)
+
+ // 群推送特有字段
+ wechatGroups?: number[]; // 微信群ID数组
+ announcementContent?: string; // 群公告内容(群公告时必填)
+
+ // 可选字段
+ trafficPools?: string[]; // 流量池ID数组(字符串数组)
+ manualMessages?: string[]; // 手动消息数组
+ manualScriptName?: string; // 手动话术名称
+ selectedScriptGroupIds?: string[]; // 选中的话术组ID数组
+ enableAiRewrite?: 0 | 1; // 是否启用AI改写
+ aiRewritePrompt?: string; // AI改写提示词
+ scriptGroups?: Array<{
+ id: string;
+ name: string;
+ messages: string[];
+ }>; // 话术组对象数组
}
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/提示词.txt b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/提示词.txt
deleted file mode 100644
index df1db7a1..00000000
--- a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/提示词.txt
+++ /dev/null
@@ -1,79 +0,0 @@
-帮我对接数据,以下是传参实例,三种模式都是同一界面的。
-
-群发助手传参实例
-{
- "name": "群群发-新品宣传", // 任务名称
- "type": 3, // 工作台类型:3=群消息推送
- "autoStart": 1, // 保存后自动启动
- "status": 1, // 是否启用
- "pushType": 0, // 推送方式:0=定时,1=立即
- "targetType": 1, // 目标类型:1=群推送
- "groupPushSubType": 1, // 群推送子类型:1=群群发,2=群公告
- "startTime": "09:00", // 推送起始时间
- "endTime": "20:00", // 推送结束时间
- "maxPerDay": 200, // 每日最大推送群数
- "pushOrder": 1, // 推送顺序:1=最早优先,2=最新优先
- "wechatGroups": [102, 205, 318], // 选择的微信群 ID 列表
- "contentGroups": [11, 12], // 关联内容库 ID 列表
- "friendIntervalMin": 10, // 群间最小间隔(秒)
- "friendIntervalMax": 25, // 群间最大间隔(秒)
- "messageIntervalMin": 2, // 同一群消息间最小间隔(秒)
- "messageIntervalMax": 6, // 同一群消息间最大间隔(秒)
- "isRandomTemplate": 1, // 是否随机选择话术模板
- "postPushTags": [301, 302], // 推送完成后打的标签
- ownerWechatIds:[123123,1231231] //客服id
-}
-
-//群公告传参实例
-{
- "name": "群公告-双11活动", // 任务名称
- "type": 3, // 群消息推送
- "autoStart": 0, // 不自动启动
- "status": 1, // 启用
- "pushType": 1, // 立即推送
- "targetType": 1, // 群推送
- "groupPushSubType": 2, // 群公告
- "startTime": "08:30", // 开始时间
- "endTime": "18:30", // 结束时间
- "maxPerDay": 80, // 每日最大公告数
- "pushOrder": 2, // 最新优先
- "wechatGroups": [5021, 5026], // 公告目标群
- "announcementContent": "…", // 公告正文
- "enableAiRewrite": 1, // 启用 AI 改写
- "aiRewritePrompt": "保持活泼口吻…", // AI 改写提示词
- "contentGroups": [21], // 关联内容库
- "friendIntervalMin": 15, // 群间最小间隔
- "friendIntervalMax": 30, // 群间最大间隔
- "messageIntervalMin": 3, // 消息间最小间隔
- "messageIntervalMax": 9, // 消息间最大间隔
- "isRandomTemplate": 0, // 不随机模板
- "postPushTags": [], // 推送后标签
- ownerWechatIds:[123123,1231231] //客服id
-}
-
-//好友传参实例
-{
- "name": "好友私聊-新客转化", // 任务名称
- "type": 3, // 群消息推送
- "autoStart": 1, // 自动启动
- "status": 1, // 启用
- "pushType": 0, // 定时推送
- "targetType": 2, // 目标类型:2=好友推送
- "groupPushSubType": 1, // 固定为群群发(好友推送不支持公告)
- "startTime": "10:00", // 开始时间
- "endTime": "22:00", // 结束时间
- "maxPerDay": 150, // 每日最大推送好友数
- "pushOrder": 1, // 最早优先
- "wechatFriends": ["12312"], // 指定好友列表(可为空数组)
- "deviceGroups": [9001, 9002], // 必选:推送设备分组 ID
- "contentGroups": [41, 42], // 话术内容库
- "friendIntervalMin": 12, // 好友间最小间隔
- "friendIntervalMax": 28, // 好友间最大间隔
- "messageIntervalMin": 4, // 消息间最小间隔
- "messageIntervalMax": 10, // 消息间最大间隔
- "isRandomTemplate": 1, // 随机话术
- "postPushTags": [501], // 推送后标签
- ownerWechatIds:[123123,1231231] //客服id
-}
-
-请求接口是 queryWorkbenchCreate
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/api.ts b/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/api.ts
index a3627033..9b4a4abe 100644
--- a/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/api.ts
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/api.ts
@@ -6,8 +6,10 @@ export interface GetPushHistoryParams {
page?: number;
pageSize?: number;
keyword?: string;
- pushType?: string;
- status?: string;
+ pushTypeCode?: string; // 推送类型代码:friend, group, announcement
+ status?: string; // 状态:pending, completed, failed
+ workbenchId?: string;
+ [property: string]: any;
}
// 获取推送历史接口响应
@@ -27,11 +29,30 @@ export interface GetPushHistoryResponse {
*/
export interface GetGroupPushHistoryParams {
keyword?: string;
- limit: string;
- page: string;
+ limit?: string | number;
+ page?: string | number;
+ pageSize?: string | number;
+ pushTypeCode?: string;
+ status?: string;
workbenchId?: string;
[property: string]: any;
}
-export const getPushHistory = async (params: GetGroupPushHistoryParams) => {
- return request("/v1/workbench/group-push-history", params, "GET");
+
+export const getPushHistory = async (
+ params: GetGroupPushHistoryParams,
+): Promise => {
+ // 转换参数格式,确保 limit 和 page 是字符串
+ const requestParams: Record = {
+ ...params,
+ };
+
+ if (params.page !== undefined) {
+ requestParams.page = String(params.page);
+ }
+
+ if (params.pageSize !== undefined) {
+ requestParams.limit = String(params.pageSize);
+ }
+
+ return request("/v1/workbench/group-push-history", requestParams, "GET");
};
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/index.tsx
index bd2545a7..8b71fa3c 100644
--- a/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/index.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/index.tsx
@@ -15,30 +15,33 @@ import styles from "./index.module.scss";
const { Option } = Select;
-// 推送类型枚举
-export enum PushType {
- FRIEND_MESSAGE = "friend-message", // 好友消息
- GROUP_MESSAGE = "group-message", // 群消息
- GROUP_ANNOUNCEMENT = "group-announcement", // 群公告
+// 推送类型代码枚举
+export enum PushTypeCode {
+ FRIEND = "friend", // 好友消息
+ GROUP = "group", // 群消息
+ ANNOUNCEMENT = "announcement", // 群公告
}
// 推送状态枚举
export enum PushStatus {
+ PENDING = "pending", // 进行中
COMPLETED = "completed", // 已完成
- IN_PROGRESS = "in-progress", // 进行中
FAILED = "failed", // 失败
}
// 推送历史记录接口
export interface PushHistoryRecord {
- id: string;
- pushType: PushType;
- pushContent: string;
+ workbenchId: number;
+ taskName: string;
+ pushType: string; // 推送类型中文名称,如 "好友消息"
+ pushTypeCode: string; // 推送类型代码,如 "friend"
targetCount: number;
successCount: number;
- failureCount: number;
- status: PushStatus;
+ failCount: number;
+ status: string; // 状态代码,如 "pending"
+ statusText: string; // 状态中文名称,如 "进行中"
createTime: string;
+ contentLibraryName: string; // 内容库名称
}
const PushHistory: React.FC = () => {
@@ -59,8 +62,8 @@ const PushHistory: React.FC = () => {
try {
setLoading(true);
const params: any = {
- page,
- pageSize: pagination.pageSize,
+ page: String(page),
+ limit: String(pagination.pageSize),
};
if (searchValue.trim()) {
@@ -68,7 +71,7 @@ const PushHistory: React.FC = () => {
}
if (typeFilter !== "all") {
- params.pushType = typeFilter;
+ params.pushTypeCode = typeFilter;
}
if (statusFilter !== "all") {
@@ -157,13 +160,33 @@ const PushHistory: React.FC = () => {
};
// 获取推送类型标签
- const getPushTypeTag = (type: PushType) => {
- const typeMap = {
- [PushType.FRIEND_MESSAGE]: { text: "好友消息", color: "#666" },
- [PushType.GROUP_MESSAGE]: { text: "群消息", color: "#666" },
- [PushType.GROUP_ANNOUNCEMENT]: { text: "群公告", color: "#666" },
+ const getPushTypeTag = (pushType: string, pushTypeCode?: string) => {
+ // 优先使用中文名称,如果没有则根据代码映射
+ if (pushType) {
+ const colorMap: Record = {
+ 好友消息: "#1890ff",
+ 群消息: "#52c41a",
+ 群公告: "#722ed1",
+ };
+ return (
+
+ {pushType}
+
+ );
+ }
+ // 如果没有中文名称,根据代码映射
+ const codeMap: Record = {
+ [PushTypeCode.FRIEND]: { text: "好友消息", color: "#1890ff" },
+ [PushTypeCode.GROUP]: { text: "群消息", color: "#52c41a" },
+ [PushTypeCode.ANNOUNCEMENT]: { text: "群公告", color: "#722ed1" },
};
- const config = typeMap[type] || { text: "未知", color: "#666" };
+ const config =
+ pushTypeCode && codeMap[pushTypeCode]
+ ? codeMap[pushTypeCode]
+ : { text: pushType || "未知", color: "#666" };
return (
{config.text}
@@ -172,14 +195,31 @@ const PushHistory: React.FC = () => {
};
// 获取状态标签
- const getStatusTag = (status: PushStatus) => {
- const statusMap = {
+ const getStatusTag = (status: string, statusText?: string) => {
+ // 优先使用中文状态文本
+ const displayText = statusText || status;
+
+ // 根据状态代码或文本匹配
+ const statusMap: Record<
+ string,
+ { text: string; color: string; icon: React.ReactNode }
+ > = {
[PushStatus.COMPLETED]: {
text: "已完成",
color: "#52c41a",
icon: ,
},
- [PushStatus.IN_PROGRESS]: {
+ completed: {
+ text: "已完成",
+ color: "#52c41a",
+ icon: ,
+ },
+ [PushStatus.PENDING]: {
+ text: "进行中",
+ color: "#1890ff",
+ icon: ,
+ },
+ pending: {
text: "进行中",
color: "#1890ff",
icon: ,
@@ -189,12 +229,43 @@ const PushHistory: React.FC = () => {
color: "#ff4d4f",
icon: ,
},
+ failed: {
+ text: "失败",
+ color: "#ff4d4f",
+ icon: ,
+ },
};
- const config = statusMap[status] || {
- text: "未知",
- color: "#666",
- icon: null,
+
+ // 根据状态文本匹配
+ const textMap: Record<
+ string,
+ { text: string; color: string; icon: React.ReactNode }
+ > = {
+ 已完成: {
+ text: "已完成",
+ color: "#52c41a",
+ icon: ,
+ },
+ 进行中: {
+ text: "进行中",
+ color: "#1890ff",
+ icon: ,
+ },
+ 失败: {
+ text: "失败",
+ color: "#ff4d4f",
+ icon: ,
+ },
};
+
+ const config = textMap[displayText] ||
+ statusMap[status] ||
+ statusMap[status.toLowerCase()] || {
+ text: displayText,
+ color: "#666",
+ icon: null,
+ };
+
return (
{
dataIndex: "pushType",
key: "pushType",
width: 120,
- render: (type: PushType) => getPushTypeTag(type),
+ render: (pushType: string, record: PushHistoryRecord) =>
+ getPushTypeTag(pushType, record.pushTypeCode),
},
{
- title: "推送内容",
- dataIndex: "pushContent",
- key: "pushContent",
+ title: "任务名称",
+ dataIndex: "taskName",
+ key: "taskName",
ellipsis: true,
render: (text: string) => {text},
},
+ {
+ title: "内容库",
+ dataIndex: "contentLibraryName",
+ key: "contentLibraryName",
+ width: 150,
+ ellipsis: true,
+ render: (text: string) => (
+ {text || "-"}
+ ),
+ },
{
title: "目标数量",
dataIndex: "targetCount",
@@ -246,8 +328,8 @@ const PushHistory: React.FC = () => {
},
{
title: "失败数",
- dataIndex: "failureCount",
- key: "failureCount",
+ dataIndex: "failCount",
+ key: "failCount",
width: 100,
align: "center" as const,
render: (count: number) => (
@@ -260,7 +342,8 @@ const PushHistory: React.FC = () => {
key: "status",
width: 120,
align: "center" as const,
- render: (status: PushStatus) => getStatusTag(status),
+ render: (status: string, record: PushHistoryRecord) =>
+ getStatusTag(status, record.statusText),
},
{
title: "创建时间",
@@ -329,9 +412,9 @@ const PushHistory: React.FC = () => {
suffixIcon={▼}
>
-
-
-
+
+
+
@@ -353,7 +436,7 @@ const PushHistory: React.FC = () => {
columns={columns}
dataSource={dataSource}
loading={loading}
- rowKey="id"
+ rowKey="workbenchId"
pagination={false}
className={styles.dataTable}
/>
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/selectMap.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/selectMap.module.scss
new file mode 100644
index 00000000..b8f65724
--- /dev/null
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/selectMap.module.scss
@@ -0,0 +1,115 @@
+.selectMapContainer {
+ display: flex;
+ flex-direction: column;
+ height: 600px;
+ gap: 16px;
+}
+
+.searchArea {
+ flex-shrink: 0;
+ position: relative;
+ z-index: 10000;
+}
+
+.searchInput {
+ width: 100%;
+ position: relative;
+ z-index: 10000;
+}
+
+.searchResults {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ z-index: 10001;
+ background: #fff;
+ border: 1px solid #e8e8e8;
+ border-radius: 4px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ margin-top: 4px;
+ max-height: 300px;
+ overflow-y: auto;
+ pointer-events: auto;
+
+ :global(.ant-list-item) {
+ cursor: pointer;
+ padding: 12px 16px;
+ transition: background-color 0.2s;
+
+ &:hover {
+ background-color: #f5f5f5;
+ }
+ }
+}
+
+.mapArea {
+ flex: 1;
+ position: relative;
+ border: 1px solid #e8e8e8;
+ border-radius: 4px;
+ overflow: hidden;
+}
+
+.mapContainer {
+ width: 100%;
+ height: 100%;
+ min-height: 400px;
+}
+
+.loadingOverlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(255, 255, 255, 0.8);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 100;
+}
+
+.locationInfo {
+ flex-shrink: 0;
+ padding: 12px 16px;
+ background: #f5f5f5;
+ border-radius: 4px;
+ border: 1px solid #e8e8e8;
+}
+
+.locationLabel {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 14px;
+ font-weight: 500;
+ color: #1890ff;
+ margin-bottom: 8px;
+}
+
+.locationText {
+ font-size: 14px;
+ color: #333;
+ margin-bottom: 4px;
+ word-break: break-all;
+}
+
+.locationCoords {
+ font-size: 12px;
+ color: #999;
+ font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
+}
+
+.resultItem {
+ :global(.ant-list-item-meta-title) {
+ font-size: 14px;
+ color: #333;
+ margin-bottom: 4px;
+ }
+
+ :global(.ant-list-item-meta-description) {
+ font-size: 12px;
+ color: #999;
+ }
+}
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/selectMap.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/selectMap.tsx
new file mode 100644
index 00000000..093b3067
--- /dev/null
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/selectMap.tsx
@@ -0,0 +1,1016 @@
+import React, { useState, useEffect, useRef } from "react";
+import { Modal, Input, Button, List, message, Spin } from "antd";
+import { SearchOutlined, EnvironmentOutlined } from "@ant-design/icons";
+import { useWebSocketStore } from "@/store/module/websocket/websocket";
+import styles from "./selectMap.module.scss";
+
+// 声明腾讯地图类型(新版TMap API)
+declare global {
+ interface Window {
+ TMap: any;
+ geolocationRef: any; // 全局IP定位服务引用(TMap.service.IPLocation实例)
+ }
+}
+
+interface SelectMapProps {
+ visible: boolean;
+ onClose: () => void;
+ contract?: any;
+ addMessage?: (message: any) => void;
+ onConfirm?: (locationXml: string) => void;
+}
+
+interface SearchResult {
+ id: string;
+ title: string;
+ address: string;
+ location: {
+ lat: number;
+ lng: number;
+ };
+ adcode?: string;
+ city?: string;
+ district?: string;
+}
+
+interface LocationData {
+ x: string; // 纬度
+ y: string; // 经度
+ scale: string; // 缩放级别
+ label: string; // 地址标签
+ poiname: string; // POI名称
+ maptype: string; // 地图类型
+ poiid: string; // POI ID
+}
+
+const SelectMap: React.FC = ({
+ visible,
+ onClose,
+ contract,
+ addMessage,
+ onConfirm,
+}) => {
+ const [searchValue, setSearchValue] = useState("");
+ const [searchResults, setSearchResults] = useState([]);
+ const [isSearching, setIsSearching] = useState(false);
+ const [selectedLocation, setSelectedLocation] = useState(
+ null,
+ );
+ const [map, setMap] = useState(null);
+ const [isReverseGeocoding, setIsReverseGeocoding] = useState(false);
+ const [isLocating, setIsLocating] = useState(false);
+ const [tmapLoaded, setTmapLoaded] = useState(false);
+ const mapContainerRef = useRef(null);
+ const geocoderRef = useRef(null);
+ const suggestServiceRef = useRef(null);
+ const markerRef = useRef(null);
+ const { sendCommand } = useWebSocketStore.getState();
+
+ // XML转义函数,防止特殊字符破坏XML格式
+ const escapeXml = (str: string | undefined | null): string => {
+ if (!str) return "";
+ return String(str)
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """)
+ .replace(/'/g, "'");
+ };
+
+ // 加载腾讯地图SDK
+ useEffect(() => {
+ // 检查TMap是否已经加载
+ if (window.TMap) {
+ // 等待 API 完全初始化
+ const checkAPIReady = () => {
+ if (window.TMap && window.TMap.Map) {
+ console.log("腾讯地图SDK已加载,API 可用");
+ setTmapLoaded(true);
+ } else {
+ // 如果 API 还未完全初始化,等待一段时间后重试
+ setTimeout(checkAPIReady, 100);
+ }
+ };
+ checkAPIReady();
+ return;
+ }
+
+ // 动态加载腾讯地图SDK(使用与index.html相同的密钥)
+ const script = document.createElement("script");
+ script.src =
+ "https://map.qq.com/api/gljs?v=1.exp&libraries=service&key=7DZBZ-ZSRK3-QJN3W-O5VTV-4E2P6-7GFYX";
+ script.async = true;
+ script.onload = () => {
+ console.log("腾讯地图SDK脚本加载成功,等待 API 初始化...");
+ // 等待 API 完全初始化
+ const checkAPIReady = () => {
+ if (window.TMap && window.TMap.Map) {
+ console.log("腾讯地图SDK API 初始化完成");
+ setTmapLoaded(true);
+ } else {
+ // 如果 API 还未完全初始化,等待一段时间后重试(最多等待 5 秒)
+ setTimeout(checkAPIReady, 100);
+ }
+ };
+ // 延迟检查,给 API 一些初始化时间
+ setTimeout(checkAPIReady, 200);
+ };
+ script.onerror = () => {
+ console.error("腾讯地图SDK加载失败");
+ message.error("地图加载失败,请刷新页面重试");
+ };
+ document.head.appendChild(script);
+
+ return () => {
+ // 清理script标签
+ if (document.head.contains(script)) {
+ document.head.removeChild(script);
+ }
+ };
+ }, []);
+
+ // 检查 TMap API 是否可用(辅助函数)
+ const checkTMapAPI = () => {
+ if (!window.TMap) {
+ console.error("TMap 未加载");
+ return false;
+ }
+
+ // 检查 MultiMarker 是否可用
+ if (!window.TMap.MultiMarker) {
+ console.error("TMap.MultiMarker 不可用", {
+ TMap: window.TMap,
+ keys: Object.keys(window.TMap || {}),
+ });
+ return false;
+ }
+
+ // 检查 Style 是否存在(可能是构造函数、对象或命名空间)
+ // 注意:Style 可能不是构造函数,而是配置对象或命名空间
+ const hasStyle =
+ window.TMap.MultiMarker.Style !== undefined ||
+ window.TMap.MarkerStyle !== undefined;
+
+ if (!hasStyle) {
+ console.warn("TMap Style API 不可用,将使用配置对象方式", {
+ MultiMarker: window.TMap.MultiMarker,
+ MultiMarkerKeys: Object.keys(window.TMap.MultiMarker || {}),
+ MarkerStyle: window.TMap.MarkerStyle,
+ });
+ // 不返回 false,因为 MultiMarker 可能接受配置对象
+ }
+
+ return true;
+ };
+
+ // 创建标记样式(兼容不同的 API 版本)
+ const createMarkerStyle = (options: any) => {
+ // 检查 MultiMarker.Style 是否存在
+ if (window.TMap.MultiMarker?.Style) {
+ // 如果 Style 是函数(构造函数),使用 new
+ if (typeof window.TMap.MultiMarker.Style === "function") {
+ try {
+ return new window.TMap.MultiMarker.Style(options);
+ } catch (error) {
+ console.warn(
+ "使用 new MultiMarker.Style 失败,尝试直接返回配置对象:",
+ error,
+ );
+ // 如果构造函数调用失败,直接返回配置对象
+ return options;
+ }
+ } else {
+ // 如果 Style 不是函数,可能是对象或命名空间,直接返回配置对象
+ // MultiMarker 可能接受配置对象而不是 Style 实例
+ console.log("MultiMarker.Style 不是构造函数,直接使用配置对象");
+ return options;
+ }
+ }
+ // 尝试 MarkerStyle
+ if (window.TMap.MarkerStyle) {
+ if (typeof window.TMap.MarkerStyle === "function") {
+ try {
+ return new window.TMap.MarkerStyle(options);
+ } catch (error) {
+ console.warn(
+ "使用 new MarkerStyle 失败,尝试直接返回配置对象:",
+ error,
+ );
+ return options;
+ }
+ } else {
+ return options;
+ }
+ }
+ // 如果都不存在,直接返回配置对象(让 MultiMarker 自己处理)
+ console.warn("未找到 Style API,直接使用配置对象");
+ return options;
+ };
+
+ // 初始化地图
+ useEffect(() => {
+ if (visible && mapContainerRef.current && tmapLoaded && window.TMap) {
+ console.log("开始初始化地图");
+ console.log("TMap API 检查:", {
+ TMap: !!window.TMap,
+ MultiMarker: !!window.TMap.MultiMarker,
+ MultiMarkerStyle: !!window.TMap.MultiMarker?.Style,
+ MarkerStyle: !!window.TMap.MarkerStyle,
+ });
+
+ // 检查容器尺寸,确保容器有有效的宽高
+ const checkContainerSize = () => {
+ if (!mapContainerRef.current) return false;
+ const rect = mapContainerRef.current.getBoundingClientRect();
+ return rect.width > 0 && rect.height > 0;
+ };
+
+ let mapInstance: any = null;
+ let handleMapClickFn: ((evt: any) => void) | null = null;
+ let delayTimer: NodeJS.Timeout | null = null;
+ let isMounted = true; // 标记弹窗是否仍然打开
+
+ // 初始化地图函数(使用箭头函数避免函数声明位置问题)
+ const initializeMap = () => {
+ if (!mapContainerRef.current) return;
+
+ try {
+ // 再次检查容器尺寸
+ const rect = mapContainerRef.current.getBoundingClientRect();
+ if (rect.width <= 0 || rect.height <= 0) {
+ console.error("地图容器尺寸无效:", rect);
+ message.error("地图容器尺寸无效,请刷新页面重试");
+ return;
+ }
+
+ // 创建地图实例
+ const center = new window.TMap.LatLng(39.908823, 116.39747); // 默认北京
+ mapInstance = new window.TMap.Map(mapContainerRef.current, {
+ center: center,
+ zoom: 13,
+ rotation: 0,
+ pitch: 0,
+ });
+
+ setMap(mapInstance);
+
+ // 创建地理编码服务(用于反向地理编码)
+ geocoderRef.current = new window.TMap.service.Geocoder();
+
+ // 创建IP定位服务
+ window.geolocationRef = new window.TMap.service.IPLocation();
+
+ // 创建搜索建议服务
+ suggestServiceRef.current = new window.TMap.service.Suggestion({
+ pageSize: 10,
+ autoExtend: true,
+ });
+
+ // 地图点击事件处理函数
+ handleMapClickFn = (evt: any) => {
+ try {
+ // 检查弹窗是否仍然打开,以及必要的API是否可用
+ if (!isMounted || !mapInstance || !mapContainerRef.current) {
+ return;
+ }
+
+ // 检查 TMap API 是否可用
+ if (!checkTMapAPI()) {
+ console.error("TMap API 不可用,无法创建标记点");
+ message.warning("地图标记功能不可用,请刷新页面重试");
+ return;
+ }
+
+ const lat = evt.latLng.getLat();
+ const lng = evt.latLng.getLng();
+
+ console.log("地图点击:", lat, lng);
+
+ // 更新标记点
+ if (markerRef.current) {
+ markerRef.current.setMap(null);
+ markerRef.current = null;
+ }
+
+ // 创建标记样式
+ const markerStyle = createMarkerStyle({
+ width: 25,
+ height: 35,
+ anchor: { x: 12, y: 35 },
+ src: "https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/markerDefault.png",
+ });
+
+ // 创建新标记
+ const newMarker = new window.TMap.MultiMarker({
+ id: "marker-layer",
+ map: mapInstance,
+ styles: {
+ marker: markerStyle,
+ },
+ geometries: [
+ {
+ id: "selected-marker",
+ styleId: "marker",
+ position: new window.TMap.LatLng(lat, lng),
+ properties: {
+ title: "选中位置",
+ },
+ },
+ ],
+ });
+
+ markerRef.current = newMarker;
+
+ // 设置基本位置信息(防止白屏)
+ // 经纬度格式化为6位小数(微信位置消息标准格式)
+ setSelectedLocation({
+ x: lat.toString(),
+ y: lng.toString(),
+ scale: "16",
+ label: `${lat}, ${lng}`,
+ poiname: "选中位置",
+ maptype: "0",
+ poiid: "",
+ });
+
+ // 反向地理编码获取地址
+ if (!isMounted || !geocoderRef.current) {
+ return;
+ }
+
+ setIsReverseGeocoding(true);
+ geocoderRef.current
+ .getAddress({ location: new window.TMap.LatLng(lat, lng) })
+ .then((result: any) => {
+ // 检查弹窗是否仍然打开
+ if (!isMounted) {
+ return;
+ }
+ setIsReverseGeocoding(false);
+ console.log("反向地理编码结果:", result);
+
+ try {
+ if (result && result.result) {
+ const resultData = result.result;
+ const address = resultData.address || "";
+ const addressComponent =
+ resultData.address_component || {};
+ const formattedAddresses =
+ resultData.formatted_addresses || {};
+
+ // 构建地址标签
+ let addressLabel =
+ formattedAddresses.recommend ||
+ formattedAddresses.rough ||
+ address;
+
+ if (!addressLabel) {
+ const parts = [];
+ if (addressComponent.province)
+ parts.push(addressComponent.province);
+ if (addressComponent.city)
+ parts.push(addressComponent.city);
+ if (addressComponent.district)
+ parts.push(addressComponent.district);
+ if (addressComponent.street)
+ parts.push(addressComponent.street);
+ if (addressComponent.street_number)
+ parts.push(addressComponent.street_number);
+ addressLabel = parts.join("");
+ }
+
+ if (!addressLabel) {
+ addressLabel = `${lat}, ${lng}`;
+ }
+
+ // 经纬度格式化为6位小数(微信位置消息标准格式)
+ setSelectedLocation({
+ x: lat.toString(),
+ y: lng.toString(),
+ scale: "16",
+ label: addressLabel,
+ poiname: addressComponent.street || "未知位置",
+ maptype: "0",
+ poiid: resultData.poi_id || "",
+ });
+ } else {
+ message.warning("获取详细地址信息失败,将使用坐标显示");
+ }
+ } catch (error) {
+ console.error("解析地址信息错误:", error);
+ message.warning("解析地址信息失败,将使用坐标显示");
+ }
+ })
+ .catch((error: any) => {
+ // 检查弹窗是否仍然打开
+ if (!isMounted) {
+ return;
+ }
+ setIsReverseGeocoding(false);
+ console.error("反向地理编码错误:", error);
+ message.warning("获取详细地址信息失败,将使用坐标显示");
+ });
+ } catch (error) {
+ console.error("地图点击处理错误:", error);
+ message.error("处理地图点击时出错,请重试");
+ }
+ };
+
+ // 绑定地图点击事件
+ mapInstance.on("click", handleMapClickFn);
+
+ // 使用腾讯地图API初始化用户位置
+ const initializeUserLocation = (
+ lat: number,
+ lng: number,
+ isDefault: boolean = false,
+ ) => {
+ // 检查弹窗是否仍然打开,以及必要的API是否可用
+ if (!isMounted || !mapInstance || !mapContainerRef.current) {
+ console.log("弹窗已关闭或地图实例无效,跳过初始化位置");
+ return;
+ }
+
+ // 检查 TMap API 是否可用
+ if (!checkTMapAPI()) {
+ console.error("TMap API 不可用,无法创建标记点");
+ message.warning("地图标记功能不可用,请刷新页面重试");
+ return;
+ }
+
+ // 创建位置对象
+ let userLocation: any = null;
+ try {
+ console.log(isDefault ? "使用默认位置:" : "用户位置:", lat, lng);
+
+ // 移动地图中心到位置
+ userLocation = new window.TMap.LatLng(lat, lng);
+ mapInstance.setCenter(userLocation);
+ mapInstance.setZoom(16);
+
+ // 添加标记点
+ if (markerRef.current) {
+ markerRef.current.setMap(null);
+ markerRef.current = null;
+ }
+
+ // 创建标记样式
+ const markerStyle = createMarkerStyle({
+ width: 25,
+ height: 35,
+ anchor: { x: 12, y: 35 },
+ src: "https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/markerDefault.png",
+ });
+
+ const newMarker = new window.TMap.MultiMarker({
+ id: "marker-layer",
+ map: mapInstance,
+ styles: {
+ marker: markerStyle,
+ },
+ geometries: [
+ {
+ id: "user-location",
+ styleId: "marker",
+ position: userLocation,
+ properties: {
+ title: isDefault ? "默认位置" : "当前位置",
+ },
+ },
+ ],
+ });
+
+ markerRef.current = newMarker;
+ } catch (error) {
+ console.error("创建标记点失败:", error);
+ // 即使创建标记失败,也设置基本的位置信息
+ // 经纬度格式化为6位小数(微信位置消息标准格式)
+ setSelectedLocation({
+ x: lat.toString(),
+ y: lng.toString(),
+ scale: "16",
+ label: `${lat}, ${lng}`,
+ poiname: isDefault ? "默认位置" : "当前位置",
+ maptype: "0",
+ poiid: "",
+ });
+ return;
+ }
+
+ // 使用腾讯地图服务获取该位置的地址信息
+ if (!isMounted || !geocoderRef.current || !userLocation) {
+ return;
+ }
+
+ setIsReverseGeocoding(true);
+ geocoderRef.current
+ .getAddress({ location: userLocation })
+ .then((result: any) => {
+ // 检查弹窗是否仍然打开
+ if (!isMounted) {
+ return;
+ }
+ setIsReverseGeocoding(false);
+ if (result && result.result) {
+ const resultData = result.result;
+ const formattedAddresses =
+ resultData.formatted_addresses || {};
+ const addressComponent = resultData.address_component || {};
+
+ const addressLabel =
+ formattedAddresses.recommend ||
+ formattedAddresses.rough ||
+ resultData.address ||
+ `${lat}, ${lng}`;
+
+ // 经纬度格式化为6位小数(微信位置消息标准格式)
+ setSelectedLocation({
+ x: lat.toString(),
+ y: lng.toString(),
+ scale: "16",
+ label: addressLabel,
+ poiname:
+ addressComponent.street ||
+ (isDefault ? "默认位置" : "当前位置"),
+ maptype: "0",
+ poiid: resultData.poi_id || "",
+ });
+ }
+ })
+ .catch((error: any) => {
+ // 检查弹窗是否仍然打开
+ if (!isMounted) {
+ return;
+ }
+ setIsReverseGeocoding(false);
+ console.error("获取地址信息失败:", error);
+ // 即使获取地址失败,也设置基本的位置信息
+ // 经纬度格式化为6位小数(微信位置消息标准格式)
+ setSelectedLocation({
+ x: lat.toString(),
+ y: lng.toString(),
+ scale: "16",
+ label: `${lat}, ${lng}`,
+ poiname: isDefault ? "默认位置" : "当前位置",
+ maptype: "0",
+ poiid: "",
+ });
+ });
+ };
+
+ // 使用腾讯地图IP定位获取用户位置
+ setIsLocating(true);
+ try {
+ if (window.geolocationRef) {
+ window.geolocationRef
+ .locate()
+ .then((result: any) => {
+ // 检查弹窗是否仍然打开
+ if (!isMounted) {
+ return;
+ }
+ setIsLocating(false);
+ console.log("IP定位结果:", result);
+ if (result && result.result && result.result.location) {
+ const { lat, lng } = result.result.location;
+ // message.info("已定位到您的大致位置");
+ initializeUserLocation(lat, lng, false);
+ } else {
+ // IP定位失败:使用默认位置
+ message.info("无法获取您的位置,已定位到北京");
+ // 使用默认位置(北京市)
+ initializeUserLocation(39.908823, 116.39747, true);
+ }
+ })
+ .catch((error: any) => {
+ // 检查弹窗是否仍然打开
+ if (!isMounted) {
+ return;
+ }
+ setIsLocating(false);
+ console.error("IP定位失败:", error);
+ message.info("无法获取您的位置,已定位到北京");
+ // 使用默认位置(北京市)
+ initializeUserLocation(39.908823, 116.39747, true);
+ });
+ } else {
+ // IP定位服务未初始化:使用默认位置
+ setIsLocating(false);
+ message.info("无法获取您的位置,已定位到北京");
+ // 使用默认位置(北京市)
+ initializeUserLocation(39.908823, 116.39747, true);
+ }
+ } catch (error) {
+ // 捕获任何可能的错误,防止白屏
+ console.error("定位过程中发生错误:", error);
+ if (isMounted) {
+ setIsLocating(false);
+ message.error("定位服务出现异常,已定位到北京");
+ // 使用默认位置(北京市)
+ initializeUserLocation(39.908823, 116.39747, true);
+ }
+ }
+ } catch (error) {
+ console.error("初始化地图时出错:", error);
+ message.error("地图加载失败,请刷新页面重试");
+ setIsLocating(false);
+ }
+ };
+
+ // 使用 requestAnimationFrame 确保容器尺寸正确后再初始化
+ const initTimer = requestAnimationFrame(() => {
+ // 再次检查容器尺寸
+ if (!checkContainerSize()) {
+ console.log("容器尺寸无效,延迟初始化地图");
+ delayTimer = setTimeout(() => {
+ if (checkContainerSize() && mapContainerRef.current) {
+ initializeMap();
+ } else {
+ console.error("地图容器尺寸仍然无效");
+ message.error("地图容器初始化失败,请刷新页面重试");
+ }
+ }, 100);
+ return;
+ }
+
+ // 容器尺寸有效,立即初始化
+ initializeMap();
+ });
+
+ // 清理函数
+ return () => {
+ // 标记弹窗已关闭
+ isMounted = false;
+ // 取消 requestAnimationFrame
+ cancelAnimationFrame(initTimer);
+ // 清理延迟定时器
+ if (delayTimer) {
+ clearTimeout(delayTimer);
+ }
+ // 清理地图事件监听
+ if (mapInstance && handleMapClickFn) {
+ try {
+ mapInstance.off("click", handleMapClickFn);
+ } catch (error) {
+ console.error("清理地图事件监听失败:", error);
+ }
+ }
+ // 清理地图实例
+ if (mapInstance) {
+ try {
+ mapInstance.destroy();
+ } catch (error) {
+ console.error("销毁地图实例失败:", error);
+ }
+ mapInstance = null;
+ }
+ // 清理标记点
+ if (markerRef.current) {
+ try {
+ markerRef.current.setMap(null);
+ } catch (error) {
+ console.error("清理标记点失败:", error);
+ }
+ markerRef.current = null;
+ }
+ // 重置地图状态
+ setMap(null);
+ };
+ }
+ }, [visible, tmapLoaded]);
+
+ // 搜索地址(获取搜索建议)
+ const handleSearch = () => {
+ try {
+ if (!searchValue.trim()) {
+ message.warning("请输入搜索关键词");
+ return;
+ }
+
+ if (!suggestServiceRef.current) {
+ message.error("搜索服务未初始化,请刷新页面重试");
+ return;
+ }
+
+ setIsSearching(true);
+ suggestServiceRef.current
+ .getSuggestions({
+ keyword: searchValue,
+ location: map ? map.getCenter() : undefined,
+ })
+ .then((result: any) => {
+ setIsSearching(false);
+ console.log("搜索建议结果:", result);
+
+ if (result && result.data && result.data.length > 0) {
+ const searchResults = result.data.map((item: any) => ({
+ id: item.id,
+ title: item.title || item.name || "",
+ address: item.address || "",
+ location: {
+ lat: item.location.lat,
+ lng: item.location.lng,
+ },
+ adcode: item.adcode || "",
+ city: item.city || "",
+ district: item.district || "",
+ }));
+ setSearchResults(searchResults);
+ } else {
+ setSearchResults([]);
+ message.info("未找到相关地址");
+ }
+ })
+ .catch((error: any) => {
+ setIsSearching(false);
+ console.error("搜索失败:", error);
+ message.error("搜索失败,请重试");
+ // 确保搜索状态被重置
+ setSearchResults([]);
+ });
+ } catch (error) {
+ setIsSearching(false);
+ console.error("搜索处理错误:", error);
+ message.error("搜索过程中出错,请重试");
+ setSearchResults([]);
+ }
+ };
+
+ // 选择搜索结果
+ const handleSelectResult = (result: SearchResult) => {
+ try {
+ if (!map) {
+ message.error("地图未初始化,请刷新页面重试");
+ return;
+ }
+
+ // 检查 TMap API 是否可用
+ if (!checkTMapAPI()) {
+ console.error("TMap API 不可用,无法创建标记点");
+ message.error("地图API不可用,请刷新页面重试");
+ return;
+ }
+
+ const lat = result.location.lat;
+ const lng = result.location.lng;
+
+ console.log("选择搜索结果:", result);
+
+ // 移动地图中心
+ map.setCenter(new window.TMap.LatLng(lat, lng));
+ map.setZoom(16);
+
+ // 更新标记点
+ if (markerRef.current) {
+ markerRef.current.setMap(null);
+ markerRef.current = null;
+ }
+
+ // 创建标记样式
+ const markerStyle = createMarkerStyle({
+ width: 25,
+ height: 35,
+ anchor: { x: 12, y: 35 },
+ src: "https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/markerDefault.png",
+ });
+
+ const newMarker = new window.TMap.MultiMarker({
+ id: "marker-layer",
+ map: map,
+ styles: {
+ marker: markerStyle,
+ },
+ geometries: [
+ {
+ id: "selected-poi",
+ styleId: "marker",
+ position: new window.TMap.LatLng(lat, lng),
+ properties: {
+ title: result.title,
+ },
+ },
+ ],
+ });
+
+ markerRef.current = newMarker;
+
+ // 设置选中的位置信息
+ // 经纬度格式化为6位小数(微信位置消息标准格式)
+ setSelectedLocation({
+ x: lat.toString(),
+ y: lng.toString(),
+ scale: "16",
+ label: result.address || result.title,
+ poiname: result.title || "",
+ maptype: "0",
+ poiid: result.id || "",
+ });
+
+ // 清空搜索结果
+ setSearchResults([]);
+ setSearchValue("");
+ } catch (error) {
+ console.error("选择搜索结果错误:", error);
+ message.error("选择位置时出错,请重试");
+ }
+ };
+
+ // 确认选择
+ const handleConfirm = () => {
+ try {
+ if (!selectedLocation) {
+ message.warning("请先选择位置");
+ return;
+ }
+
+ // 转义XML特殊字符,确保格式正确
+ // 注意:经纬度在存储时已经格式化为6位小数,直接使用即可
+ const escapedLabel = escapeXml(selectedLocation.label);
+ const escapedPoiname = escapeXml(selectedLocation.poiname);
+ const scale = selectedLocation.scale || "16";
+ const maptype = selectedLocation.maptype || "0";
+ const poiid = escapeXml(selectedLocation.poiid || "");
+
+ // 生成XML格式的位置信息(格式与正确示例保持一致)
+ const locationXml =
+ '';
+
+ // 如果有onConfirm回调,调用它
+ if (onConfirm) {
+ onConfirm(locationXml);
+ }
+
+ // 如果有addMessage和contract,发送位置消息
+ if (addMessage && contract) {
+ const messageId = +Date.now();
+ const localMessage = {
+ id: messageId,
+ wechatAccountId: contract.wechatAccountId,
+ wechatFriendId: contract?.chatroomId ? 0 : contract.id,
+ wechatChatroomId: contract?.chatroomId ? contract.id : 0,
+ tenantId: 0,
+ accountId: 0,
+ synergyAccountId: 0,
+ content: locationXml,
+ msgType: 48, // 位置消息类型
+ msgSubType: 0,
+ msgSvrId: "",
+ isSend: true,
+ createTime: new Date().toISOString(),
+ isDeleted: false,
+ deleteTime: "",
+ sendStatus: 1,
+ wechatTime: Date.now(),
+ origin: 0,
+ msgId: 0,
+ recalled: false,
+ seq: messageId,
+ };
+
+ addMessage(localMessage);
+ console.log(locationXml);
+
+ // 发送消息到服务器
+ sendCommand("CmdSendMessage", {
+ wechatAccountId: contract.wechatAccountId,
+ wechatChatroomId: contract?.chatroomId ? contract.id : 0,
+ wechatFriendId: contract?.chatroomId ? 0 : contract.id,
+ msgSubType: 0,
+ msgType: 48,
+ content: locationXml,
+ seq: messageId,
+ });
+ }
+
+ // 关闭弹窗并重置状态
+ handleClose();
+ } catch (error) {
+ console.error("确认位置时出错:", error);
+ message.error("发送位置信息时出错,请重试");
+ }
+ };
+
+ // 关闭弹窗
+ const handleClose = () => {
+ setSearchValue("");
+ setSearchResults([]);
+ setSelectedLocation(null);
+ if (markerRef.current) {
+ markerRef.current.setMap(null);
+ markerRef.current = null;
+ }
+ setIsSearching(false);
+ setIsReverseGeocoding(false);
+ setIsLocating(false);
+ onClose();
+ };
+
+ return (
+
+ 取消
+ ,
+ ,
+ ]}
+ >
+
+ {/* 搜索区域 */}
+
+
setSearchValue(e.target.value)}
+ onPressEnter={handleSearch}
+ prefix={
}
+ suffix={
+
+ }
+ className={styles.searchInput}
+ />
+
+ {/* 搜索结果列表 */}
+ {searchResults.length > 0 && (
+
+ (
+ handleSelectResult(item)}
+ >
+ }
+ title={item.title}
+ description={item.address}
+ />
+
+ )}
+ />
+
+ )}
+
+
+ {/* 地图区域 */}
+
+
+ {/* 选中位置信息 */}
+ {selectedLocation && (
+
+
+ 已选择位置
+
+
+ {selectedLocation.label || selectedLocation.poiname}
+
+
+ 经度: {selectedLocation.y}, 纬度: {selectedLocation.x}
+
+
+ )}
+
+
+ );
+};
+
+export default SelectMap;
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx
index 864ccb6e..9135624f 100644
--- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx
@@ -1,5 +1,15 @@
-import React, { useEffect, useState } from "react";
-import { Layout, Input, Button, Modal, message, Tooltip } from "antd";
+import React, { useEffect, useState, useRef } from "react";
+import {
+ Layout,
+ Input,
+ Button,
+ Modal,
+ message,
+ Tooltip,
+ AutoComplete,
+ Input as AntInput,
+ Spin,
+} from "antd";
import {
SendOutlined,
FolderOutlined,
@@ -8,6 +18,7 @@ import {
CloseOutlined,
MessageOutlined,
ReloadOutlined,
+ EnvironmentOutlined,
} from "@ant-design/icons";
import { ContractData, weChatGroup, ChatRecord } from "@/pages/pc/ckbox/data";
import { useWebSocketStore } from "@/store/module/websocket/websocket";
@@ -23,6 +34,7 @@ import {
manualTriggerAi,
} from "@/store/module/weChat/weChat";
import { useContactStore } from "@/store/module/weChat/contacts";
+import SelectMap from "./components/selectMap";
const { Footer } = Layout;
const { TextArea } = Input;
@@ -326,6 +338,8 @@ const MessageEnter: React.FC = ({ contract }) => {
updateShowChatRecordModel(!showChatRecordModel);
};
+ const [mapVisible, setMapVisible] = useState(false);
+
return (
<>
{/* 聊天输入 */}
@@ -423,6 +437,12 @@ const MessageEnter: React.FC = ({ contract }) => {
}
className={styles.toolbarButton}
/>
+ }
+ onClick={() => setMapVisible(true)}
+ />
{/* AI模式下显示重新生成按钮 */}
{(isAiAssist || isAiTakeover) && (
@@ -502,7 +522,12 @@ const MessageEnter: React.FC = ({ contract }) => {
>
)}
- 、
+ setMapVisible(false)}
+ contract={contract}
+ addMessage={addMessage}
+ />
>
);
};
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage/LocationMessage.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage/LocationMessage.module.scss
index 61ef4774..99bdc3e4 100644
--- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage/LocationMessage.module.scss
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage/LocationMessage.module.scss
@@ -9,7 +9,7 @@
// 位置消息基础样式
.locationMessage {
- max-width: 420px;
+ width: 420px;
margin: 4px 0;
}
@@ -21,6 +21,8 @@
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
overflow: hidden;
+ display: flex;
+ flex-direction: column;
&:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
@@ -33,6 +35,45 @@
}
}
+// 地图预览区域
+.mapPreview {
+ position: relative;
+ width: 100%;
+ height: 200px;
+ overflow: hidden;
+ background: #f5f5f5;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.mapImage {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+}
+
+.mapPlaceholder {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: none;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ background: #f5f5f5;
+ color: #999;
+ font-size: 14px;
+ gap: 8px;
+
+ span:first-child {
+ font-size: 32px;
+ }
+}
+
// 位置消息头部
.locationHeader {
display: flex;
@@ -70,6 +111,21 @@
// 位置消息内容
.locationContent {
padding: 12px 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.roadName {
+ font-size: 16px;
+ font-weight: 600;
+ color: #1a1a1a;
+ line-height: 1.4;
+ display: -webkit-box;
+ -webkit-line-clamp: 1;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
.poiName {
@@ -89,9 +145,8 @@
font-size: 13px;
color: #666;
line-height: 1.5;
- margin-bottom: 12px;
display: -webkit-box;
- -webkit-line-clamp: 3;
+ -webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
@@ -175,13 +230,17 @@
// 响应式设计
@media (max-width: 768px) {
.locationMessage {
- max-width: 280px;
+ width: 280px;
}
.locationCard {
border-radius: 10px;
}
+ .mapPreview {
+ height: 150px;
+ }
+
.locationHeader {
padding: 10px 14px 6px;
}
@@ -253,6 +312,15 @@
}
}
+ .mapPreview {
+ background: #2a2a2a;
+ }
+
+ .mapPlaceholder {
+ background: #2a2a2a;
+ color: #999;
+ }
+
.locationHeader {
background: linear-gradient(135deg, #2a2a2a 0%, #1a1a1a 100%);
border-bottom-color: #333;
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage/index.tsx
index c2d6d8a4..4456f3e8 100644
--- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage/index.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage/index.tsx
@@ -85,10 +85,36 @@ const LocationMessage: React.FC = ({ content }) => {
return renderErrorMessage("[位置消息 - 解析失败]");
}
- // 生成地图链接
+ // 格式化经纬度为6位小数
+ const formatCoordinate = (coord: string): string => {
+ const num = parseFloat(coord);
+ if (isNaN(num)) {
+ return coord; // 如果无法解析,返回原值
+ }
+ return num.toFixed(6);
+ };
+
+ // 生成地图链接(用于点击跳转)
const generateMapUrl = (lat: string, lng: string, label: string) => {
+ const formattedLat = formatCoordinate(lat);
+ const formattedLng = formatCoordinate(lng);
// 使用腾讯地图链接
- return `https://apis.map.qq.com/uri/v1/marker?marker=coord:${lat},${lng};title:${encodeURIComponent(label)}&referer=wechat`;
+ return `https://apis.map.qq.com/uri/v1/marker?marker=coord:${formattedLng},${formattedLat};title:${encodeURIComponent(label)}&referer=wechat`;
+ };
+
+ // 生成静态地图预览图URL
+ const generateStaticMapUrl = (
+ lat: string,
+ lng: string,
+ width: number = 420,
+ height: number = 200,
+ ) => {
+ const formattedLat = formatCoordinate(lat);
+ const formattedLng = formatCoordinate(lng);
+ const key = "7DZBZ-ZSRK3-QJN3W-O5VTV-4E2P6-7GFYX";
+ const zoom = locationData.scale || "15";
+ // 腾讯地图静态地图API
+ return `https://apis.map.qq.com/ws/staticmap/v2/?center=${formattedLng},${formattedLat}&zoom=${zoom}&size=${width}x${height}&markers=${formattedLng},${formattedLat}&key=${key}`;
};
const mapUrl = generateMapUrl(
@@ -97,12 +123,18 @@ const LocationMessage: React.FC = ({ content }) => {
locationData.label,
);
+ const staticMapUrl = generateStaticMapUrl(
+ locationData.y,
+ locationData.x,
+ 420,
+ 200,
+ );
+
// 处理POI信息
- const poiName = locationData.poiname || locationData.label;
- const poiCategory = locationData.poiCategoryTips
- ? locationData.poiCategoryTips.split(":")[0]
- : "";
- const poiPhone = locationData.poiPhone || "";
+ // 提取道路名称(如果有的话,从label中提取)
+ const roadName =
+ locationData.poiname.split(/[((]/)[0] || locationData.label;
+ const detailAddress = locationData.label;
return (
@@ -110,29 +142,35 @@ const LocationMessage: React.FC
= ({ content }) => {
className={styles.locationCard}
onClick={() => window.open(mapUrl, "_blank")}
>
- {/* 位置详情 */}
+ {/* 地图预览图 */}
+
+

{
+ // 如果图片加载失败,显示占位符
+ const target = e.target as HTMLImageElement;
+ target.style.display = "none";
+ const placeholder = target.nextElementSibling as HTMLElement;
+ if (placeholder) {
+ placeholder.style.display = "flex";
+ }
+ }}
+ />
+
+ 📍
+ 地图加载中...
+
+
+
+ {/* 位置信息 */}
- {/* POI名称 */}
- {poiName &&
{poiName}
}
+ {/* 道路名称 */}
+ {roadName &&
{roadName}
}
{/* 详细地址 */}
-
{locationData.label}
-
- {/* POI分类和电话 */}
-
- {poiCategory && (
-
- 🏷️
- {poiCategory}
-
- )}
- {poiPhone && (
-
- 📞
- {poiPhone}
-
- )}
-
+
{detailAddress}
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx
index 4bfe7c51..a3de4b84 100644
--- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx
@@ -347,6 +347,7 @@ const MessageRecord: React.FC = ({ contract }) => {
useEffect(() => {
const prevMessages = prevMessagesRef.current;
+ const prevLength = prevMessages.length;
const hasVideoStateChange = currentMessages.some((msg, index) => {
// 首先检查消息对象本身是否为null或undefined
@@ -384,8 +385,9 @@ const MessageRecord: React.FC = ({ contract }) => {
}
});
- // 只有在没有视频状态变化时才自动滚动到底部
- if (!hasVideoStateChange && isLoadingData) {
+ if (currentMessages.length > prevLength && !hasVideoStateChange) {
+ scrollToBottom();
+ } else if (isLoadingData && !hasVideoStateChange) {
scrollToBottom();
}
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/components/detailValue.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/components/detailValue.tsx
index 4966a863..a986f5cb 100644
--- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/components/detailValue.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/components/detailValue.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useState, useEffect } from "react";
+import React, { useCallback, useState, useEffect, useRef } from "react";
import { Input, message } from "antd";
import { Button } from "antd-mobile";
import { EditOutlined } from "@ant-design/icons";
@@ -56,8 +56,32 @@ const DetailValue: React.FC = ({
useState>(value);
const [changedKeys, setChangedKeys] = useState([]);
+ // 使用 useRef 存储上一次的 value,用于深度比较
+ const prevValueRef = useRef>(value);
+
+ // 深度比较函数:比较两个对象的值是否真的变化了
+ const isValueChanged = useCallback(
+ (prev: Record, next: Record) => {
+ const allKeys = new Set([...Object.keys(prev), ...Object.keys(next)]);
+ for (const key of allKeys) {
+ if (prev[key] !== next[key]) {
+ return true;
+ }
+ }
+ return false;
+ },
+ [],
+ );
+
// 当外部value变化时,更新内部状态
+ // 优化:只有当值真正变化时才重置编辑状态,避免因对象引用变化导致编辑状态丢失
useEffect(() => {
+ // 深度比较,只有当值真正变化时才更新
+ if (!isValueChanged(prevValueRef.current, value)) {
+ return;
+ }
+
+ // 只有在值真正变化时才更新状态
setFieldValues(value);
setOriginalValues(value);
setChangedKeys([]);
@@ -67,7 +91,10 @@ const DetailValue: React.FC = ({
newEditingFields[field.key] = false;
});
setEditingFields(newEditingFields);
- }, [value, fields]);
+
+ // 更新 ref
+ prevValueRef.current = value;
+ }, [value, fields, isValueChanged]);
const handleFieldChange = useCallback(
(fieldKey: string, nextVal: string) => {
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/index.tsx
index 6f40e175..951b5bcc 100644
--- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/index.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/index.tsx
@@ -210,14 +210,34 @@ const Person: React.FC = ({ contract }) => {
// 构建联系人或群聊详细信息
- const customerList = useCustomerStore(state => state.customerList);
- const kfSelectedUser = useMemo(() => {
- if (!contract.wechatAccountId) return null;
- const matchedCustomer = customerList.find(
- customer => customer.id === contract.wechatAccountId,
- );
- return matchedCustomer || null;
- }, [customerList, contract.wechatAccountId]);
+ // 优化:使用选择器函数直接订阅匹配的客服对象,避免订阅整个 customerList
+ // 添加相等性比较,只有当匹配的客服对象或其 labels 真正变化时才触发重新渲染
+ const kfSelectedUser = useCustomerStore(
+ state => {
+ if (!contract.wechatAccountId) return null;
+ return (
+ state.customerList.find(
+ customer => customer.id === contract.wechatAccountId,
+ ) || null
+ );
+ },
+ (prev, next) => {
+ // 如果都是 null,认为相等
+ if (!prev && !next) return true;
+ // 如果一个是 null 另一个不是,认为不相等
+ if (!prev || !next) return false;
+ // 比较关键字段:id 和 labels(因为 useEffect 中使用了 labels)
+ if (prev.id !== next.id) return false;
+ // 比较 labels 数组是否真的变化了
+ const prevLabels = prev.labels || [];
+ const nextLabels = next.labels || [];
+ if (prevLabels.length !== nextLabels.length) return false;
+ // 深度比较 labels 数组内容(先复制再排序,避免修改原数组)
+ const prevLabelsStr = JSON.stringify([...prevLabels].sort());
+ const nextLabelsStr = JSON.stringify([...nextLabels].sort());
+ return prevLabelsStr === nextLabelsStr;
+ },
+ );
// 不再需要从useContactStore获取getContactsByCustomer
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/CustomerList/com.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/CustomerList/com.module.scss
index 9cc2941e..29e92bf8 100644
--- a/Touchkebao/src/pages/pc/ckbox/weChat/components/CustomerList/com.module.scss
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/CustomerList/com.module.scss
@@ -47,6 +47,11 @@
.active & {
border-color: #1890ff;
}
+
+ &.offline {
+ filter: grayscale(100%);
+ opacity: 0.6;
+ }
}
}
.allUser {
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/CustomerList/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/CustomerList/index.tsx
index 9ce88a1f..55ba297f 100644
--- a/Touchkebao/src/pages/pc/ckbox/weChat/components/CustomerList/index.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/CustomerList/index.tsx
@@ -89,7 +89,6 @@ const CustomerList: React.FC = () => {
>
全部
-
{customerList.map(customer => (
{
{
{!customer.avatar && customer.name.charAt(0)}
-
))}
>
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx
index 2625b581..0e39a94b 100644
--- a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx
@@ -383,7 +383,7 @@ const MessageList: React.FC = () => {
const requestId = ++loadRequestRef.current;
const initializeSessions = async () => {
- setLoading(true);
+ // setLoading(true);
try {
const cachedSessions =
@@ -416,7 +416,7 @@ const MessageList: React.FC = () => {
}
} finally {
if (!isCancelled && loadRequestRef.current === requestId) {
- setLoading(false);
+ // setLoading(false);
}
}
};
diff --git a/Touchkebao/src/utils/dbAction/contact.ts b/Touchkebao/src/utils/dbAction/contact.ts
index abbb7fdb..661d827e 100644
--- a/Touchkebao/src/utils/dbAction/contact.ts
+++ b/Touchkebao/src/utils/dbAction/contact.ts
@@ -353,13 +353,13 @@ export class ContactManager {
exclude: boolean = false,
): Promise {
try {
- console.log("getContactCount 调用参数:", {
- userId,
- type,
- customerId,
- groupIds,
- exclude,
- });
+ // console.log("getContactCount 调用参数:", {
+ // userId,
+ // type,
+ // customerId,
+ // groupIds,
+ // exclude,
+ // });
const conditions: any[] = [
{ field: "userId", operator: "equals", value: userId },
@@ -394,14 +394,14 @@ export class ContactManager {
}
}
- console.log("查询条件:", conditions);
+ // console.log("查询条件:", conditions);
const contacts =
await contactUnifiedService.findWhereMultiple(conditions);
- console.log(
- `查询结果数量: ${contacts.length}, type: ${type}, groupIds: ${groupIds}`,
- );
+ // console.log(
+ // `查询结果数量: ${contacts.length}, type: ${type}, groupIds: ${groupIds}`,
+ // );
return contacts.length;
} catch (error) {
diff --git a/Touchkebao/src/utils/filter.ts b/Touchkebao/src/utils/filter.ts
index 5e2d3754..22887a8b 100644
--- a/Touchkebao/src/utils/filter.ts
+++ b/Touchkebao/src/utils/filter.ts
@@ -58,6 +58,11 @@ export const messageFilter = (message: string) => {
return "[图片]";
}
+ // XML 格式的位置消息:包含 ]/i.test(message)) {
+ return "[位置]";
+ }
+
// 其他情况直接返回原始消息
return message;
}
diff --git a/Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/InputMessage/InputMessage.tsx b/Touchkebao/消息功能规划.md
similarity index 100%
rename from Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/InputMessage/InputMessage.tsx
rename to Touchkebao/消息功能规划.md