Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a77192ebc2 | ||
|
|
181a792d0a | ||
|
|
43f6a061e1 | ||
|
|
05bba1a461 | ||
|
|
97fc9959bb | ||
|
|
ad2d1c27ab | ||
|
|
97144e4ebe | ||
|
|
0cc30e5cb1 | ||
|
|
eebb73f49e | ||
|
|
72626e3367 | ||
|
|
07a44e104d | ||
|
|
e4a8975baa | ||
|
|
23de3cac7e | ||
|
|
0244871fc1 | ||
|
|
5691d78004 | ||
|
|
9e76d39707 | ||
|
|
5087190816 | ||
|
|
ce47856c81 | ||
|
|
a03c162962 | ||
|
|
e658a32244 | ||
|
|
637dcddee2 | ||
|
|
5c7940538d | ||
|
|
499b36b03b | ||
|
|
9e101e924e | ||
|
|
10f876f2f6 | ||
|
|
ed4d305165 | ||
|
|
a5cc4e5e18 | ||
|
|
65da14032b | ||
|
|
7e2dd2914d | ||
|
|
644dd8912d | ||
|
|
d7e08fe420 | ||
|
|
cd27506388 | ||
|
|
78de6c92b7 | ||
|
|
cd41190663 | ||
|
|
bbdfdc136b | ||
|
|
5a52b56d89 | ||
|
|
d8d1003d7a | ||
|
|
c801490c2f | ||
|
|
30b33ec974 | ||
|
|
4140e6d3d7 | ||
|
|
1786dec900 | ||
|
|
bda57a84e8 | ||
|
|
deac306f69 | ||
|
|
43b60a0049 | ||
|
|
ab2ee050de | ||
|
|
21de87ca32 | ||
|
|
782031cbc6 | ||
|
|
6c9551be05 | ||
|
|
36eb67a217 | ||
|
|
7dcbcfacc1 | ||
|
|
2c9bda734e | ||
|
|
d296d019e5 | ||
|
|
ccf947c64d | ||
|
|
3faee0d8be | ||
|
|
fe12f78c1d | ||
|
|
a2b0041490 | ||
|
|
235e3ed994 | ||
|
|
84ced0c32a | ||
|
|
beabe1c191 | ||
|
|
719ed98996 | ||
|
|
b0a60e428c | ||
|
|
16a8656b49 | ||
|
|
7fdb993f57 | ||
|
|
0e56f0808c | ||
|
|
bd45a9252a | ||
|
|
57d85765c3 | ||
|
|
067ae0fd10 | ||
|
|
bf421e1a49 | ||
|
|
339f06e630 | ||
|
|
367a250fba | ||
|
|
73110acb37 | ||
|
|
0912a0067e | ||
|
|
5faf83e437 | ||
|
|
3f76f37a4c | ||
|
|
f0574ef555 | ||
|
|
273ea73b1b |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -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/
|
||||
|
||||
8
Cunkebao/pnpm-lock.yaml
generated
8
Cunkebao/pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
@@ -3,6 +3,8 @@ import {
|
||||
GetContentItemListParams,
|
||||
CreateContentItemParams,
|
||||
UpdateContentItemParams,
|
||||
AIRewriteParams,
|
||||
ReplaceContentParams,
|
||||
} from "./data";
|
||||
|
||||
// 获取素材列表
|
||||
@@ -35,3 +37,13 @@ export function deleteContentItem(id: string) {
|
||||
export function getContentLibraryDetail(id: string) {
|
||||
return request("/v1/content/library/detail", { id }, "GET");
|
||||
}
|
||||
|
||||
// AI改写内容
|
||||
export function aiRewriteContent(params: AIRewriteParams) {
|
||||
return request("/v1/content/library/aiEditContent", params, "GET");
|
||||
}
|
||||
|
||||
// 替换原内容
|
||||
export function replaceContent(params: ReplaceContentParams) {
|
||||
return request("/v1/content/library/aiEditContent", params, "POST");
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface ContentItem {
|
||||
delTime: number;
|
||||
wechatChatroomId?: string | null;
|
||||
senderNickname: string;
|
||||
senderAvatar?: string | null;
|
||||
createMessageTime?: string | null;
|
||||
comment: string;
|
||||
sendTime: number;
|
||||
@@ -104,3 +105,15 @@ export interface UpdateContentItemParams
|
||||
extends Partial<CreateContentItemParams> {
|
||||
id: string;
|
||||
}
|
||||
|
||||
// AI改写参数
|
||||
export interface AIRewriteParams {
|
||||
id: string;
|
||||
aiPrompt: string;
|
||||
}
|
||||
|
||||
// 替换内容参数
|
||||
export interface ReplaceContentParams {
|
||||
id: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
@@ -131,6 +131,25 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.avatar-icon-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.avatar-icon {
|
||||
@@ -613,3 +632,147 @@
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
|
||||
// AI改写弹框样式
|
||||
.ai-popup-content {
|
||||
padding: 20px;
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
background: #f9fbfd;
|
||||
|
||||
.ai-popup-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #e8f4ff;
|
||||
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1677ff;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 18px;
|
||||
background: #1677ff;
|
||||
margin-right: 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ai-form {
|
||||
.ai-form-item {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.ai-form-label {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 3px;
|
||||
height: 14px;
|
||||
background: #1677ff;
|
||||
margin-right: 6px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-result-description {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-submit {
|
||||
margin: 24px 0;
|
||||
|
||||
button {
|
||||
height: 44px;
|
||||
font-size: 16px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 6px rgba(22, 119, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.ai-result-box {
|
||||
background: #ffffff;
|
||||
border: 1px solid #e0f0ff;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
min-height: 100px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
|
||||
.ai-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.ai-result-content {
|
||||
font-size: 15px;
|
||||
line-height: 1.8;
|
||||
color: #333;
|
||||
white-space: pre-wrap;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.ai-result-placeholder {
|
||||
color: #999;
|
||||
text-align: center;
|
||||
padding: 30px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.placeholder-icon {
|
||||
font-size: 28px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.placeholder-text {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ai-replace-action {
|
||||
margin-top: 20px;
|
||||
|
||||
button {
|
||||
height: 44px;
|
||||
font-size: 16px;
|
||||
border-radius: 8px;
|
||||
background: #1677ff;
|
||||
border-color: #1677ff;
|
||||
box-shadow: 0 2px 6px rgba(22, 119, 255, 0.2);
|
||||
color: #ffffff;
|
||||
|
||||
&:hover, &:focus {
|
||||
background: #4096ff;
|
||||
border-color: #4096ff;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { Toast, SpinLoading, Dialog, Card } from "antd-mobile";
|
||||
import { Input, Pagination, Button } from "antd";
|
||||
import { Toast, SpinLoading, Dialog, Card, Popup, TextArea } from "antd-mobile";
|
||||
import { Input, Pagination, Button, Spin } from "antd";
|
||||
import {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
} from "@ant-design/icons";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import NavCommon from "@/components/NavCommon";
|
||||
import { getContentItemList, deleteContentItem } from "./api";
|
||||
import { getContentItemList, deleteContentItem, aiRewriteContent, replaceContent } from "./api";
|
||||
import { ContentItem } from "./data";
|
||||
import style from "./index.module.scss";
|
||||
|
||||
@@ -42,6 +42,14 @@ const MaterialsList: React.FC = () => {
|
||||
const [total, setTotal] = useState(0);
|
||||
const pageSize = 20;
|
||||
|
||||
// AI改写相关状态
|
||||
const [showAIRewritePopup, setShowAIRewritePopup] = useState(false);
|
||||
const [currentMaterial, setCurrentMaterial] = useState<ContentItem | null>(null);
|
||||
const [aiPrompt, setAiPrompt] = useState("");
|
||||
const [aiResult, setAiResult] = useState("");
|
||||
const [aiLoading, setAiLoading] = useState(false);
|
||||
const [replaceLoading, setReplaceLoading] = useState(false);
|
||||
|
||||
// 获取素材列表
|
||||
const fetchMaterials = useCallback(async () => {
|
||||
if (!id) return;
|
||||
@@ -104,9 +112,75 @@ const MaterialsList: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleView = (materialId: number) => {
|
||||
// 可以跳转到素材详情页面或显示弹窗
|
||||
console.log("查看素材:", materialId);
|
||||
const handleAIRewrite = (material: ContentItem) => {
|
||||
setCurrentMaterial(material);
|
||||
setAiPrompt("重写这条朋友圈 要求: 1、原本的字数和意思不要修改超过10% 2、出现品牌名或个人名字就去除 3、适当的换行及加些表情点缀");
|
||||
setAiResult("");
|
||||
setShowAIRewritePopup(true);
|
||||
};
|
||||
|
||||
const handleSubmitAIRewrite = async () => {
|
||||
if (!currentMaterial) return;
|
||||
|
||||
try {
|
||||
setAiLoading(true);
|
||||
const response = await aiRewriteContent({
|
||||
id: currentMaterial.id.toString(),
|
||||
aiPrompt: aiPrompt
|
||||
});
|
||||
|
||||
setAiResult(response.contentAfter || "暂无改写结果");
|
||||
|
||||
// 可以在这里显示原内容和改写后内容的对比
|
||||
console.log("原内容:", response.contentFront);
|
||||
console.log("改写后内容:", response.contentAfter);
|
||||
} catch (error) {
|
||||
console.error("AI改写失败:", error);
|
||||
Toast.show({
|
||||
content: "AI改写失败,请重试",
|
||||
position: "top",
|
||||
});
|
||||
} finally {
|
||||
setAiLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReplaceContent = async () => {
|
||||
if (!currentMaterial || !aiResult) return;
|
||||
|
||||
try {
|
||||
setReplaceLoading(true);
|
||||
await replaceContent({
|
||||
id: currentMaterial.id.toString(),
|
||||
content: aiResult
|
||||
});
|
||||
|
||||
Toast.show({
|
||||
content: "内容已成功替换",
|
||||
position: "top",
|
||||
});
|
||||
|
||||
// 刷新素材列表
|
||||
fetchMaterials();
|
||||
|
||||
// 关闭弹窗
|
||||
closeAIRewritePopup();
|
||||
} catch (error) {
|
||||
console.error("替换内容失败:", error);
|
||||
Toast.show({
|
||||
content: "替换内容失败,请重试",
|
||||
position: "top",
|
||||
});
|
||||
} finally {
|
||||
setReplaceLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const closeAIRewritePopup = () => {
|
||||
setShowAIRewritePopup(false);
|
||||
setCurrentMaterial(null);
|
||||
setAiPrompt("");
|
||||
setAiResult("");
|
||||
};
|
||||
|
||||
const handleRefresh = () => {
|
||||
@@ -348,7 +422,23 @@ const MaterialsList: React.FC = () => {
|
||||
<div className={style["card-header"]}>
|
||||
<div className={style["avatar-section"]}>
|
||||
<div className={style["avatar"]}>
|
||||
<UserOutlined className={style["avatar-icon"]} />
|
||||
{material.senderAvatar ? (
|
||||
<img
|
||||
src={material.senderAvatar}
|
||||
alt="头像"
|
||||
className={style["avatar-img"]}
|
||||
onError={(e) => {
|
||||
e.currentTarget.style.display = 'none';
|
||||
const nextElement = e.currentTarget.nextSibling as HTMLElement;
|
||||
if (nextElement) {
|
||||
nextElement.style.display = 'flex';
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<div className={style["avatar-icon-wrapper"]} style={{display: material.senderAvatar ? 'none' : 'flex'}}>
|
||||
<UserOutlined className={style["avatar-icon"]}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["header-info"]}>
|
||||
<span className={style["creator-name"]}>
|
||||
@@ -381,7 +471,7 @@ const MaterialsList: React.FC = () => {
|
||||
编辑
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleView(material.id)}
|
||||
onClick={() => handleAIRewrite(material)}
|
||||
className={style["action-btn"]}
|
||||
>
|
||||
<BarChartOutlined />
|
||||
@@ -402,6 +492,100 @@ const MaterialsList: React.FC = () => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* AI改写弹框 */}
|
||||
<Popup
|
||||
visible={showAIRewritePopup}
|
||||
onMaskClick={closeAIRewritePopup}
|
||||
bodyStyle={{
|
||||
borderRadius: "16px 16px 0 0",
|
||||
maxHeight: "90vh",
|
||||
boxShadow: "0 -4px 12px rgba(0, 0, 0, 0.1)"
|
||||
}}
|
||||
>
|
||||
<div className={style["ai-popup-content"]}>
|
||||
<div className={style["ai-popup-header"]}>
|
||||
<h3>AI内容改写</h3>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={closeAIRewritePopup}
|
||||
>
|
||||
关闭
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className={style["ai-form"]}>
|
||||
{/* 提示词输入区 */}
|
||||
<div className={style["ai-form-item"]}>
|
||||
<div className={style["ai-form-label"]}>提示词</div>
|
||||
<div className={style["ai-form-control"]}>
|
||||
<TextArea
|
||||
placeholder="请输入提示词指导AI如何改写内容"
|
||||
value={aiPrompt}
|
||||
onChange={val => setAiPrompt(val)}
|
||||
rows={4}
|
||||
showCount
|
||||
maxLength={500}
|
||||
style={{
|
||||
border: "1px solid #d9e8ff",
|
||||
borderRadius: "8px",
|
||||
padding: "12px",
|
||||
fontSize: "14px"
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={style["ai-submit"]}>
|
||||
<Button
|
||||
block
|
||||
color="primary"
|
||||
onClick={handleSubmitAIRewrite}
|
||||
loading={aiLoading}
|
||||
disabled={aiLoading || !aiPrompt.trim()}
|
||||
>
|
||||
{aiLoading ? "生成中..." : "生成内容"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 改写结果区 */}
|
||||
<div className={style["ai-form-item"]}>
|
||||
<div className={style["ai-form-label"]}>改写结果</div>
|
||||
<div className={style["ai-result-description"]}>AI生成的内容将显示在下方区域</div>
|
||||
<div className={style["ai-result-box"]}>
|
||||
{aiLoading ? (
|
||||
<div className={style["ai-loading"]}>
|
||||
<Spin tip="AI正在思考中..." />
|
||||
</div>
|
||||
) : aiResult ? (
|
||||
<div className={style["ai-result-content"]}>
|
||||
{aiResult}
|
||||
</div>
|
||||
) : (
|
||||
<div className={style["ai-result-placeholder"]}>
|
||||
<div className={style["placeholder-icon"]}>✨</div>
|
||||
<div className={style["placeholder-text"]}>点击"生成内容"按钮获取AI改写结果</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 替换按钮 */}
|
||||
{aiResult && (
|
||||
<div className={style["ai-replace-action"]}>
|
||||
<Button
|
||||
block
|
||||
color="primary"
|
||||
onClick={handleReplaceContent}
|
||||
loading={replaceLoading}
|
||||
disabled={replaceLoading || !aiResult}
|
||||
>
|
||||
{replaceLoading ? "替换中..." : "替换原内容"}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
LogoutOutlined,
|
||||
SettingOutlined,
|
||||
LockOutlined,
|
||||
ReloadOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import { useUserStore } from "@/store/module/user";
|
||||
@@ -16,7 +15,7 @@ import { useSettingsStore } from "@/store/module/settings";
|
||||
import style from "./index.module.scss";
|
||||
import NavCommon from "@/components/NavCommon";
|
||||
import { sendMessageToParent, TYPE_EMUE } from "@/utils/postApp";
|
||||
import { updateChecker } from "@/utils/updateChecker";
|
||||
import { clearApplicationCache } from "@/utils/cacheCleaner";
|
||||
|
||||
interface SettingItem {
|
||||
id: string;
|
||||
@@ -58,13 +57,35 @@ const Setting: React.FC = () => {
|
||||
const handleClearCache = () => {
|
||||
Dialog.confirm({
|
||||
content: "确定要清除缓存吗?这将清除所有本地数据。",
|
||||
onConfirm: () => {
|
||||
sendMessageToParent(
|
||||
{
|
||||
action: "clearCache",
|
||||
},
|
||||
TYPE_EMUE.FUNCTION,
|
||||
);
|
||||
onConfirm: async () => {
|
||||
const handler = Toast.show({
|
||||
icon: "loading",
|
||||
content: "正在清理缓存...",
|
||||
duration: 0,
|
||||
});
|
||||
try {
|
||||
await clearApplicationCache();
|
||||
sendMessageToParent(
|
||||
{
|
||||
action: "clearCache",
|
||||
},
|
||||
TYPE_EMUE.FUNCTION,
|
||||
);
|
||||
handler.close();
|
||||
Toast.show({
|
||||
icon: "success",
|
||||
content: "缓存清理完成",
|
||||
position: "top",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("clear cache failed", error);
|
||||
handler.close();
|
||||
Toast.show({
|
||||
icon: "fail",
|
||||
content: "缓存清理失败,请稍后再试",
|
||||
position: "top",
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
import request from "@/api/request";
|
||||
import axios from "axios";
|
||||
import { useUserStore } from "@/store/module/user";
|
||||
|
||||
// 获取微信号详情
|
||||
export function getWechatAccountDetail(id: string) {
|
||||
return request("/v1/wechats/getWechatInfo", { wechatId: id }, "GET");
|
||||
}
|
||||
|
||||
// 获取微信号概览数据
|
||||
export function getWechatAccountOverview(id: string) {
|
||||
return request("/v1/wechats/overview", { wechatId: id }, "GET");
|
||||
}
|
||||
|
||||
// 获取微信号朋友圈列表
|
||||
export function getWechatMoments(params: {
|
||||
wechatId: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}) {
|
||||
return request("/v1/wechats/moments", params, "GET");
|
||||
}
|
||||
|
||||
// 获取微信号好友列表
|
||||
export function getWechatFriends(params: {
|
||||
wechatAccount: string;
|
||||
@@ -36,3 +52,68 @@ export function transferWechatFriends(params: {
|
||||
}) {
|
||||
return request("/v1/wechats/transfer-friends", params, "POST");
|
||||
}
|
||||
|
||||
// 导出朋友圈接口(直接下载文件)
|
||||
export async function exportWechatMoments(params: {
|
||||
wechatId: string;
|
||||
keyword?: string;
|
||||
type?: number;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
}): Promise<void> {
|
||||
const { token } = useUserStore.getState();
|
||||
const baseURL =
|
||||
(import.meta as any).env?.VITE_API_BASE_URL || "/api";
|
||||
|
||||
// 构建查询参数
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.append("wechatId", params.wechatId);
|
||||
if (params.keyword) {
|
||||
queryParams.append("keyword", params.keyword);
|
||||
}
|
||||
if (params.type !== undefined) {
|
||||
queryParams.append("type", params.type.toString());
|
||||
}
|
||||
if (params.startTime) {
|
||||
queryParams.append("startTime", params.startTime);
|
||||
}
|
||||
if (params.endTime) {
|
||||
queryParams.append("endTime", params.endTime);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`${baseURL}/v1/wechats/moments/export?${queryParams.toString()}`,
|
||||
{
|
||||
responseType: "blob",
|
||||
headers: {
|
||||
Authorization: token ? `Bearer ${token}` : undefined,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// 创建下载链接
|
||||
const blob = new Blob([response.data]);
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
|
||||
// 从响应头获取文件名,如果没有则使用默认文件名
|
||||
const contentDisposition = response.headers["content-disposition"];
|
||||
let fileName = "朋友圈导出.xlsx";
|
||||
if (contentDisposition) {
|
||||
const fileNameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
|
||||
if (fileNameMatch && fileNameMatch[1]) {
|
||||
fileName = decodeURIComponent(fileNameMatch[1].replace(/['"]/g, ""));
|
||||
}
|
||||
}
|
||||
|
||||
link.download = fileName;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response?.data?.message || error.message || "导出失败");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,41 @@
|
||||
// 概览数据接口
|
||||
export interface WechatAccountOverview {
|
||||
healthScoreAssessment: {
|
||||
score: number;
|
||||
dailyLimit: number;
|
||||
todayAdded: number;
|
||||
lastAddTime: string;
|
||||
statusTag: string;
|
||||
baseComposition?: Array<{
|
||||
name: string;
|
||||
score: number;
|
||||
formatted: string;
|
||||
friendCount?: number;
|
||||
}>;
|
||||
dynamicRecords?: Array<{
|
||||
title?: string;
|
||||
description?: string;
|
||||
time?: string;
|
||||
score?: number;
|
||||
formatted?: string;
|
||||
statusTag?: string;
|
||||
}>;
|
||||
};
|
||||
accountValue: {
|
||||
value: number;
|
||||
formatted: string;
|
||||
};
|
||||
todayValueChange: {
|
||||
change: number;
|
||||
formatted: string;
|
||||
isPositive: boolean;
|
||||
};
|
||||
totalFriends: number;
|
||||
todayNewFriends: number;
|
||||
highValueChatrooms: number;
|
||||
todayNewChatrooms: number;
|
||||
}
|
||||
|
||||
export interface WechatAccountSummary {
|
||||
accountAge: string;
|
||||
activityLevel: {
|
||||
@@ -15,12 +53,51 @@ export interface WechatAccountSummary {
|
||||
todayAdded: number;
|
||||
addLimit: number;
|
||||
};
|
||||
healthScore?: {
|
||||
score: number;
|
||||
lastUpdate?: string;
|
||||
lastAddTime?: string;
|
||||
baseScore?: number;
|
||||
verifiedScore?: number;
|
||||
friendsScore?: number;
|
||||
activities?: {
|
||||
type: string;
|
||||
time?: string;
|
||||
score: number;
|
||||
description?: string;
|
||||
status?: string;
|
||||
}[];
|
||||
};
|
||||
moments?: {
|
||||
id: string;
|
||||
date: string;
|
||||
month: string;
|
||||
day: string;
|
||||
content: string;
|
||||
images?: string[];
|
||||
timeAgo?: string;
|
||||
hasEmoji?: boolean;
|
||||
}[];
|
||||
accountValue?: {
|
||||
value: number;
|
||||
todayChange?: number;
|
||||
};
|
||||
friendsCount?: {
|
||||
total: number;
|
||||
todayAdded?: number;
|
||||
};
|
||||
groupsCount?: {
|
||||
total: number;
|
||||
todayAdded?: number;
|
||||
};
|
||||
restrictions: {
|
||||
id: number;
|
||||
level: number;
|
||||
reason: string;
|
||||
date: string;
|
||||
}[];
|
||||
// 新增概览数据
|
||||
overview?: WechatAccountOverview;
|
||||
}
|
||||
|
||||
export interface Friend {
|
||||
@@ -39,6 +116,27 @@ export interface Friend {
|
||||
region: string;
|
||||
source: string;
|
||||
notes: string;
|
||||
value?: number;
|
||||
valueFormatted?: string;
|
||||
statusTags?: string[];
|
||||
}
|
||||
|
||||
export interface MomentItem {
|
||||
id: string;
|
||||
snsId: string;
|
||||
type: number;
|
||||
content: string;
|
||||
resUrls: string[];
|
||||
commentList?: any[];
|
||||
likeList?: any[];
|
||||
createTime: string;
|
||||
momentEntity?: {
|
||||
lat?: string;
|
||||
lng?: string;
|
||||
location?: string;
|
||||
picSize?: number;
|
||||
userName?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface WechatFriendDetail {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@ import {
|
||||
Avatar,
|
||||
Tag,
|
||||
Switch,
|
||||
DatePicker,
|
||||
} from "antd-mobile";
|
||||
import { Input, Pagination } from "antd";
|
||||
import NavCommon from "@/components/NavCommon";
|
||||
@@ -21,11 +22,18 @@ import {
|
||||
} from "@ant-design/icons";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import style from "./detail.module.scss";
|
||||
import { getWechatAccountDetail, getWechatFriends, transferWechatFriends } from "./api";
|
||||
import {
|
||||
getWechatAccountDetail,
|
||||
getWechatFriends,
|
||||
transferWechatFriends,
|
||||
getWechatAccountOverview,
|
||||
getWechatMoments,
|
||||
exportWechatMoments,
|
||||
} from "./api";
|
||||
import DeviceSelection from "@/components/DeviceSelection";
|
||||
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
|
||||
|
||||
import { WechatAccountSummary, Friend } from "./data";
|
||||
import { WechatAccountSummary, Friend, MomentItem } from "./data";
|
||||
|
||||
const WechatAccountDetail: React.FC = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
@@ -34,6 +42,7 @@ const WechatAccountDetail: React.FC = () => {
|
||||
const [accountSummary, setAccountSummary] =
|
||||
useState<WechatAccountSummary | null>(null);
|
||||
const [accountInfo, setAccountInfo] = useState<any>(null);
|
||||
const [overviewData, setOverviewData] = useState<any>(null);
|
||||
const [showRestrictions, setShowRestrictions] = useState(false);
|
||||
const [showTransferConfirm, setShowTransferConfirm] = useState(false);
|
||||
const [selectedDevices, setSelectedDevices] = useState<DeviceSelectionItem[]>([]);
|
||||
@@ -50,6 +59,22 @@ const WechatAccountDetail: React.FC = () => {
|
||||
const [isFetchingFriends, setIsFetchingFriends] = useState(false);
|
||||
const [hasFriendLoadError, setHasFriendLoadError] = useState(false);
|
||||
const [isFriendsEmpty, setIsFriendsEmpty] = useState(false);
|
||||
const [moments, setMoments] = useState<MomentItem[]>([]);
|
||||
const [momentsPage, setMomentsPage] = useState(1);
|
||||
const [momentsTotal, setMomentsTotal] = useState(0);
|
||||
const [isFetchingMoments, setIsFetchingMoments] = useState(false);
|
||||
const [momentsError, setMomentsError] = useState<string | null>(null);
|
||||
const MOMENTS_LIMIT = 10;
|
||||
|
||||
// 导出相关状态
|
||||
const [showExportPopup, setShowExportPopup] = useState(false);
|
||||
const [exportKeyword, setExportKeyword] = useState("");
|
||||
const [exportType, setExportType] = useState<number | undefined>(undefined);
|
||||
const [exportStartTime, setExportStartTime] = useState<Date | null>(null);
|
||||
const [exportEndTime, setExportEndTime] = useState<Date | null>(null);
|
||||
const [showStartTimePicker, setShowStartTimePicker] = useState(false);
|
||||
const [showEndTimePicker, setShowEndTimePicker] = useState(false);
|
||||
const [exportLoading, setExportLoading] = useState(false);
|
||||
|
||||
// 获取基础信息
|
||||
const fetchAccountInfo = useCallback(async () => {
|
||||
@@ -80,6 +105,19 @@ const WechatAccountDetail: React.FC = () => {
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
// 获取概览数据
|
||||
const fetchOverviewData = useCallback(async () => {
|
||||
if (!id) return;
|
||||
try {
|
||||
const response = await getWechatAccountOverview(id);
|
||||
if (response) {
|
||||
setOverviewData(response);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("获取概览数据失败:", e);
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
// 获取好友列表 - 封装为独立函数
|
||||
const fetchFriendsList = useCallback(
|
||||
async (page: number = 1, keyword: string = "") => {
|
||||
@@ -96,26 +134,44 @@ const WechatAccountDetail: React.FC = () => {
|
||||
keyword: keyword,
|
||||
});
|
||||
|
||||
const newFriends = response.list.map((friend: any) => ({
|
||||
id: friend.id.toString(),
|
||||
avatar: friend.avatar || "/placeholder.svg",
|
||||
nickname: friend.nickname || "未知用户",
|
||||
wechatId: friend.wechatId || "",
|
||||
remark: friend.memo || "",
|
||||
addTime: friend.createTime || new Date().toISOString().split("T")[0],
|
||||
lastInteraction:
|
||||
friend.lastInteraction || new Date().toISOString().split("T")[0],
|
||||
tags: friend.tags
|
||||
? friend.tags.map((tag: string, index: number) => ({
|
||||
id: `tag-${index}`,
|
||||
name: tag,
|
||||
color: getRandomTagColor(),
|
||||
}))
|
||||
: [],
|
||||
region: friend.region || "未知",
|
||||
source: friend.source || "未知",
|
||||
notes: friend.notes || "",
|
||||
}));
|
||||
const newFriends = response.list.map((friend: any) => {
|
||||
const memoTags = Array.isArray(friend.memo)
|
||||
? friend.memo
|
||||
: friend.memo
|
||||
? String(friend.memo)
|
||||
.split(/[,\s,、]+/)
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
|
||||
const tagList = Array.isArray(friend.tags)
|
||||
? friend.tags
|
||||
: friend.tags
|
||||
? [friend.tags]
|
||||
: [];
|
||||
|
||||
return {
|
||||
id: friend.id.toString(),
|
||||
avatar: friend.avatar || "/placeholder.svg",
|
||||
nickname: friend.nickname || "未知用户",
|
||||
wechatId: friend.wechatId || "",
|
||||
remark: friend.notes || "",
|
||||
addTime:
|
||||
friend.createTime || new Date().toISOString().split("T")[0],
|
||||
lastInteraction:
|
||||
friend.lastInteraction || new Date().toISOString().split("T")[0],
|
||||
tags: memoTags.map((tag: string, index: number) => ({
|
||||
id: `tag-${index}`,
|
||||
name: tag,
|
||||
color: getRandomTagColor(),
|
||||
})),
|
||||
statusTags: tagList,
|
||||
region: friend.region || "未知",
|
||||
source: friend.source || "未知",
|
||||
notes: friend.notes || "",
|
||||
value: friend.value,
|
||||
valueFormatted: friend.valueFormatted,
|
||||
};
|
||||
});
|
||||
|
||||
setFriends(newFriends);
|
||||
setFriendsTotal(response.total);
|
||||
@@ -137,6 +193,46 @@ const WechatAccountDetail: React.FC = () => {
|
||||
[id],
|
||||
);
|
||||
|
||||
const fetchMomentsList = useCallback(
|
||||
async (page: number = 1, append: boolean = false) => {
|
||||
if (!id) return;
|
||||
setIsFetchingMoments(true);
|
||||
setMomentsError(null);
|
||||
try {
|
||||
const response = await getWechatMoments({
|
||||
wechatId: id,
|
||||
page,
|
||||
limit: MOMENTS_LIMIT,
|
||||
});
|
||||
|
||||
const list: MomentItem[] = (response.list || []).map((moment: any) => ({
|
||||
id: moment.id?.toString() || Math.random().toString(),
|
||||
snsId: moment.snsId,
|
||||
type: moment.type,
|
||||
content: moment.content || "",
|
||||
resUrls: moment.resUrls || [],
|
||||
commentList: moment.commentList || [],
|
||||
likeList: moment.likeList || [],
|
||||
createTime: moment.createTime || "",
|
||||
momentEntity: moment.momentEntity || {},
|
||||
}));
|
||||
|
||||
setMoments(prev => (append ? [...prev, ...list] : list));
|
||||
setMomentsTotal(response.total || list.length);
|
||||
setMomentsPage(page);
|
||||
} catch (error) {
|
||||
console.error("获取朋友圈数据失败:", error);
|
||||
setMomentsError("获取朋友圈数据失败");
|
||||
if (!append) {
|
||||
setMoments([]);
|
||||
}
|
||||
} finally {
|
||||
setIsFetchingMoments(false);
|
||||
}
|
||||
},
|
||||
[id],
|
||||
);
|
||||
|
||||
// 搜索好友
|
||||
const handleSearch = useCallback(() => {
|
||||
setFriendsPage(1);
|
||||
@@ -161,8 +257,9 @@ const WechatAccountDetail: React.FC = () => {
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
fetchAccountInfo();
|
||||
fetchOverviewData();
|
||||
}
|
||||
}, [id, fetchAccountInfo]);
|
||||
}, [id, fetchAccountInfo, fetchOverviewData]);
|
||||
|
||||
// 监听标签切换 - 只在切换到好友列表时请求一次
|
||||
useEffect(() => {
|
||||
@@ -173,6 +270,14 @@ const WechatAccountDetail: React.FC = () => {
|
||||
}
|
||||
}, [activeTab, id, fetchFriendsList, searchQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab === "moments" && id) {
|
||||
if (moments.length === 0) {
|
||||
fetchMomentsList(1, false);
|
||||
}
|
||||
}
|
||||
}, [activeTab, id, fetchMomentsList, moments.length]);
|
||||
|
||||
// 工具函数
|
||||
const getRandomTagColor = (): string => {
|
||||
const colors = [
|
||||
@@ -271,6 +376,85 @@ const WechatAccountDetail: React.FC = () => {
|
||||
navigate(`/mine/traffic-pool/detail/${friend.wechatId}/${friend.id}`);
|
||||
};
|
||||
|
||||
const handleLoadMoreMoments = () => {
|
||||
if (isFetchingMoments) return;
|
||||
if (moments.length >= momentsTotal) return;
|
||||
fetchMomentsList(momentsPage + 1, true);
|
||||
};
|
||||
|
||||
// 处理朋友圈导出
|
||||
const handleExportMoments = useCallback(async () => {
|
||||
if (!id) {
|
||||
Toast.show({ content: "微信ID不存在", position: "top" });
|
||||
return;
|
||||
}
|
||||
|
||||
setExportLoading(true);
|
||||
try {
|
||||
// 格式化时间
|
||||
const formatDate = (date: Date | null): string | undefined => {
|
||||
if (!date) return undefined;
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
return `${year}-${month}-${day}`;
|
||||
};
|
||||
|
||||
await exportWechatMoments({
|
||||
wechatId: id,
|
||||
keyword: exportKeyword || undefined,
|
||||
type: exportType,
|
||||
startTime: formatDate(exportStartTime),
|
||||
endTime: formatDate(exportEndTime),
|
||||
});
|
||||
|
||||
Toast.show({ content: "导出成功", position: "top" });
|
||||
setShowExportPopup(false);
|
||||
// 重置筛选条件
|
||||
setExportKeyword("");
|
||||
setExportType(undefined);
|
||||
setExportStartTime(null);
|
||||
setExportEndTime(null);
|
||||
} catch (error: any) {
|
||||
console.error("导出失败:", error);
|
||||
Toast.show({
|
||||
content: error.message || "导出失败,请重试",
|
||||
position: "top",
|
||||
});
|
||||
} finally {
|
||||
setExportLoading(false);
|
||||
}
|
||||
}, [id, exportKeyword, exportType, exportStartTime, exportEndTime]);
|
||||
|
||||
const formatMomentDateParts = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return { day: "--", month: "--" };
|
||||
}
|
||||
const day = date.getDate().toString().padStart(2, "0");
|
||||
const month = `${date.getMonth() + 1}月`;
|
||||
return { day, month };
|
||||
};
|
||||
|
||||
const formatMomentTimeAgo = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return dateString || "--";
|
||||
}
|
||||
const diff = Date.now() - date.getTime();
|
||||
const minutes = Math.floor(diff / (1000 * 60));
|
||||
if (minutes < 1) return "刚刚";
|
||||
if (minutes < 60) return `${minutes}分钟前`;
|
||||
const hours = Math.floor(minutes / 60);
|
||||
if (hours < 24) return `${hours}小时前`;
|
||||
const days = Math.floor(hours / 24);
|
||||
if (days < 7) return `${days}天前`;
|
||||
return date.toLocaleDateString("zh-CN", {
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout header={<NavCommon title="微信号详情" />} loading={loadingInfo}>
|
||||
<div className={style["wechat-account-detail-page"]}>
|
||||
@@ -313,73 +497,223 @@ const WechatAccountDetail: React.FC = () => {
|
||||
onChange={handleTabChange}
|
||||
className={style["tabs"]}
|
||||
>
|
||||
<Tabs.Tab title="账号概览" key="overview">
|
||||
<Tabs.Tab title="概览" key="overview">
|
||||
<div className={style["overview-content"]}>
|
||||
<div className={style["summary-grid"]}>
|
||||
<div className={style["summary-item"]}>
|
||||
<div className={style["summary-value"]}>
|
||||
{accountInfo?.friendShip?.totalFriend ?? "-"}
|
||||
{/* 健康分评估区域 */}
|
||||
<div className={style["health-score-section"]}>
|
||||
<div className={style["health-score-title"]}>健康分评估</div>
|
||||
<div className={style["health-score-info"]}>
|
||||
<div className={style["health-score-status"]}>
|
||||
<span className={style["status-tag"]}>{overviewData?.healthScoreAssessment?.statusTag || "已添加加人"}</span>
|
||||
<span className={style["status-time"]}>最后添加时间: {overviewData?.healthScoreAssessment?.lastAddTime || "18:44:14"}</span>
|
||||
</div>
|
||||
<div className={style["summary-label"]}>好友数量</div>
|
||||
</div>
|
||||
<div className={style["summary-item"]}>
|
||||
<div className={style["summary-value-green"]}>
|
||||
+{accountSummary?.statistics.todayAdded ?? "-"}
|
||||
<div className={style["health-score-display"]}>
|
||||
<div className={style["score-circle-wrapper"]}>
|
||||
<div className={style["score-circle"]}>
|
||||
<div className={style["score-number"]}>
|
||||
{overviewData?.healthScoreAssessment?.score || 67}
|
||||
</div>
|
||||
<div className={style["score-label"]}>SCORE</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["health-score-stats"]}>
|
||||
<div className={style["stats-row"]}>
|
||||
<div className={style["stats-label"]}>每日限额</div>
|
||||
<div className={style["stats-value"]}>{overviewData?.healthScoreAssessment?.dailyLimit || 0} 人</div>
|
||||
</div>
|
||||
<div className={style["stats-row"]}>
|
||||
<div className={style["stats-label"]}>今日已加</div>
|
||||
<div className={style["stats-value"]}>{overviewData?.healthScoreAssessment?.todayAdded || 0} 人</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["summary-label"]}>今日新增</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["summary-progress-row"]}>
|
||||
<span>今日可添加:</span>
|
||||
<span className={style["summary-progress-text"]}>
|
||||
{accountSummary?.statistics.todayAdded ?? 0}/
|
||||
{accountSummary?.statistics.addLimit ?? 0}
|
||||
</span>
|
||||
</div>
|
||||
<div className={style["summary-progress-bar"]}>
|
||||
<div className={style["progress-bg"]}>
|
||||
<div
|
||||
className={style["progress-fill"]}
|
||||
style={{
|
||||
width: `${Math.min(((accountSummary?.statistics.todayAdded ?? 0) / (accountSummary?.statistics.addLimit || 1)) * 100, 100)}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["summary-grid"]}>
|
||||
<div className={style["summary-item"]}>
|
||||
<div className={style["summary-value-blue"]}>
|
||||
{accountInfo?.friendShip?.groupNumber ?? "-"}
|
||||
|
||||
{/* 账号价值和好友数量区域 */}
|
||||
<div className={style["account-stats-grid"]}>
|
||||
{/* 账号价值 */}
|
||||
<div className={style["stat-card"]}>
|
||||
<div className={style["stat-header"]}>
|
||||
<div className={style["stat-title"]}>账号价值</div>
|
||||
<div className={style["stat-icon-up"]}></div>
|
||||
</div>
|
||||
<div className={style["summary-label"]}>群聊数量</div>
|
||||
</div>
|
||||
<div className={style["summary-item"]}>
|
||||
<div className={style["summary-value-green"]}>
|
||||
{accountInfo?.activity?.yesterdayMsgCount ?? "-"}
|
||||
<div className={style["stat-value"]}>
|
||||
{overviewData?.accountValue?.formatted || `¥${overviewData?.accountValue?.value || "29,800"}`}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 今日价值变化 */}
|
||||
<div className={style["stat-card"]}>
|
||||
<div className={style["stat-header"]}>
|
||||
<div className={style["stat-title"]}>今日价值变化</div>
|
||||
<div className={style["stat-icon-plus"]}></div>
|
||||
</div>
|
||||
<div className={style["stat-value-positive"]}>
|
||||
{overviewData?.todayValueChange?.formatted || `+${overviewData?.todayValueChange?.change || "500"}`}
|
||||
</div>
|
||||
<div className={style["summary-label"]}>今日消息</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["device-card"]}>
|
||||
<div className={style["device-title"]}>设备信息</div>
|
||||
<div className={style["device-row"]}>
|
||||
<span className={style["device-label"]}>设备名称:</span>
|
||||
<span>{accountInfo?.deviceName ?? "-"}</span>
|
||||
|
||||
{/* 好友数量和今日新增好友区域 */}
|
||||
<div className={style["account-stats-grid"]}>
|
||||
{/* 好友总数 */}
|
||||
<div className={style["stat-card"]}>
|
||||
<div className={style["stat-header"]}>
|
||||
<div className={style["stat-title"]}>好友总数</div>
|
||||
<div className={style["stat-icon-people"]}></div>
|
||||
</div>
|
||||
<div className={style["stat-value"]}>
|
||||
{overviewData?.totalFriends || accountInfo?.friendShip?.totalFriend || "0"}
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["device-row"]}>
|
||||
<span className={style["device-label"]}>系统类型:</span>
|
||||
<span>{accountInfo?.deviceType ?? "-"}</span>
|
||||
|
||||
{/* 今日新增好友 */}
|
||||
<div className={style["stat-card"]}>
|
||||
<div className={style["stat-header"]}>
|
||||
<div className={style["stat-title"]}>今日新增好友</div>
|
||||
<div className={style["stat-icon-plus"]}></div>
|
||||
</div>
|
||||
<div className={style["stat-value-positive"]}>
|
||||
+{overviewData?.todayNewFriends || accountSummary?.statistics.todayAdded || "0"}
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["device-row"]}>
|
||||
<span className={style["device-label"]}>系统版本:</span>
|
||||
<span>{accountInfo?.deviceVersion ?? "-"}</span>
|
||||
</div>
|
||||
|
||||
{/* 高价群聊区域 */}
|
||||
<div className={style["account-stats-grid"]}>
|
||||
{/* 高价群聊 */}
|
||||
<div className={style["stat-card"]}>
|
||||
<div className={style["stat-header"]}>
|
||||
<div className={style["stat-title"]}>高价群聊</div>
|
||||
<div className={style["stat-icon-chat"]}></div>
|
||||
</div>
|
||||
<div className={style["stat-value"]}>
|
||||
{overviewData?.highValueChatrooms || accountInfo?.friendShip?.groupNumber || "0"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 今日新增群聊 */}
|
||||
<div className={style["stat-card"]}>
|
||||
<div className={style["stat-header"]}>
|
||||
<div className={style["stat-title"]}>今日新增群聊</div>
|
||||
<div className={style["stat-icon-plus"]}></div>
|
||||
</div>
|
||||
<div className={style["stat-value-positive"]}>
|
||||
+{overviewData?.todayNewChatrooms || "0"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</Tabs.Tab>
|
||||
|
||||
<Tabs.Tab title="健康分" key="health">
|
||||
<div className={style["health-content"]}>
|
||||
{/* 健康分评估区域 */}
|
||||
<div className={style["health-score-section"]}>
|
||||
<div className={style["health-score-title"]}>健康分评估</div>
|
||||
<div className={style["health-score-info"]}>
|
||||
<div className={style["health-score-status"]}>
|
||||
<span className={style["status-tag"]}>{overviewData?.healthScoreAssessment?.statusTag || "已添加加人"}</span>
|
||||
<span className={style["status-time"]}>最后添加时间: {overviewData?.healthScoreAssessment?.lastAddTime || "18:44:14"}</span>
|
||||
</div>
|
||||
<div className={style["health-score-display"]}>
|
||||
<div className={style["score-circle-wrapper"]}>
|
||||
<div className={style["score-circle"]}>
|
||||
<div className={style["score-number"]}>
|
||||
{overviewData?.healthScoreAssessment?.score || 67}
|
||||
</div>
|
||||
<div className={style["score-label"]}>SCORE</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["health-score-stats"]}>
|
||||
<div className={style["stats-row"]}>
|
||||
<div className={style["stats-label"]}>每日限额</div>
|
||||
<div className={style["stats-value"]}>{overviewData?.healthScoreAssessment?.dailyLimit || 0} 人</div>
|
||||
</div>
|
||||
<div className={style["stats-row"]}>
|
||||
<div className={style["stats-label"]}>今日已加</div>
|
||||
<div className={style["stats-value"]}>{overviewData?.healthScoreAssessment?.todayAdded || 0} 人</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 基础构成 */}
|
||||
<div className={style["health-section"]}>
|
||||
<div className={style["health-section-title"]}>基础构成</div>
|
||||
{(overviewData?.healthScoreAssessment?.baseComposition &&
|
||||
overviewData.healthScoreAssessment.baseComposition.length > 0
|
||||
? overviewData.healthScoreAssessment.baseComposition
|
||||
: [
|
||||
{ name: "账号基础分", formatted: "+60" },
|
||||
{ name: "已修改微信号", formatted: "+10" },
|
||||
{ name: "好友数量加成", formatted: "+12", friendCount: 5595 },
|
||||
]
|
||||
).map((item, index) => (
|
||||
<div className={style["health-item"]} key={`${item.name}-${index}`}>
|
||||
<div className={style["health-item-label"]}>
|
||||
{item.name}
|
||||
{item.friendCount ? ` (${item.friendCount})` : ""}
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
(item.score ?? 0) >= 0
|
||||
? style["health-item-value-positive"]
|
||||
: style["health-item-value-negative"]
|
||||
}
|
||||
>
|
||||
{item.formatted || `${item.score ?? 0}`}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 动态记录 */}
|
||||
<div className={style["health-section"]}>
|
||||
<div className={style["health-section-title"]}>动态记录</div>
|
||||
{overviewData?.healthScoreAssessment?.dynamicRecords &&
|
||||
overviewData.healthScoreAssessment.dynamicRecords.length > 0 ? (
|
||||
overviewData.healthScoreAssessment.dynamicRecords.map(
|
||||
(record, index) => (
|
||||
<div className={style["health-item"]} key={`record-${index}`}>
|
||||
<div className={style["health-item-label"]}>
|
||||
<span className={style["health-item-icon-warning"]}></span>
|
||||
{record.title || record.description || "记录"}
|
||||
{record.statusTag && (
|
||||
<span className={style["health-item-tag"]}>
|
||||
{record.statusTag}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
(record.score ?? 0) >= 0
|
||||
? style["health-item-value-positive"]
|
||||
: style["health-item-value-negative"]
|
||||
}
|
||||
>
|
||||
{record.formatted ||
|
||||
(record.score && record.score > 0
|
||||
? `+${record.score}`
|
||||
: record.score || "-")}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
)
|
||||
) : (
|
||||
<div className={style["health-empty"]}>暂无动态记录</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Tab>
|
||||
|
||||
|
||||
<Tabs.Tab
|
||||
title={`好友列表${activeTab === "friends" && friendsTotal > 0 ? ` (${friendsTotal.toLocaleString()})` : ""}`}
|
||||
title={`好友${activeTab === "friends" && friendsTotal > 0 ? ` (${friendsTotal.toLocaleString()})` : ""}`}
|
||||
key="friends"
|
||||
>
|
||||
<div className={style["friends-content"]}>
|
||||
@@ -406,6 +740,23 @@ const WechatAccountDetail: React.FC = () => {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 好友概要 */}
|
||||
<div className={style["friends-summary"]}>
|
||||
<div className={style["summary-item"]}>
|
||||
<div className={style["summary-label"]}>好友总数</div>
|
||||
<div className={style["summary-value"]}>
|
||||
{friendsTotal || overviewData?.totalFriends || 0}
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["summary-divider"]} />
|
||||
<div className={style["summary-item"]}>
|
||||
<div className={style["summary-label"]}>好友总估值</div>
|
||||
<div className={style["summary-value-highlight"]}>
|
||||
{overviewData?.accountValue?.formatted || "¥1,500,000"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 好友列表 */}
|
||||
<div className={style["friends-list"]}>
|
||||
{isFetchingFriends && friends.length === 0 ? (
|
||||
@@ -431,36 +782,44 @@ const WechatAccountDetail: React.FC = () => {
|
||||
{friends.map(friend => (
|
||||
<div
|
||||
key={friend.id}
|
||||
className={style["friend-item"]}
|
||||
className={style["friend-card"]}
|
||||
onClick={() => handleFriendClick(friend)}
|
||||
>
|
||||
<Avatar
|
||||
src={friend.avatar}
|
||||
className={style["friend-avatar"]}
|
||||
/>
|
||||
<div className={style["friend-info"]}>
|
||||
<div className={style["friend-header"]}>
|
||||
<div className={style["friend-avatar"]}>
|
||||
<Avatar src={friend.avatar} />
|
||||
</div>
|
||||
<div className={style["friend-main"]}>
|
||||
<div className={style["friend-name-row"]}>
|
||||
<div className={style["friend-name"]}>
|
||||
{friend.nickname}
|
||||
{friend.remark && (
|
||||
<span className={style["friend-remark"]}>
|
||||
({friend.remark})
|
||||
</span>
|
||||
)}
|
||||
{friend.nickname || "未知好友"}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className={style["friend-wechat-id"]}>
|
||||
{friend.wechatId}
|
||||
<div className={style["friend-id-row"]}>
|
||||
ID: {friend.wechatId || "-"}
|
||||
</div>
|
||||
<div className={style["friend-tags"]}>
|
||||
{friend.tags?.map((tag, index) => (
|
||||
<Tag
|
||||
key={index}
|
||||
className={style["friend-tag"]}
|
||||
<div className={style["friend-status-row"]}>
|
||||
{friend.statusTags?.map((tag, idx) => (
|
||||
<span
|
||||
key={idx}
|
||||
className={style["friend-status-chip"]}
|
||||
>
|
||||
{typeof tag === "string" ? tag : tag.name}
|
||||
</Tag>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
{friend.remark && (
|
||||
<span className={style["friend-status-chip"]}>
|
||||
{friend.remark}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["friend-value"]}>
|
||||
<div className={style["value-amount"]}>
|
||||
{friend.valueFormatted
|
||||
|| (typeof friend.value === "number"
|
||||
? `¥${friend.value.toLocaleString()}`
|
||||
: "估值 -")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -484,45 +843,118 @@ const WechatAccountDetail: React.FC = () => {
|
||||
</div>
|
||||
</Tabs.Tab>
|
||||
|
||||
<Tabs.Tab title="风险评估" key="risk">
|
||||
<div className={style["risk-content"]}>
|
||||
{accountSummary?.restrictions &&
|
||||
accountSummary.restrictions.length > 0 ? (
|
||||
<div className={style["restrictions-list"]}>
|
||||
{accountSummary.restrictions.map(restriction => (
|
||||
<div
|
||||
key={restriction.id}
|
||||
className={style["restriction-item"]}
|
||||
>
|
||||
<div className={style["restriction-info"]}>
|
||||
<div className={style["restriction-reason"]}>
|
||||
{restriction.reason}
|
||||
</div>
|
||||
<div className={style["restriction-date"]}>
|
||||
{restriction.date
|
||||
? formatDateTime(restriction.date)
|
||||
: "暂无时间"}
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["restriction-level"]}>
|
||||
<span
|
||||
className={`${style["level-badge"]} ${style[`level-${restriction.level}`]}`}
|
||||
>
|
||||
{restriction.level === 1
|
||||
? "低风险"
|
||||
: restriction.level === 2
|
||||
? "中风险"
|
||||
: "高风险"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<Tabs.Tab title="朋友圈" key="moments">
|
||||
<div className={style["moments-content"]}>
|
||||
{/* 功能按钮栏 */}
|
||||
<div className={style["moments-action-bar"]}>
|
||||
<div className={style["action-button"]}>
|
||||
<span className={style["action-icon-text"]}></span>
|
||||
<span className={style["action-text"]}>文本</span>
|
||||
</div>
|
||||
<div className={style["action-button"]}>
|
||||
<span className={style["action-icon-image"]}></span>
|
||||
<span className={style["action-text"]}>图片</span>
|
||||
</div>
|
||||
<div className={style["action-button"]}>
|
||||
<span className={style["action-icon-video"]}></span>
|
||||
<span className={style["action-text"]}>视频</span>
|
||||
</div>
|
||||
<div
|
||||
className={style["action-button-dark"]}
|
||||
onClick={() => setShowExportPopup(true)}
|
||||
>
|
||||
<span className={style["action-icon-export"]}></span>
|
||||
<span className={style["action-text-light"]}>导出</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 朋友圈列表 */}
|
||||
<div className={style["moments-list"]}>
|
||||
{isFetchingMoments && moments.length === 0 ? (
|
||||
<div className={style["loading"]}>
|
||||
<SpinLoading color="primary" style={{ fontSize: 32 }} />
|
||||
</div>
|
||||
) : momentsError ? (
|
||||
<div className={style["error"]}>{momentsError}</div>
|
||||
) : moments.length === 0 ? (
|
||||
<div className={style["empty"]}>暂无朋友圈内容</div>
|
||||
) : (
|
||||
moments.map(moment => {
|
||||
const { day, month } = formatMomentDateParts(
|
||||
moment.createTime,
|
||||
);
|
||||
const timeAgo = formatMomentTimeAgo(moment.createTime);
|
||||
const imageCount = moment.resUrls?.length || 0;
|
||||
// 根据图片数量选择对应的grid类,参考素材管理的实现
|
||||
let gridClass = "";
|
||||
if (imageCount === 1) gridClass = style["single"];
|
||||
else if (imageCount === 2) gridClass = style["double"];
|
||||
else if (imageCount === 3) gridClass = style["triple"];
|
||||
else if (imageCount === 4) gridClass = style["quad"];
|
||||
else if (imageCount > 4) gridClass = style["grid"];
|
||||
|
||||
return (
|
||||
<div className={style["moment-item"]} key={moment.id}>
|
||||
<div className={style["moment-date"]}>
|
||||
<div className={style["date-day"]}>{day}</div>
|
||||
<div className={style["date-month"]}>{month}</div>
|
||||
</div>
|
||||
<div className={style["moment-content"]}>
|
||||
{moment.content && (
|
||||
<div className={style["moment-text"]}>
|
||||
{moment.content}
|
||||
</div>
|
||||
)}
|
||||
{imageCount > 0 && (
|
||||
<div className={style["moment-images"]}>
|
||||
<div
|
||||
className={`${style["image-grid"]} ${gridClass}`}
|
||||
>
|
||||
{moment.resUrls
|
||||
.slice(0, 9)
|
||||
.map((url, index) => (
|
||||
<img
|
||||
key={`${moment.id}-img-${index}`}
|
||||
src={url}
|
||||
alt="朋友圈图片"
|
||||
/>
|
||||
))}
|
||||
{imageCount > 9 && (
|
||||
<div className={style["image-more"]}>
|
||||
+{imageCount - 9}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className={style["moment-footer"]}>
|
||||
<span className={style["moment-time"]}>
|
||||
{timeAgo}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
|
||||
{moments.length < momentsTotal && (
|
||||
<div className={style["moments-load-more"]}>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={handleLoadMoreMoments}
|
||||
loading={isFetchingMoments}
|
||||
disabled={isFetchingMoments}
|
||||
>
|
||||
加载更多
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className={style["empty"]}>暂无风险记录</div>
|
||||
)}
|
||||
</div>
|
||||
</Tabs.Tab>
|
||||
|
||||
</Tabs>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -644,6 +1076,153 @@ const WechatAccountDetail: React.FC = () => {
|
||||
</div>
|
||||
</Popup>
|
||||
|
||||
{/* 朋友圈导出弹窗 */}
|
||||
<Popup
|
||||
visible={showExportPopup}
|
||||
onMaskClick={() => setShowExportPopup(false)}
|
||||
bodyStyle={{ borderRadius: "16px 16px 0 0" }}
|
||||
>
|
||||
<div className={style["popup-content"]}>
|
||||
<div className={style["popup-header"]}>
|
||||
<h3>导出朋友圈</h3>
|
||||
<Button
|
||||
size="small"
|
||||
fill="outline"
|
||||
onClick={() => setShowExportPopup(false)}
|
||||
>
|
||||
关闭
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className={style["export-form"]}>
|
||||
{/* 关键词搜索 */}
|
||||
<div className={style["form-item"]}>
|
||||
<label>关键词搜索</label>
|
||||
<Input
|
||||
placeholder="请输入关键词"
|
||||
value={exportKeyword}
|
||||
onChange={e => setExportKeyword(e.target.value)}
|
||||
allowClear
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 类型筛选 */}
|
||||
<div className={style["form-item"]}>
|
||||
<label>类型筛选</label>
|
||||
<div className={style["type-selector"]}>
|
||||
<div
|
||||
className={`${style["type-option"]} ${
|
||||
exportType === undefined ? style["active"] : ""
|
||||
}`}
|
||||
onClick={() => setExportType(undefined)}
|
||||
>
|
||||
全部
|
||||
</div>
|
||||
<div
|
||||
className={`${style["type-option"]} ${
|
||||
exportType === 4 ? style["active"] : ""
|
||||
}`}
|
||||
onClick={() => setExportType(4)}
|
||||
>
|
||||
文本
|
||||
</div>
|
||||
<div
|
||||
className={`${style["type-option"]} ${
|
||||
exportType === 1 ? style["active"] : ""
|
||||
}`}
|
||||
onClick={() => setExportType(1)}
|
||||
>
|
||||
图片
|
||||
</div>
|
||||
<div
|
||||
className={`${style["type-option"]} ${
|
||||
exportType === 3 ? style["active"] : ""
|
||||
}`}
|
||||
onClick={() => setExportType(3)}
|
||||
>
|
||||
视频
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 开始时间 */}
|
||||
<div className={style["form-item"]}>
|
||||
<label>开始时间</label>
|
||||
<Input
|
||||
readOnly
|
||||
placeholder="请选择开始时间"
|
||||
value={
|
||||
exportStartTime
|
||||
? exportStartTime.toLocaleDateString("zh-CN")
|
||||
: ""
|
||||
}
|
||||
onClick={() => setShowStartTimePicker(true)}
|
||||
/>
|
||||
<DatePicker
|
||||
visible={showStartTimePicker}
|
||||
title="开始时间"
|
||||
value={exportStartTime}
|
||||
onClose={() => setShowStartTimePicker(false)}
|
||||
onConfirm={val => {
|
||||
setExportStartTime(val);
|
||||
setShowStartTimePicker(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 结束时间 */}
|
||||
<div className={style["form-item"]}>
|
||||
<label>结束时间</label>
|
||||
<Input
|
||||
readOnly
|
||||
placeholder="请选择结束时间"
|
||||
value={
|
||||
exportEndTime ? exportEndTime.toLocaleDateString("zh-CN") : ""
|
||||
}
|
||||
onClick={() => setShowEndTimePicker(true)}
|
||||
/>
|
||||
<DatePicker
|
||||
visible={showEndTimePicker}
|
||||
title="结束时间"
|
||||
value={exportEndTime}
|
||||
onClose={() => setShowEndTimePicker(false)}
|
||||
onConfirm={val => {
|
||||
setExportEndTime(val);
|
||||
setShowEndTimePicker(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={style["popup-footer"]}>
|
||||
<Button
|
||||
block
|
||||
color="primary"
|
||||
onClick={handleExportMoments}
|
||||
loading={exportLoading}
|
||||
disabled={exportLoading}
|
||||
>
|
||||
{exportLoading ? "导出中..." : "确认导出"}
|
||||
</Button>
|
||||
<Button
|
||||
block
|
||||
color="danger"
|
||||
fill="outline"
|
||||
onClick={() => {
|
||||
setShowExportPopup(false);
|
||||
setExportKeyword("");
|
||||
setExportType(undefined);
|
||||
setExportStartTime(null);
|
||||
setExportEndTime(null);
|
||||
}}
|
||||
style={{ marginTop: 12 }}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
|
||||
{/* 好友详情弹窗 */}
|
||||
{/* Removed */}
|
||||
</Layout>
|
||||
|
||||
@@ -2,6 +2,39 @@
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.filter-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
.filter-button {
|
||||
flex: 1;
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #d9d9d9;
|
||||
background: #fff;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: #1677ff;
|
||||
color: #1677ff;
|
||||
}
|
||||
|
||||
&.filter-button-active {
|
||||
background: #1677ff;
|
||||
border-color: #1677ff;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
|
||||
@@ -33,11 +33,12 @@ const WechatAccounts: React.FC = () => {
|
||||
const [totalAccounts, setTotalAccounts] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const [statusFilter, setStatusFilter] = useState<"all" | "online" | "offline">("all");
|
||||
|
||||
// 获取路由参数 wechatStatus
|
||||
const wechatStatus = searchParams.get("wechatStatus");
|
||||
|
||||
const fetchAccounts = async (page = 1, keyword = "") => {
|
||||
const fetchAccounts = async (page = 1, keyword = "", status?: "all" | "online" | "offline") => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const params: any = {
|
||||
@@ -46,8 +47,12 @@ const WechatAccounts: React.FC = () => {
|
||||
keyword,
|
||||
};
|
||||
|
||||
// 如果有 wechatStatus 参数,添加到请求参数中
|
||||
if (wechatStatus) {
|
||||
// 优先使用传入的status参数,否则使用路由参数,最后使用状态中的筛选
|
||||
const filterStatus = status || wechatStatus || statusFilter;
|
||||
|
||||
if (filterStatus && filterStatus !== "all") {
|
||||
params.wechatStatus = filterStatus === "online" ? "1" : "0";
|
||||
} else if (wechatStatus) {
|
||||
params.wechatStatus = wechatStatus;
|
||||
}
|
||||
|
||||
@@ -60,7 +65,7 @@ const WechatAccounts: React.FC = () => {
|
||||
setTotalAccounts(0);
|
||||
}
|
||||
} catch (e) {
|
||||
Toast.show({ content: "获取微信号失败", position: "top" });
|
||||
|
||||
setAccounts([]);
|
||||
setTotalAccounts(0);
|
||||
} finally {
|
||||
@@ -69,18 +74,24 @@ const WechatAccounts: React.FC = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchAccounts(currentPage, searchTerm);
|
||||
fetchAccounts(currentPage, searchTerm, statusFilter);
|
||||
// eslint-disable-next-line
|
||||
}, [currentPage]);
|
||||
}, [currentPage, statusFilter]);
|
||||
|
||||
const handleSearch = () => {
|
||||
setCurrentPage(1);
|
||||
fetchAccounts(1, searchTerm);
|
||||
fetchAccounts(1, searchTerm, statusFilter);
|
||||
};
|
||||
|
||||
const handleStatusFilterChange = (status: "all" | "online" | "offline") => {
|
||||
setStatusFilter(status);
|
||||
setCurrentPage(1);
|
||||
fetchAccounts(1, searchTerm, status);
|
||||
};
|
||||
|
||||
const handleRefresh = async () => {
|
||||
setIsRefreshing(true);
|
||||
await fetchAccounts(currentPage, searchTerm);
|
||||
await fetchAccounts(currentPage, searchTerm, statusFilter);
|
||||
setIsRefreshing(false);
|
||||
Toast.show({ content: "刷新成功", position: "top" });
|
||||
};
|
||||
@@ -122,6 +133,31 @@ const WechatAccounts: React.FC = () => {
|
||||
<ReloadOutlined />
|
||||
</Button>
|
||||
</div>
|
||||
<div className={style["filter-bar"]}>
|
||||
<div className={style["filter-buttons"]}>
|
||||
<Button
|
||||
size="small"
|
||||
className={`${style["filter-button"]} ${statusFilter === "all" ? style["filter-button-active"] : ""}`}
|
||||
onClick={() => handleStatusFilterChange("all")}
|
||||
>
|
||||
全部
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
className={`${style["filter-button"]} ${statusFilter === "online" ? style["filter-button-active"] : ""}`}
|
||||
onClick={() => handleStatusFilterChange("online")}
|
||||
>
|
||||
在线
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
className={`${style["filter-button"]} ${statusFilter === "offline" ? style["filter-button-active"] : ""}`}
|
||||
onClick={() => handleStatusFilterChange("offline")}
|
||||
>
|
||||
离线
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -369,13 +369,15 @@ const ScenarioList: React.FC = () => {
|
||||
backFn={() => navigate("/scenarios")}
|
||||
title={scenarioName || ""}
|
||||
right={
|
||||
<Button
|
||||
size="small"
|
||||
color="primary"
|
||||
onClick={handleCreateNewPlan}
|
||||
>
|
||||
<PlusOutlined /> 新建计划
|
||||
</Button>
|
||||
scenarioId !== "10" ? (
|
||||
<Button
|
||||
size="small"
|
||||
color="primary"
|
||||
onClick={handleCreateNewPlan}
|
||||
>
|
||||
<PlusOutlined /> 新建计划
|
||||
</Button>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -424,13 +426,15 @@ const ScenarioList: React.FC = () => {
|
||||
<div className={style["empty-text"]}>
|
||||
{searchTerm ? "没有找到匹配的计划" : "暂无计划"}
|
||||
</div>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={handleCreateNewPlan}
|
||||
className={style["create-first-btn"]}
|
||||
>
|
||||
<PlusOutlined /> 创建第一个计划
|
||||
</Button>
|
||||
{scenarioId !== "10" && (
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={handleCreateNewPlan}
|
||||
className={style["create-first-btn"]}
|
||||
>
|
||||
<PlusOutlined /> 创建第一个计划
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -242,7 +242,9 @@ const BasicSettings: React.FC<BasicSettingsProps> = ({
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles["basic-scene-grid"]}>
|
||||
{sceneList.map(scene => {
|
||||
{sceneList
|
||||
.filter(scene => scene.id !== 10)
|
||||
.map(scene => {
|
||||
const selected = formData.scenario === scene.id;
|
||||
return (
|
||||
<button
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
export * from "./module/user";
|
||||
export * from "./module/app";
|
||||
export * from "./module/settings";
|
||||
export * from "./module/websocket/websocket";
|
||||
|
||||
// 导入store实例
|
||||
import { useUserStore } from "./module/user";
|
||||
import { useAppStore } from "./module/app";
|
||||
import { useSettingsStore } from "./module/settings";
|
||||
import { useWebSocketStore } from "./module/websocket/websocket";
|
||||
|
||||
// 导出持久化store创建函数
|
||||
export {
|
||||
@@ -34,7 +32,6 @@ export interface StoreState {
|
||||
user: ReturnType<typeof useUserStore.getState>;
|
||||
app: ReturnType<typeof useAppStore.getState>;
|
||||
settings: ReturnType<typeof useSettingsStore.getState>;
|
||||
websocket: ReturnType<typeof useWebSocketStore.getState>;
|
||||
}
|
||||
|
||||
// 便利的store访问函数
|
||||
@@ -42,14 +39,12 @@ export const getStores = (): StoreState => ({
|
||||
user: useUserStore.getState(),
|
||||
app: useAppStore.getState(),
|
||||
settings: useSettingsStore.getState(),
|
||||
websocket: useWebSocketStore.getState(),
|
||||
});
|
||||
|
||||
// 获取特定store状态
|
||||
export const getUserStore = () => useUserStore.getState();
|
||||
export const getAppStore = () => useAppStore.getState();
|
||||
export const getSettingsStore = () => useSettingsStore.getState();
|
||||
export const getWebSocketStore = () => useWebSocketStore.getState();
|
||||
|
||||
// 清除所有持久化数据(使用工具函数)
|
||||
export const clearAllPersistedData = clearAllData;
|
||||
@@ -61,7 +56,6 @@ export const getPersistKeys = () => Object.values(PERSIST_KEYS);
|
||||
export const subscribeToUserStore = useUserStore.subscribe;
|
||||
export const subscribeToAppStore = useAppStore.subscribe;
|
||||
export const subscribeToSettingsStore = useSettingsStore.subscribe;
|
||||
export const subscribeToWebSocketStore = useWebSocketStore.subscribe;
|
||||
|
||||
// 组合订阅函数
|
||||
export const subscribeToAllStores = (callback: (state: StoreState) => void) => {
|
||||
@@ -74,14 +68,10 @@ export const subscribeToAllStores = (callback: (state: StoreState) => void) => {
|
||||
const unsubscribeSettings = useSettingsStore.subscribe(() => {
|
||||
callback(getStores());
|
||||
});
|
||||
const unsubscribeWebSocket = useWebSocketStore.subscribe(() => {
|
||||
callback(getStores());
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribeUser();
|
||||
unsubscribeApp();
|
||||
unsubscribeSettings();
|
||||
unsubscribeWebSocket();
|
||||
};
|
||||
};
|
||||
|
||||
70
Cunkebao/src/utils/cacheCleaner.ts
Normal file
70
Cunkebao/src/utils/cacheCleaner.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
// 全局缓存清理工具:浏览器存储 + IndexedDB + Zustand store
|
||||
import { clearAllPersistedData } from "@/store";
|
||||
import { useUserStore } from "@/store/module/user";
|
||||
import { useAppStore } from "@/store/module/app";
|
||||
import { useSettingsStore } from "@/store/module/settings";
|
||||
|
||||
const isBrowser = typeof window !== "undefined";
|
||||
|
||||
const safeStorageClear = (storage?: Storage) => {
|
||||
if (!storage) return;
|
||||
try {
|
||||
storage.clear();
|
||||
} catch (error) {
|
||||
console.warn("清理存储失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
export const clearBrowserStorage = () => {
|
||||
if (!isBrowser) return;
|
||||
safeStorageClear(window.localStorage);
|
||||
safeStorageClear(window.sessionStorage);
|
||||
try {
|
||||
clearAllPersistedData();
|
||||
} catch (error) {
|
||||
console.warn("清理持久化 store 失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
export const clearAllIndexedDB = async (): Promise<void> => {
|
||||
if (!isBrowser || !window.indexedDB || !indexedDB.databases) return;
|
||||
|
||||
const databases = await indexedDB.databases();
|
||||
const deleteJobs = databases
|
||||
.map(db => db.name)
|
||||
.filter((name): name is string => Boolean(name))
|
||||
.map(
|
||||
name =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
const request = indexedDB.deleteDatabase(name);
|
||||
request.onsuccess = () => resolve();
|
||||
request.onerror = () => reject(new Error(`删除数据库 ${name} 失败`));
|
||||
request.onblocked = () => {
|
||||
setTimeout(() => {
|
||||
const retry = indexedDB.deleteDatabase(name);
|
||||
retry.onsuccess = () => resolve();
|
||||
retry.onerror = () =>
|
||||
reject(new Error(`删除数据库 ${name} 失败`));
|
||||
}, 100);
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
await Promise.allSettled(deleteJobs);
|
||||
};
|
||||
|
||||
export const resetAllStores = () => {
|
||||
const userStore = useUserStore.getState();
|
||||
const appStore = useAppStore.getState();
|
||||
const settingsStore = useSettingsStore.getState();
|
||||
|
||||
userStore?.clearUser?.();
|
||||
appStore?.resetAppState?.();
|
||||
settingsStore?.resetSettings?.();
|
||||
};
|
||||
|
||||
export const clearApplicationCache = async () => {
|
||||
clearBrowserStorage();
|
||||
await clearAllIndexedDB();
|
||||
resetAllStores();
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
73
Moncter/src/utils/cacheCleaner.ts
Normal file
73
Moncter/src/utils/cacheCleaner.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
// 缓存清理工具,统一处理浏览器存储与 Zustand store
|
||||
import { clearAllPersistedData } from "@/store";
|
||||
import { useUserStore } from "@/store/module/user";
|
||||
import { useAppStore } from "@/store/module/app";
|
||||
import { useSettingsStore } from "@/store/module/settings";
|
||||
|
||||
const isBrowser = typeof window !== "undefined";
|
||||
|
||||
const safeStorageClear = (storage?: Storage) => {
|
||||
if (!storage) return;
|
||||
try {
|
||||
storage.clear();
|
||||
} catch (error) {
|
||||
console.warn("清理存储失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
export const clearBrowserStorage = () => {
|
||||
if (!isBrowser) return;
|
||||
safeStorageClear(window.localStorage);
|
||||
safeStorageClear(window.sessionStorage);
|
||||
// 清理自定义持久化数据
|
||||
try {
|
||||
clearAllPersistedData();
|
||||
} catch (error) {
|
||||
console.warn("清理持久化 store 失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
export const clearAllIndexedDB = async (): Promise<void> => {
|
||||
if (!isBrowser || !window.indexedDB || !indexedDB.databases) return;
|
||||
|
||||
const databases = await indexedDB.databases();
|
||||
const deleteJobs = databases
|
||||
.map(db => db.name)
|
||||
.filter((name): name is string => Boolean(name))
|
||||
.map(
|
||||
name =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
const request = indexedDB.deleteDatabase(name);
|
||||
request.onsuccess = () => resolve();
|
||||
request.onerror = () =>
|
||||
reject(new Error(`删除数据库 ${name} 失败`));
|
||||
request.onblocked = () => {
|
||||
setTimeout(() => {
|
||||
const retry = indexedDB.deleteDatabase(name);
|
||||
retry.onsuccess = () => resolve();
|
||||
retry.onerror = () =>
|
||||
reject(new Error(`删除数据库 ${name} 失败`));
|
||||
}, 100);
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
await Promise.allSettled(deleteJobs);
|
||||
};
|
||||
|
||||
export const resetAllStores = () => {
|
||||
const userStore = useUserStore.getState();
|
||||
const appStore = useAppStore.getState();
|
||||
const settingsStore = useSettingsStore.getState();
|
||||
|
||||
userStore?.clearUser?.();
|
||||
appStore?.resetAppState?.();
|
||||
settingsStore?.resetSettings?.();
|
||||
};
|
||||
|
||||
export const clearApplicationCache = async () => {
|
||||
clearBrowserStorage();
|
||||
await clearAllIndexedDB();
|
||||
resetAllStores();
|
||||
};
|
||||
|
||||
3
Moncter/weChat.ts
Normal file
3
Moncter/weChat.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
|
||||
|
||||
374
Moncter/数据库/KR.js
Normal file
374
Moncter/数据库/KR.js
Normal file
@@ -0,0 +1,374 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:18:11
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 厦门京东内
|
||||
// ----------------------------
|
||||
db.getCollection("厦门京东内").drop();
|
||||
db.createCollection("厦门京东内");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 厦门用户资产2025年9月
|
||||
// ----------------------------
|
||||
db.getCollection("厦门用户资产2025年9月").drop();
|
||||
db.createCollection("厦门用户资产2025年9月");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 厦门用户资产2025年9月_优化版
|
||||
// ----------------------------
|
||||
db.getCollection("厦门用户资产2025年9月_优化版").drop();
|
||||
db.createCollection("厦门用户资产2025年9月_优化版");
|
||||
db.getCollection("厦门用户资产2025年9月_优化版").createIndex({
|
||||
user_level: NumberInt("1")
|
||||
}, {
|
||||
name: "user_level_1"
|
||||
});
|
||||
db.getCollection("厦门用户资产2025年9月_优化版").createIndex({
|
||||
total_score: NumberInt("-1")
|
||||
}, {
|
||||
name: "total_score_-1"
|
||||
});
|
||||
db.getCollection("厦门用户资产2025年9月_优化版").createIndex({
|
||||
mobile: NumberInt("1")
|
||||
}, {
|
||||
name: "mobile_1"
|
||||
});
|
||||
db.getCollection("厦门用户资产2025年9月_优化版").createIndex({
|
||||
city: NumberInt("1")
|
||||
}, {
|
||||
name: "city_1"
|
||||
});
|
||||
db.getCollection("厦门用户资产2025年9月_优化版").createIndex({
|
||||
name: NumberInt("1")
|
||||
}, {
|
||||
name: "name_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 游戏_深度用户有过语音聊天、魔兽等
|
||||
// ----------------------------
|
||||
db.getCollection("游戏_深度用户有过语音聊天、魔兽等").drop();
|
||||
db.createCollection("游戏_深度用户有过语音聊天、魔兽等");
|
||||
db.getCollection("游戏_深度用户有过语音聊天、魔兽等").createIndex({
|
||||
user_id: NumberInt("1")
|
||||
}, {
|
||||
name: "user_id_1"
|
||||
});
|
||||
db.getCollection("游戏_深度用户有过语音聊天、魔兽等").createIndex({
|
||||
user_value: NumberInt("1")
|
||||
}, {
|
||||
name: "user_value_1"
|
||||
});
|
||||
db.getCollection("游戏_深度用户有过语音聊天、魔兽等").createIndex({
|
||||
overlap_count: NumberInt("1")
|
||||
}, {
|
||||
name: "overlap_count_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 用户估值
|
||||
// ----------------------------
|
||||
db.getCollection("用户估值").drop();
|
||||
db.createCollection("用户估值");
|
||||
db.getCollection("用户估值").createIndex({
|
||||
user_key: NumberInt("1")
|
||||
}, {
|
||||
name: "user_key_1",
|
||||
unique: true
|
||||
});
|
||||
db.getCollection("用户估值").createIndex({
|
||||
phone: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1"
|
||||
});
|
||||
db.getCollection("用户估值").createIndex({
|
||||
phone_masked: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_masked_1"
|
||||
});
|
||||
db.getCollection("用户估值").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1"
|
||||
});
|
||||
db.getCollection("用户估值").createIndex({
|
||||
name: NumberInt("1")
|
||||
}, {
|
||||
name: "name_1"
|
||||
});
|
||||
db.getCollection("用户估值").createIndex({
|
||||
province: NumberInt("1"),
|
||||
city: NumberInt("1")
|
||||
}, {
|
||||
name: "province_1_city_1"
|
||||
});
|
||||
db.getCollection("用户估值").createIndex({
|
||||
computed_at: NumberInt("-1")
|
||||
}, {
|
||||
name: "computed_at_-1"
|
||||
});
|
||||
db.getCollection("用户估值").createIndex({
|
||||
source_channels: NumberInt("1")
|
||||
}, {
|
||||
name: "source_channels_1"
|
||||
});
|
||||
db.getCollection("用户估值").createIndex({
|
||||
"data_quality.completeness": NumberInt("-1")
|
||||
}, {
|
||||
name: "data_quality.completeness_-1"
|
||||
});
|
||||
db.getCollection("用户估值").createIndex({
|
||||
user_evaluation_score: NumberInt("-1")
|
||||
}, {
|
||||
name: "user_evaluation_score_-1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 用户资产整合
|
||||
// ----------------------------
|
||||
db.getCollection("用户资产整合").drop();
|
||||
db.createCollection("用户资产整合");
|
||||
db.getCollection("用户资产整合").createIndex({
|
||||
user_key: NumberInt("1")
|
||||
}, {
|
||||
name: "idx_user_key"
|
||||
});
|
||||
db.getCollection("用户资产整合").createIndex({
|
||||
phone: NumberInt("1")
|
||||
}, {
|
||||
name: "idx_phone"
|
||||
});
|
||||
db.getCollection("用户资产整合").createIndex({
|
||||
consumption_level: NumberInt("1")
|
||||
}, {
|
||||
name: "idx_consumption_level"
|
||||
});
|
||||
db.getCollection("用户资产整合").createIndex({
|
||||
consumption_score: NumberInt("-1")
|
||||
}, {
|
||||
name: "idx_consumption_score_desc"
|
||||
});
|
||||
db.getCollection("用户资产整合").createIndex({
|
||||
city: NumberInt("1")
|
||||
}, {
|
||||
name: "idx_city"
|
||||
});
|
||||
db.getCollection("用户资产整合").createIndex({
|
||||
age_range: NumberInt("1")
|
||||
}, {
|
||||
name: "idx_age_range"
|
||||
});
|
||||
db.getCollection("用户资产整合").createIndex({
|
||||
user_evaluation_score: NumberInt("-1")
|
||||
}, {
|
||||
name: "idx_user_evaluation_score_desc"
|
||||
});
|
||||
db.getCollection("用户资产整合").createIndex({
|
||||
consumption_level: NumberInt("1"),
|
||||
consumption_score: NumberInt("-1")
|
||||
}, {
|
||||
name: "idx_level_score"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 金融客户_厦门_A级用户
|
||||
// ----------------------------
|
||||
db.getCollection("金融客户_厦门_A级用户").drop();
|
||||
db.createCollection("金融客户_厦门_A级用户");
|
||||
db.getCollection("金融客户_厦门_A级用户").createIndex({
|
||||
total_score: NumberInt("1")
|
||||
}, {
|
||||
name: "total_score_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_A级用户").createIndex({
|
||||
"姓名": NumberInt("1")
|
||||
}, {
|
||||
name: "姓名_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_A级用户").createIndex({
|
||||
"手机": NumberInt("1")
|
||||
}, {
|
||||
name: "手机_1",
|
||||
background: true
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 金融客户_厦门_B级用户
|
||||
// ----------------------------
|
||||
db.getCollection("金融客户_厦门_B级用户").drop();
|
||||
db.createCollection("金融客户_厦门_B级用户");
|
||||
db.getCollection("金融客户_厦门_B级用户").createIndex({
|
||||
total_score: NumberInt("1")
|
||||
}, {
|
||||
name: "total_score_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_B级用户").createIndex({
|
||||
"姓名": NumberInt("1")
|
||||
}, {
|
||||
name: "姓名_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_B级用户").createIndex({
|
||||
"手机": NumberInt("1")
|
||||
}, {
|
||||
name: "手机_1",
|
||||
background: true
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 金融客户_厦门_C级用户
|
||||
// ----------------------------
|
||||
db.getCollection("金融客户_厦门_C级用户").drop();
|
||||
db.createCollection("金融客户_厦门_C级用户");
|
||||
db.getCollection("金融客户_厦门_C级用户").createIndex({
|
||||
total_score: NumberInt("1")
|
||||
}, {
|
||||
name: "total_score_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_C级用户").createIndex({
|
||||
"姓名": NumberInt("1")
|
||||
}, {
|
||||
name: "姓名_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_C级用户").createIndex({
|
||||
"手机": NumberInt("1")
|
||||
}, {
|
||||
name: "手机_1",
|
||||
background: true
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 金融客户_厦门_D级用户
|
||||
// ----------------------------
|
||||
db.getCollection("金融客户_厦门_D级用户").drop();
|
||||
db.createCollection("金融客户_厦门_D级用户");
|
||||
db.getCollection("金融客户_厦门_D级用户").createIndex({
|
||||
total_score: NumberInt("1")
|
||||
}, {
|
||||
name: "total_score_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_D级用户").createIndex({
|
||||
"姓名": NumberInt("1")
|
||||
}, {
|
||||
name: "姓名_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_D级用户").createIndex({
|
||||
"手机": NumberInt("1")
|
||||
}, {
|
||||
name: "手机_1",
|
||||
background: true
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 金融客户_厦门_E级用户
|
||||
// ----------------------------
|
||||
db.getCollection("金融客户_厦门_E级用户").drop();
|
||||
db.createCollection("金融客户_厦门_E级用户");
|
||||
db.getCollection("金融客户_厦门_E级用户").createIndex({
|
||||
total_score: NumberInt("1")
|
||||
}, {
|
||||
name: "total_score_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_E级用户").createIndex({
|
||||
"姓名": NumberInt("1")
|
||||
}, {
|
||||
name: "姓名_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_E级用户").createIndex({
|
||||
"手机": NumberInt("1")
|
||||
}, {
|
||||
name: "手机_1",
|
||||
background: true
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 金融客户_厦门_S级用户
|
||||
// ----------------------------
|
||||
db.getCollection("金融客户_厦门_S级用户").drop();
|
||||
db.createCollection("金融客户_厦门_S级用户");
|
||||
db.getCollection("金融客户_厦门_S级用户").createIndex({
|
||||
total_score: NumberInt("1")
|
||||
}, {
|
||||
name: "total_score_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_S级用户").createIndex({
|
||||
"姓名": NumberInt("1")
|
||||
}, {
|
||||
name: "姓名_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_S级用户").createIndex({
|
||||
"手机": NumberInt("1")
|
||||
}, {
|
||||
name: "手机_1",
|
||||
background: true
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 金融组02250905
|
||||
// ----------------------------
|
||||
db.getCollection("金融组02250905").drop();
|
||||
db.createCollection("金融组02250905");
|
||||
db.getCollection("金融组02250905").createIndex({
|
||||
"姓名": NumberInt("1")
|
||||
}, {
|
||||
name: "姓名_1"
|
||||
});
|
||||
db.getCollection("金融组02250905").createIndex({
|
||||
"手机": NumberInt("1")
|
||||
}, {
|
||||
name: "手机_1"
|
||||
});
|
||||
db.getCollection("金融组02250905").createIndex({
|
||||
"身份证": NumberInt("1")
|
||||
}, {
|
||||
name: "身份证_1"
|
||||
});
|
||||
db.getCollection("金融组02250905").createIndex({
|
||||
"综合评分": NumberInt("-1")
|
||||
}, {
|
||||
name: "综合评分_-1"
|
||||
});
|
||||
db.getCollection("金融组02250905").createIndex({
|
||||
"用户等级": NumberInt("1")
|
||||
}, {
|
||||
name: "用户等级_1"
|
||||
});
|
||||
db.getCollection("金融组02250905").createIndex({
|
||||
"城市": NumberInt("1")
|
||||
}, {
|
||||
name: "城市_1"
|
||||
});
|
||||
509
Moncter/数据库/KR_KR.js
Normal file
509
Moncter/数据库/KR_KR.js
Normal file
@@ -0,0 +1,509 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_KR
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:18:19
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 34万HR人力资源经理企业1
|
||||
// ----------------------------
|
||||
db.getCollection("34万HR人力资源经理企业1").drop();
|
||||
db.createCollection("34万HR人力资源经理企业1");
|
||||
db.getCollection("34万HR人力资源经理企业1").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
db.getCollection("34万HR人力资源经理企业1").createIndex({
|
||||
"电话": NumberInt("1")
|
||||
}, {
|
||||
name: "电话_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 34万HR人力资源经理企业3
|
||||
// ----------------------------
|
||||
db.getCollection("34万HR人力资源经理企业3").drop();
|
||||
db.createCollection("34万HR人力资源经理企业3");
|
||||
db.getCollection("34万HR人力资源经理企业3").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 34万HR人力资源经理企业4
|
||||
// ----------------------------
|
||||
db.getCollection("34万HR人力资源经理企业4").drop();
|
||||
db.createCollection("34万HR人力资源经理企业4");
|
||||
db.getCollection("34万HR人力资源经理企业4").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 34万HR人力资源经理企业5
|
||||
// ----------------------------
|
||||
db.getCollection("34万HR人力资源经理企业5").drop();
|
||||
db.createCollection("34万HR人力资源经理企业5");
|
||||
db.getCollection("34万HR人力资源经理企业5").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 51JOB
|
||||
// ----------------------------
|
||||
db.getCollection("51JOB").drop();
|
||||
db.createCollection("51JOB");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 8万条借款数据
|
||||
// ----------------------------
|
||||
db.getCollection("8万条借款数据").drop();
|
||||
db.createCollection("8万条借款数据");
|
||||
db.getCollection("8万条借款数据").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for CSDN用户数据
|
||||
// ----------------------------
|
||||
db.getCollection("CSDN用户数据").drop();
|
||||
db.createCollection("CSDN用户数据");
|
||||
db.getCollection("CSDN用户数据").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1"
|
||||
});
|
||||
db.getCollection("CSDN用户数据").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1"
|
||||
});
|
||||
db.getCollection("CSDN用户数据").createIndex({
|
||||
domain: NumberInt("1")
|
||||
}, {
|
||||
name: "domain_1"
|
||||
});
|
||||
db.getCollection("CSDN用户数据").createIndex({
|
||||
created_at: NumberInt("-1")
|
||||
}, {
|
||||
name: "created_at_-1"
|
||||
});
|
||||
db.getCollection("CSDN用户数据").createIndex({
|
||||
data_source: NumberInt("1")
|
||||
}, {
|
||||
name: "data_source_1"
|
||||
});
|
||||
db.getCollection("CSDN用户数据").createIndex({
|
||||
username: NumberInt("1"),
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1_email_1",
|
||||
unique: true
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for Sheet2
|
||||
// ----------------------------
|
||||
db.getCollection("Sheet2").drop();
|
||||
db.createCollection("Sheet2");
|
||||
db.getCollection("Sheet2").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for pw_members
|
||||
// ----------------------------
|
||||
db.getCollection("pw_members").drop();
|
||||
db.createCollection("pw_members");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for www.jfclub.com
|
||||
// ----------------------------
|
||||
db.getCollection("www.jfclub.com").drop();
|
||||
db.createCollection("www.jfclub.com");
|
||||
db.getCollection("www.jfclub.com").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
db.getCollection("www.jfclub.com").createIndex({
|
||||
u_id: NumberInt("1")
|
||||
}, {
|
||||
name: "u_id_1"
|
||||
});
|
||||
db.getCollection("www.jfclub.com").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1"
|
||||
});
|
||||
db.getCollection("www.jfclub.com").createIndex({
|
||||
phone: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1"
|
||||
});
|
||||
db.getCollection("www.jfclub.com").createIndex({
|
||||
name: NumberInt("1")
|
||||
}, {
|
||||
name: "name_1"
|
||||
});
|
||||
db.getCollection("www.jfclub.com").createIndex({
|
||||
email: NumberInt("1"),
|
||||
name: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1_name_1"
|
||||
});
|
||||
db.getCollection("www.jfclub.com").createIndex({
|
||||
phone: NumberInt("1"),
|
||||
name: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1_name_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 人人网用户数据
|
||||
// ----------------------------
|
||||
db.getCollection("人人网用户数据").drop();
|
||||
db.createCollection("人人网用户数据");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 借贷
|
||||
// ----------------------------
|
||||
db.getCollection("借贷").drop();
|
||||
db.createCollection("借贷");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 全国车主数据集_2020年
|
||||
// ----------------------------
|
||||
db.getCollection("全国车主数据集_2020年").drop();
|
||||
db.createCollection("全国车主数据集_2020年");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 北京本科
|
||||
// ----------------------------
|
||||
db.getCollection("北京本科").drop();
|
||||
db.createCollection("北京本科");
|
||||
db.getCollection("北京本科").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 千脑云电脑_qiannao_com
|
||||
// ----------------------------
|
||||
db.getCollection("千脑云电脑_qiannao_com").drop();
|
||||
db.createCollection("千脑云电脑_qiannao_com");
|
||||
db.getCollection("千脑云电脑_qiannao_com").createIndex({
|
||||
"用户名": NumberInt("1")
|
||||
}, {
|
||||
name: "用户名_1"
|
||||
});
|
||||
db.getCollection("千脑云电脑_qiannao_com").createIndex({
|
||||
"邮箱": NumberInt("1")
|
||||
}, {
|
||||
name: "邮箱_1"
|
||||
});
|
||||
db.getCollection("千脑云电脑_qiannao_com").createIndex({
|
||||
"用户ID": NumberInt("1")
|
||||
}, {
|
||||
name: "用户ID_1"
|
||||
});
|
||||
db.getCollection("千脑云电脑_qiannao_com").createIndex({
|
||||
"创建时间": NumberInt("1")
|
||||
}, {
|
||||
name: "创建时间_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 天涯论坛tianya.cn
|
||||
// ----------------------------
|
||||
db.getCollection("天涯论坛tianya.cn").drop();
|
||||
db.createCollection("天涯论坛tianya.cn");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 情感
|
||||
// ----------------------------
|
||||
db.getCollection("情感").drop();
|
||||
db.createCollection("情感");
|
||||
db.getCollection("情感").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
db.getCollection("情感").createIndex({
|
||||
"手机": NumberInt("1")
|
||||
}, {
|
||||
name: "手机_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 慈溪人才网
|
||||
// ----------------------------
|
||||
db.getCollection("慈溪人才网").drop();
|
||||
db.createCollection("慈溪人才网");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 房产网
|
||||
// ----------------------------
|
||||
db.getCollection("房产网").drop();
|
||||
db.createCollection("房产网");
|
||||
db.getCollection("房产网").createIndex({
|
||||
uid: NumberInt("1")
|
||||
}, {
|
||||
name: "uid_1",
|
||||
unique: true
|
||||
});
|
||||
db.getCollection("房产网").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1"
|
||||
});
|
||||
db.getCollection("房产网").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1"
|
||||
});
|
||||
db.getCollection("房产网").createIndex({
|
||||
regdate: NumberInt("1")
|
||||
}, {
|
||||
name: "regdate_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 抖音直播粉
|
||||
// ----------------------------
|
||||
db.getCollection("抖音直播粉").drop();
|
||||
db.createCollection("抖音直播粉");
|
||||
db.getCollection("抖音直播粉").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
db.getCollection("抖音直播粉").createIndex({
|
||||
Uid: NumberInt("1")
|
||||
}, {
|
||||
name: "Uid_1"
|
||||
});
|
||||
db.getCollection("抖音直播粉").createIndex({
|
||||
sec_uid: NumberInt("1")
|
||||
}, {
|
||||
name: "sec_uid_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 收藏品微信好友
|
||||
// ----------------------------
|
||||
db.getCollection("收藏品微信好友").drop();
|
||||
db.createCollection("收藏品微信好友");
|
||||
db.getCollection("收藏品微信好友").createIndex({
|
||||
WechatId: NumberInt("1")
|
||||
}, {
|
||||
name: "IX_Kefure_WechatFriend_WechatId"
|
||||
});
|
||||
db.getCollection("收藏品微信好友").createIndex({
|
||||
OwnerWechatId: NumberInt("1")
|
||||
}, {
|
||||
name: "IX_Kefure_WechatFriend_OwnerWechatId"
|
||||
});
|
||||
db.getCollection("收藏品微信好友").createIndex({
|
||||
Phone: NumberInt("1")
|
||||
}, {
|
||||
name: "Phone_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 收藏品用户
|
||||
// ----------------------------
|
||||
db.getCollection("收藏品用户").drop();
|
||||
db.createCollection("收藏品用户");
|
||||
db.getCollection("收藏品用户").createIndex({
|
||||
Id: NumberInt("1")
|
||||
}, {
|
||||
name: "Id_1"
|
||||
});
|
||||
db.getCollection("收藏品用户").createIndex({
|
||||
WechatAccountId: NumberInt("1")
|
||||
}, {
|
||||
name: "WechatAccountId_1"
|
||||
});
|
||||
db.getCollection("收藏品用户").createIndex({
|
||||
WechatId: NumberInt("1")
|
||||
}, {
|
||||
name: "WechatId_1"
|
||||
});
|
||||
db.getCollection("收藏品用户").createIndex({
|
||||
AccountId: NumberInt("1")
|
||||
}, {
|
||||
name: "AccountId_1"
|
||||
});
|
||||
db.getCollection("收藏品用户").createIndex({
|
||||
OwnerWechatId: NumberInt("1")
|
||||
}, {
|
||||
name: "OwnerWechatId_1"
|
||||
});
|
||||
db.getCollection("收藏品用户").createIndex({
|
||||
GroupId: NumberInt("1")
|
||||
}, {
|
||||
name: "GroupId_1"
|
||||
});
|
||||
db.getCollection("收藏品用户").createIndex({
|
||||
Nickname: NumberInt("1")
|
||||
}, {
|
||||
name: "Nickname_1"
|
||||
});
|
||||
db.getCollection("收藏品用户").createIndex({
|
||||
AccountNickname: NumberInt("1")
|
||||
}, {
|
||||
name: "AccountNickname_1"
|
||||
});
|
||||
db.getCollection("收藏品用户").createIndex({
|
||||
OwnerNickname: NumberInt("1")
|
||||
}, {
|
||||
name: "OwnerNickname_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 木蚂蚁munayi_com
|
||||
// ----------------------------
|
||||
db.getCollection("木蚂蚁munayi_com").drop();
|
||||
db.createCollection("木蚂蚁munayi_com");
|
||||
db.getCollection("木蚂蚁munayi_com").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1",
|
||||
unique: true,
|
||||
sparse: true
|
||||
});
|
||||
db.getCollection("木蚂蚁munayi_com").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1",
|
||||
sparse: true
|
||||
});
|
||||
db.getCollection("木蚂蚁munayi_com").createIndex({
|
||||
uid: NumberInt("1")
|
||||
}, {
|
||||
name: "uid_1",
|
||||
sparse: true
|
||||
});
|
||||
db.getCollection("木蚂蚁munayi_com").createIndex({
|
||||
data_hash: NumberInt("1")
|
||||
}, {
|
||||
name: "data_hash_1",
|
||||
unique: true,
|
||||
sparse: true
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 狂人影子库user
|
||||
// ----------------------------
|
||||
db.getCollection("狂人影子库user").drop();
|
||||
db.createCollection("狂人影子库user");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 珍爱网
|
||||
// ----------------------------
|
||||
db.getCollection("珍爱网").drop();
|
||||
db.createCollection("珍爱网");
|
||||
db.getCollection("珍爱网").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 用户身份凭证数据集
|
||||
// ----------------------------
|
||||
db.getCollection("用户身份凭证数据集").drop();
|
||||
db.createCollection("用户身份凭证数据集");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 网红库
|
||||
// ----------------------------
|
||||
db.getCollection("网红库").drop();
|
||||
db.createCollection("网红库");
|
||||
db.getCollection("网红库").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
db.getCollection("网红库").createIndex({
|
||||
sec_uid: NumberInt("1")
|
||||
}, {
|
||||
name: "sec_uid_1"
|
||||
});
|
||||
db.getCollection("网红库").createIndex({
|
||||
idcard: NumberInt("1")
|
||||
}, {
|
||||
name: "idcard_1"
|
||||
});
|
||||
db.getCollection("网红库").createIndex({
|
||||
collectId: NumberInt("1")
|
||||
}, {
|
||||
name: "collectId_1"
|
||||
});
|
||||
db.getCollection("网红库").createIndex({
|
||||
cateId: NumberInt("1")
|
||||
}, {
|
||||
name: "cateId_1"
|
||||
});
|
||||
db.getCollection("网红库").createIndex({
|
||||
authorId: NumberInt("1")
|
||||
}, {
|
||||
name: "authorId_1"
|
||||
});
|
||||
db.getCollection("网红库").createIndex({
|
||||
uniqueId: NumberInt("1")
|
||||
}, {
|
||||
name: "uniqueId_1"
|
||||
});
|
||||
db.getCollection("网红库").createIndex({
|
||||
phoneNumber: NumberInt("1")
|
||||
}, {
|
||||
name: "phoneNumber_1"
|
||||
});
|
||||
db.getCollection("网红库").createIndex({
|
||||
nickname: NumberInt("1")
|
||||
}, {
|
||||
name: "nickname_1"
|
||||
});
|
||||
db.getCollection("网红库").createIndex({
|
||||
updatedAt: NumberInt("1")
|
||||
}, {
|
||||
name: "updatedAt_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 雅虎用户
|
||||
// ----------------------------
|
||||
db.getCollection("雅虎用户").drop();
|
||||
db.createCollection("雅虎用户");
|
||||
31
Moncter/数据库/KR_Linkedln.js
Normal file
31
Moncter/数据库/KR_Linkedln.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_Linkedln
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:18:28
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for linkedin
|
||||
// ----------------------------
|
||||
db.getCollection("linkedin").drop();
|
||||
db.createCollection("linkedin");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
31
Moncter/数据库/KR_京东.js
Normal file
31
Moncter/数据库/KR_京东.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_京东
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:19:11
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for jd_com
|
||||
// ----------------------------
|
||||
db.getCollection("jd_com").drop();
|
||||
db.createCollection("jd_com");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
37
Moncter/数据库/KR_人才库.js
Normal file
37
Moncter/数据库/KR_人才库.js
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_人才库
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:19:59
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 51JOB
|
||||
// ----------------------------
|
||||
db.getCollection("51JOB").drop();
|
||||
db.createCollection("51JOB");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 慈溪人才网
|
||||
// ----------------------------
|
||||
db.getCollection("慈溪人才网").drop();
|
||||
db.createCollection("慈溪人才网");
|
||||
142
Moncter/数据库/KR_企业.js
Normal file
142
Moncter/数据库/KR_企业.js
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_企业
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:19:44
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 企业400号段数据
|
||||
// ----------------------------
|
||||
db.getCollection("企业400号段数据").drop();
|
||||
db.createCollection("企业400号段数据");
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
ID: NumberInt("1")
|
||||
}, {
|
||||
name: "ID_1"
|
||||
});
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
"企业名称": NumberInt("1")
|
||||
}, {
|
||||
name: "企业名称_1"
|
||||
});
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
"400号码": NumberInt("1")
|
||||
}, {
|
||||
name: "400号码_1"
|
||||
});
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
"联系人": NumberInt("1")
|
||||
}, {
|
||||
name: "联系人_1"
|
||||
});
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
"手机号码": NumberInt("1")
|
||||
}, {
|
||||
name: "手机号码_1"
|
||||
});
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
"表名": NumberInt("1")
|
||||
}, {
|
||||
name: "表名_1"
|
||||
});
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
"导入时间": NumberInt("-1")
|
||||
}, {
|
||||
name: "导入时间_-1"
|
||||
});
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
nid: NumberInt("1")
|
||||
}, {
|
||||
name: "nid_1"
|
||||
});
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
EnterpriseName: NumberInt("1")
|
||||
}, {
|
||||
name: "EnterpriseName_1"
|
||||
});
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
Number400: NumberInt("1")
|
||||
}, {
|
||||
name: "Number400_1"
|
||||
});
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
LinkMan: NumberInt("1")
|
||||
}, {
|
||||
name: "LinkMan_1"
|
||||
});
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
Mobile: NumberInt("1")
|
||||
}, {
|
||||
name: "Mobile_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 统一用户资产表
|
||||
// ----------------------------
|
||||
db.getCollection("统一用户资产表").drop();
|
||||
db.createCollection("统一用户资产表");
|
||||
db.getCollection("统一用户资产表").createIndex({
|
||||
"用户唯一ID": NumberInt("1")
|
||||
}, {
|
||||
name: "用户唯一ID_1"
|
||||
});
|
||||
db.getCollection("统一用户资产表").createIndex({
|
||||
"基础信息.手机号": NumberInt("1")
|
||||
}, {
|
||||
name: "基础信息.手机号_1"
|
||||
});
|
||||
db.getCollection("统一用户资产表").createIndex({
|
||||
"基础信息.邮箱": NumberInt("1")
|
||||
}, {
|
||||
name: "基础信息.邮箱_1"
|
||||
});
|
||||
db.getCollection("统一用户资产表").createIndex({
|
||||
"企业信息.企业ID": NumberInt("1")
|
||||
}, {
|
||||
name: "企业信息.企业ID_1"
|
||||
});
|
||||
db.getCollection("统一用户资产表").createIndex({
|
||||
"企业信息.400号码": NumberInt("1")
|
||||
}, {
|
||||
name: "企业信息.400号码_1"
|
||||
});
|
||||
db.getCollection("统一用户资产表").createIndex({
|
||||
"资产概览.总资产价值": NumberInt("-1")
|
||||
}, {
|
||||
name: "资产概览.总资产价值_-1"
|
||||
});
|
||||
db.getCollection("统一用户资产表").createIndex({
|
||||
"资产概览.资产等级": NumberInt("1")
|
||||
}, {
|
||||
name: "资产概览.资产等级_1"
|
||||
});
|
||||
db.getCollection("统一用户资产表").createIndex({
|
||||
"用户类型": NumberInt("1")
|
||||
}, {
|
||||
name: "用户类型_1"
|
||||
});
|
||||
db.getCollection("统一用户资产表").createIndex({
|
||||
"创建时间": NumberInt("-1")
|
||||
}, {
|
||||
name: "创建时间_-1"
|
||||
});
|
||||
115
Moncter/数据库/KR_企业名录.js
Normal file
115
Moncter/数据库/KR_企业名录.js
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_企业名录
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:19:51
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 中国医院名录_copy1
|
||||
// ----------------------------
|
||||
db.getCollection("中国医院名录_copy1").drop();
|
||||
db.createCollection("中国医院名录_copy1");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 企业及个体工商目录
|
||||
// ----------------------------
|
||||
db.getCollection("企业及个体工商目录").drop();
|
||||
db.createCollection("企业及个体工商目录");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 企业名录
|
||||
// ----------------------------
|
||||
db.getCollection("企业名录").drop();
|
||||
db.createCollection("企业名录");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 四川企业老板
|
||||
// ----------------------------
|
||||
db.getCollection("四川企业老板").drop();
|
||||
db.createCollection("四川企业老板");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东工商企业库1
|
||||
// ----------------------------
|
||||
db.getCollection("广东工商企业库1").drop();
|
||||
db.createCollection("广东工商企业库1");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东工商企业库2
|
||||
// ----------------------------
|
||||
db.getCollection("广东工商企业库2").drop();
|
||||
db.createCollection("广东工商企业库2");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东工商企业库3
|
||||
// ----------------------------
|
||||
db.getCollection("广东工商企业库3").drop();
|
||||
db.createCollection("广东工商企业库3");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东工商企业库4
|
||||
// ----------------------------
|
||||
db.getCollection("广东工商企业库4").drop();
|
||||
db.createCollection("广东工商企业库4");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东工商企业库5
|
||||
// ----------------------------
|
||||
db.getCollection("广东工商企业库5").drop();
|
||||
db.createCollection("广东工商企业库5");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东工商企业库6
|
||||
// ----------------------------
|
||||
db.getCollection("广东工商企业库6").drop();
|
||||
db.createCollection("广东工商企业库6");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东工商企业库7
|
||||
// ----------------------------
|
||||
db.getCollection("广东工商企业库7").drop();
|
||||
db.createCollection("广东工商企业库7");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 行政区划代码
|
||||
// ----------------------------
|
||||
db.getCollection("行政区划代码").drop();
|
||||
db.createCollection("行政区划代码");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 表1
|
||||
// ----------------------------
|
||||
db.getCollection("表1").drop();
|
||||
db.createCollection("表1");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 表2
|
||||
// ----------------------------
|
||||
db.getCollection("表2").drop();
|
||||
db.createCollection("表2");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 销售额3000万元-5000万元企业名录
|
||||
// ----------------------------
|
||||
db.getCollection("销售额3000万元-5000万元企业名录").drop();
|
||||
db.createCollection("销售额3000万元-5000万元企业名录");
|
||||
228
Moncter/数据库/KR_卡若私域.js
Normal file
228
Moncter/数据库/KR_卡若私域.js
Normal file
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_卡若私域
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:19:24
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 存客宝流量池分类
|
||||
// ----------------------------
|
||||
db.getCollection("存客宝流量池分类").drop();
|
||||
db.createCollection("存客宝流量池分类");
|
||||
db.getCollection("存客宝流量池分类").createIndex({
|
||||
province: NumberInt("1"),
|
||||
wechat: NumberInt("1"),
|
||||
phoneNumber: NumberInt("1"),
|
||||
createdAt: NumberInt("1")
|
||||
}, {
|
||||
name: "user_province_wechat_phone_number_created_at"
|
||||
});
|
||||
db.getCollection("存客宝流量池分类").createIndex({
|
||||
province: NumberInt("1"),
|
||||
wechat: NumberInt("1"),
|
||||
phoneNumber: NumberInt("1"),
|
||||
wechatIsOpen: NumberInt("1"),
|
||||
createdAt: NumberInt("1")
|
||||
}, {
|
||||
name: "user_province_wechat_phone_number_wechat_is_open_created_at"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 存客宝流量池分类2
|
||||
// ----------------------------
|
||||
db.getCollection("存客宝流量池分类2").drop();
|
||||
db.createCollection("存客宝流量池分类2");
|
||||
db.getCollection("存客宝流量池分类2").createIndex({
|
||||
source: NumberInt("1"),
|
||||
wechat: NumberInt("1")
|
||||
}, {
|
||||
name: "user_source_wechat",
|
||||
unique: true
|
||||
});
|
||||
db.getCollection("存客宝流量池分类2").createIndex({
|
||||
source: NumberInt("1"),
|
||||
phoneNumber: NumberInt("1")
|
||||
}, {
|
||||
name: "user_source_phone_number",
|
||||
unique: true
|
||||
});
|
||||
db.getCollection("存客宝流量池分类2").createIndex({
|
||||
province: NumberInt("1")
|
||||
}, {
|
||||
name: "user_province"
|
||||
});
|
||||
db.getCollection("存客宝流量池分类2").createIndex({
|
||||
wechat: NumberInt("1")
|
||||
}, {
|
||||
name: "user_wechat"
|
||||
});
|
||||
db.getCollection("存客宝流量池分类2").createIndex({
|
||||
phoneNumber: NumberInt("1")
|
||||
}, {
|
||||
name: "user_phone_number"
|
||||
});
|
||||
db.getCollection("存客宝流量池分类2").createIndex({
|
||||
wechatIsOpen: NumberInt("1")
|
||||
}, {
|
||||
name: "user_wechat_is_open"
|
||||
});
|
||||
db.getCollection("存客宝流量池分类2").createIndex({
|
||||
createdAt: NumberInt("1")
|
||||
}, {
|
||||
name: "user_created_at"
|
||||
});
|
||||
db.getCollection("存客宝流量池分类2").createIndex({
|
||||
jdIsOpen: NumberInt("1")
|
||||
}, {
|
||||
name: "user_jd_is_open"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 存客宝采集网红
|
||||
// ----------------------------
|
||||
db.getCollection("存客宝采集网红").drop();
|
||||
db.createCollection("存客宝采集网红");
|
||||
db.getCollection("存客宝采集网红").createIndex({
|
||||
OwnerWechatId: NumberInt("1"),
|
||||
Phone: NumberInt("1"),
|
||||
Alias: NumberInt("1")
|
||||
}, {
|
||||
name: "IX_Kefure_WechatFriend_OwnerWechatId"
|
||||
});
|
||||
db.getCollection("存客宝采集网红").createIndex({
|
||||
WechatId: NumberInt("1"),
|
||||
IsSend: NumberInt("1"),
|
||||
IsDeleted: NumberInt("1"),
|
||||
Gender: NumberInt("1"),
|
||||
Nickname: NumberInt("1")
|
||||
}, {
|
||||
name: "IX_Kefure_WechatFriend_WechatId_IsSend"
|
||||
});
|
||||
db.getCollection("存客宝采集网红").createIndex({
|
||||
WechatAccountId: NumberInt("1")
|
||||
}, {
|
||||
name: "WechatAccountId"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 存客宝采集网红有微信
|
||||
// ----------------------------
|
||||
db.getCollection("存客宝采集网红有微信").drop();
|
||||
db.createCollection("存客宝采集网红有微信");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 老坑爹商店 shop.lkdie.com
|
||||
// ----------------------------
|
||||
db.getCollection("老坑爹商店 shop.lkdie.com").drop();
|
||||
db.createCollection("老坑爹商店 shop.lkdie.com");
|
||||
db.getCollection("老坑爹商店 shop.lkdie.com").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email",
|
||||
unique: true
|
||||
});
|
||||
db.getCollection("老坑爹商店 shop.lkdie.com").createIndex({
|
||||
nickname: NumberInt("1")
|
||||
}, {
|
||||
name: "nickname",
|
||||
unique: true
|
||||
});
|
||||
db.getCollection("老坑爹商店 shop.lkdie.com").createIndex({
|
||||
updatedTime: NumberInt("1")
|
||||
}, {
|
||||
name: "updatedTime"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 老坑爹论坛www.lkdie.com 会员
|
||||
// ----------------------------
|
||||
db.getCollection("老坑爹论坛www.lkdie.com 会员").drop();
|
||||
db.createCollection("老坑爹论坛www.lkdie.com 会员");
|
||||
db.getCollection("老坑爹论坛www.lkdie.com 会员").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username",
|
||||
unique: true
|
||||
});
|
||||
db.getCollection("老坑爹论坛www.lkdie.com 会员").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 黑科技www.quwanzhi.com 付款邮箱
|
||||
// ----------------------------
|
||||
db.getCollection("黑科技www.quwanzhi.com 付款邮箱").drop();
|
||||
db.createCollection("黑科技www.quwanzhi.com 付款邮箱");
|
||||
db.getCollection("黑科技www.quwanzhi.com 付款邮箱").createIndex({
|
||||
trade_no: NumberInt("1")
|
||||
}, {
|
||||
name: "tradeNo"
|
||||
});
|
||||
db.getCollection("黑科技www.quwanzhi.com 付款邮箱").createIndex({
|
||||
zid: NumberInt("1")
|
||||
}, {
|
||||
name: "zid"
|
||||
});
|
||||
db.getCollection("黑科技www.quwanzhi.com 付款邮箱").createIndex({
|
||||
tid: NumberInt("1")
|
||||
}, {
|
||||
name: "tid"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 黑科技www.quwanzhi.com 发卡邮箱
|
||||
// ----------------------------
|
||||
db.getCollection("黑科技www.quwanzhi.com 发卡邮箱").drop();
|
||||
db.createCollection("黑科技www.quwanzhi.com 发卡邮箱");
|
||||
db.getCollection("黑科技www.quwanzhi.com 发卡邮箱").createIndex({
|
||||
zid: NumberInt("1")
|
||||
}, {
|
||||
name: "zid"
|
||||
});
|
||||
db.getCollection("黑科技www.quwanzhi.com 发卡邮箱").createIndex({
|
||||
input: NumberInt("1")
|
||||
}, {
|
||||
name: "input"
|
||||
});
|
||||
db.getCollection("黑科技www.quwanzhi.com 发卡邮箱").createIndex({
|
||||
userid: NumberInt("1")
|
||||
}, {
|
||||
name: "userid"
|
||||
});
|
||||
db.getCollection("黑科技www.quwanzhi.com 发卡邮箱").createIndex({
|
||||
tradeno: NumberInt("1")
|
||||
}, {
|
||||
name: "tradeno"
|
||||
});
|
||||
db.getCollection("黑科技www.quwanzhi.com 发卡邮箱").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id"
|
||||
});
|
||||
db.getCollection("黑科技www.quwanzhi.com 发卡邮箱").createIndex({
|
||||
tid: NumberInt("1")
|
||||
}, {
|
||||
name: "tid"
|
||||
});
|
||||
122
Moncter/数据库/KR_商城.js
Normal file
122
Moncter/数据库/KR_商城.js
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_商城
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:20:13
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 21年贝蒂喜订单整合
|
||||
// ----------------------------
|
||||
db.getCollection("21年贝蒂喜订单整合").drop();
|
||||
db.createCollection("21年贝蒂喜订单整合");
|
||||
db.getCollection("21年贝蒂喜订单整合").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
db.getCollection("21年贝蒂喜订单整合").createIndex({
|
||||
"特权订金订单id": NumberInt("1")
|
||||
}, {
|
||||
name: "特权订金订单id_1"
|
||||
});
|
||||
db.getCollection("21年贝蒂喜订单整合").createIndex({
|
||||
"新零售导购门店id": NumberInt("1")
|
||||
}, {
|
||||
name: "新零售导购门店id_1"
|
||||
});
|
||||
db.getCollection("21年贝蒂喜订单整合").createIndex({
|
||||
"新零售发货门店id": NumberInt("1")
|
||||
}, {
|
||||
name: "新零售发货门店id_1"
|
||||
});
|
||||
db.getCollection("21年贝蒂喜订单整合").createIndex({
|
||||
"新零售成交门店id": NumberInt("1")
|
||||
}, {
|
||||
name: "新零售成交门店id_1"
|
||||
});
|
||||
db.getCollection("21年贝蒂喜订单整合").createIndex({
|
||||
"新零售成交经销商id": NumberInt("1")
|
||||
}, {
|
||||
name: "新零售成交经销商id_1"
|
||||
});
|
||||
db.getCollection("21年贝蒂喜订单整合").createIndex({
|
||||
"店铺Id": NumberInt("1")
|
||||
}, {
|
||||
name: "店铺Id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 凡客诚品_vancl.com
|
||||
// ----------------------------
|
||||
db.getCollection("凡客诚品_vancl.com").drop();
|
||||
db.createCollection("凡客诚品_vancl.com");
|
||||
db.getCollection("凡客诚品_vancl.com").createIndex({
|
||||
name: NumberInt("1")
|
||||
}, {
|
||||
name: "name_1"
|
||||
});
|
||||
db.getCollection("凡客诚品_vancl.com").createIndex({
|
||||
"数据来源": NumberInt("1"),
|
||||
"导入时间": NumberInt("-1")
|
||||
}, {
|
||||
name: "数据来源_1_导入时间_-1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 嘟嘟牛
|
||||
// ----------------------------
|
||||
db.getCollection("嘟嘟牛").drop();
|
||||
db.createCollection("嘟嘟牛");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 嘟嘟牛 本地同城
|
||||
// ----------------------------
|
||||
db.getCollection("嘟嘟牛 本地同城").drop();
|
||||
db.createCollection("嘟嘟牛 本地同城");
|
||||
db.getCollection("嘟嘟牛 本地同城").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1"
|
||||
});
|
||||
db.getCollection("嘟嘟牛 本地同城").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 小米 xiaomi_com
|
||||
// ----------------------------
|
||||
db.getCollection("小米 xiaomi_com").drop();
|
||||
db.createCollection("小米 xiaomi_com");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 购物-北京一电购公司2月整理版30万
|
||||
// ----------------------------
|
||||
db.getCollection("购物-北京一电购公司2月整理版30万").drop();
|
||||
db.createCollection("购物-北京一电购公司2月整理版30万");
|
||||
db.getCollection("购物-北京一电购公司2月整理版30万").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
359
Moncter/数据库/KR_国外.js
Normal file
359
Moncter/数据库/KR_国外.js
Normal file
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_国外
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:18:56
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for Yandex俄罗斯百度用户数据
|
||||
// ----------------------------
|
||||
db.getCollection("Yandex俄罗斯百度用户数据").drop();
|
||||
db.createCollection("Yandex俄罗斯百度用户数据");
|
||||
db.getCollection("Yandex俄罗斯百度用户数据").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯百度用户数据").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯百度用户数据").createIndex({
|
||||
domain: NumberInt("1")
|
||||
}, {
|
||||
name: "domain_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯百度用户数据").createIndex({
|
||||
password_strength: NumberInt("1")
|
||||
}, {
|
||||
name: "password_strength_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯百度用户数据").createIndex({
|
||||
country: NumberInt("1")
|
||||
}, {
|
||||
name: "country_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯百度用户数据").createIndex({
|
||||
service_provider: NumberInt("1")
|
||||
}, {
|
||||
name: "service_provider_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯百度用户数据").createIndex({
|
||||
created_at: NumberInt("-1")
|
||||
}, {
|
||||
name: "created_at_-1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯百度用户数据").createIndex({
|
||||
data_source: NumberInt("1")
|
||||
}, {
|
||||
name: "data_source_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯百度用户数据").createIndex({
|
||||
email: NumberInt("1"),
|
||||
password: NumberInt("1")
|
||||
}, {
|
||||
name: "email_password_unique",
|
||||
unique: true
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯百度用户数据").createIndex({
|
||||
"$**": "text"
|
||||
}, {
|
||||
name: "text_search",
|
||||
weights: {
|
||||
email: NumberInt("1"),
|
||||
username: NumberInt("1")
|
||||
},
|
||||
default_language: "english",
|
||||
language_override: "language",
|
||||
textIndexVersion: NumberInt("3")
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for Yandex俄罗斯谷歌用户数据
|
||||
// ----------------------------
|
||||
db.getCollection("Yandex俄罗斯谷歌用户数据").drop();
|
||||
db.createCollection("Yandex俄罗斯谷歌用户数据");
|
||||
db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({
|
||||
domain: NumberInt("1")
|
||||
}, {
|
||||
name: "domain_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({
|
||||
password_strength: NumberInt("1")
|
||||
}, {
|
||||
name: "password_strength_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({
|
||||
country: NumberInt("1")
|
||||
}, {
|
||||
name: "country_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({
|
||||
service_provider: NumberInt("1")
|
||||
}, {
|
||||
name: "service_provider_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({
|
||||
created_at: NumberInt("-1")
|
||||
}, {
|
||||
name: "created_at_-1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({
|
||||
data_source: NumberInt("1")
|
||||
}, {
|
||||
name: "data_source_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({
|
||||
email: NumberInt("1"),
|
||||
password: NumberInt("1")
|
||||
}, {
|
||||
name: "email_password_unique",
|
||||
unique: true
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({
|
||||
"$**": "text"
|
||||
}, {
|
||||
name: "text_search",
|
||||
weights: {
|
||||
email: NumberInt("1"),
|
||||
username: NumberInt("1")
|
||||
},
|
||||
default_language: "english",
|
||||
language_override: "language",
|
||||
textIndexVersion: NumberInt("3")
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for www.angeldiet.co.kr 1
|
||||
// ----------------------------
|
||||
db.getCollection("www.angeldiet.co.kr 1").drop();
|
||||
db.createCollection("www.angeldiet.co.kr 1");
|
||||
db.getCollection("www.angeldiet.co.kr 1").createIndex({
|
||||
ID: NumberInt("1")
|
||||
}, {
|
||||
name: "ID_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for www.angeldiet.co.kr 2
|
||||
// ----------------------------
|
||||
db.getCollection("www.angeldiet.co.kr 2").drop();
|
||||
db.createCollection("www.angeldiet.co.kr 2");
|
||||
db.getCollection("www.angeldiet.co.kr 2").createIndex({
|
||||
ID: NumberInt("1")
|
||||
}, {
|
||||
name: "ID_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for www.bookoa.com(韩国学术出版)
|
||||
// ----------------------------
|
||||
db.getCollection("www.bookoa.com(韩国学术出版)").drop();
|
||||
db.createCollection("www.bookoa.com(韩国学术出版)");
|
||||
db.getCollection("www.bookoa.com(韩国学术出版)").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
db.getCollection("www.bookoa.com(韩国学术出版)").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1"
|
||||
});
|
||||
db.getCollection("www.bookoa.com(韩国学术出版)").createIndex({
|
||||
hphone: NumberInt("1")
|
||||
}, {
|
||||
name: "hphone_1"
|
||||
});
|
||||
db.getCollection("www.bookoa.com(韩国学术出版)").createIndex({
|
||||
name: NumberInt("1")
|
||||
}, {
|
||||
name: "name_1"
|
||||
});
|
||||
db.getCollection("www.bookoa.com(韩国学术出版)").createIndex({
|
||||
email: NumberInt("1"),
|
||||
name: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1_name_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_APP数据
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_APP数据").drop();
|
||||
db.createCollection("卡塔卡银行_APP数据");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_交易明细
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_交易明细").drop();
|
||||
db.createCollection("卡塔卡银行_交易明细");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_交易明细临时
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_交易明细临时").drop();
|
||||
db.createCollection("卡塔卡银行_交易明细临时");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_卡主表
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_卡主表").drop();
|
||||
db.createCollection("卡塔卡银行_卡主表");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_卡列表
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_卡列表").drop();
|
||||
db.createCollection("卡塔卡银行_卡列表");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_审计主表
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_审计主表").drop();
|
||||
db.createCollection("卡塔卡银行_审计主表");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_客户
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_客户").drop();
|
||||
db.createCollection("卡塔卡银行_客户");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_客户主表
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_客户主表").drop();
|
||||
db.createCollection("卡塔卡银行_客户主表");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_收款人主表
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_收款人主表").drop();
|
||||
db.createCollection("卡塔卡银行_收款人主表");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_用户档案
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_用户档案").drop();
|
||||
db.createCollection("卡塔卡银行_用户档案");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_申请记录
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_申请记录").drop();
|
||||
db.createCollection("卡塔卡银行_申请记录");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_电子对账单邮箱
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_电子对账单邮箱").drop();
|
||||
db.createCollection("卡塔卡银行_电子对账单邮箱");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_订单客户明细
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_订单客户明细").drop();
|
||||
db.createCollection("卡塔卡银行_订单客户明细");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_账户主表
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_账户主表").drop();
|
||||
db.createCollection("卡塔卡银行_账户主表");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_身份证变更审计
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_身份证变更审计").drop();
|
||||
db.createCollection("卡塔卡银行_身份证变更审计");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_黑名单用户
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_黑名单用户").drop();
|
||||
db.createCollection("卡塔卡银行_黑名单用户");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 台湾财经电视台
|
||||
// ----------------------------
|
||||
db.getCollection("台湾财经电视台").drop();
|
||||
db.createCollection("台湾财经电视台");
|
||||
db.getCollection("台湾财经电视台").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1"
|
||||
});
|
||||
db.getCollection("台湾财经电视台").createIndex({
|
||||
name: NumberInt("1")
|
||||
}, {
|
||||
name: "name_1"
|
||||
});
|
||||
db.getCollection("台湾财经电视台").createIndex({
|
||||
phone: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1"
|
||||
});
|
||||
db.getCollection("台湾财经电视台").createIndex({
|
||||
source: NumberInt("1")
|
||||
}, {
|
||||
name: "source_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 土耳其公民
|
||||
// ----------------------------
|
||||
db.getCollection("土耳其公民").drop();
|
||||
db.createCollection("土耳其公民");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 社工库_1.7G_www.comicstorm.co.kr 85w_1
|
||||
// ----------------------------
|
||||
db.getCollection("社工库_1.7G_www.comicstorm.co.kr 85w_1").drop();
|
||||
db.createCollection("社工库_1.7G_www.comicstorm.co.kr 85w_1");
|
||||
db.getCollection("社工库_1.7G_www.comicstorm.co.kr 85w_1").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 社工库_1.7G_www.hacker.co.kr 140W_140W
|
||||
// ----------------------------
|
||||
db.getCollection("社工库_1.7G_www.hacker.co.kr 140W_140W").drop();
|
||||
db.createCollection("社工库_1.7G_www.hacker.co.kr 140W_140W");
|
||||
db.getCollection("社工库_1.7G_www.hacker.co.kr 140W_140W").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
618
Moncter/数据库/KR_存客宝.js
Normal file
618
Moncter/数据库/KR_存客宝.js
Normal file
@@ -0,0 +1,618 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_存客宝
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:18:41
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_AccountWechat
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_AccountWechat").drop();
|
||||
db.createCollection("cunkebao_AccountWechat");
|
||||
db.getCollection("cunkebao_AccountWechat").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_DownloadVideoSchedule
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_DownloadVideoSchedule").drop();
|
||||
db.createCollection("cunkebao_DownloadVideoSchedule");
|
||||
db.getCollection("cunkebao_DownloadVideoSchedule").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_FriendMessageTask
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_FriendMessageTask").drop();
|
||||
db.createCollection("cunkebao_FriendMessageTask");
|
||||
db.getCollection("cunkebao_FriendMessageTask").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_FriendMessageTpl
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_FriendMessageTpl").drop();
|
||||
db.createCollection("cunkebao_FriendMessageTpl");
|
||||
db.getCollection("cunkebao_FriendMessageTpl").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_FriendRequestConfig
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_FriendRequestConfig").drop();
|
||||
db.createCollection("cunkebao_FriendRequestConfig");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_FriendRequestTaskScan
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_FriendRequestTaskScan").drop();
|
||||
db.createCollection("cunkebao_FriendRequestTaskScan");
|
||||
db.getCollection("cunkebao_FriendRequestTaskScan").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_FrontSession
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_FrontSession").drop();
|
||||
db.createCollection("cunkebao_FrontSession");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_JdPromotionSite
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_JdPromotionSite").drop();
|
||||
db.createCollection("cunkebao_JdPromotionSite");
|
||||
db.getCollection("cunkebao_JdPromotionSite").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_JdSocialMedia
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_JdSocialMedia").drop();
|
||||
db.createCollection("cunkebao_JdSocialMedia");
|
||||
db.getCollection("cunkebao_JdSocialMedia").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_MaterialLib
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_MaterialLib").drop();
|
||||
db.createCollection("cunkebao_MaterialLib");
|
||||
db.getCollection("cunkebao_MaterialLib").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_MaterialPushLog
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_MaterialPushLog").drop();
|
||||
db.createCollection("cunkebao_MaterialPushLog");
|
||||
db.getCollection("cunkebao_MaterialPushLog").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_MiniProgrameLib
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_MiniProgrameLib").drop();
|
||||
db.createCollection("cunkebao_MiniProgrameLib");
|
||||
db.getCollection("cunkebao_MiniProgrameLib").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_PartTimer
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_PartTimer").drop();
|
||||
db.createCollection("cunkebao_PartTimer");
|
||||
db.getCollection("cunkebao_PartTimer").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_Poster
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_Poster").drop();
|
||||
db.createCollection("cunkebao_Poster");
|
||||
db.getCollection("cunkebao_Poster").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysAttach
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysAttach").drop();
|
||||
db.createCollection("cunkebao_SysAttach");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysAttach_20250228
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysAttach_20250228").drop();
|
||||
db.createCollection("cunkebao_SysAttach_20250228");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysAttach_copy
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysAttach_copy").drop();
|
||||
db.createCollection("cunkebao_SysAttach_copy");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysDataSource
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysDataSource").drop();
|
||||
db.createCollection("cunkebao_SysDataSource");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysModule
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysModule").drop();
|
||||
db.createCollection("cunkebao_SysModule");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysNavItem
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysNavItem").drop();
|
||||
db.createCollection("cunkebao_SysNavItem");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysObject
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysObject").drop();
|
||||
db.createCollection("cunkebao_SysObject");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysOperatorPermission
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysOperatorPermission").drop();
|
||||
db.createCollection("cunkebao_SysOperatorPermission");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysRelatedList
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysRelatedList").drop();
|
||||
db.createCollection("cunkebao_SysRelatedList");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysShare
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysShare").drop();
|
||||
db.createCollection("cunkebao_SysShare");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysSolution
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysSolution").drop();
|
||||
db.createCollection("cunkebao_SysSolution");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysUI
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysUI").drop();
|
||||
db.createCollection("cunkebao_SysUI");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_UserIncome
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_UserIncome").drop();
|
||||
db.createCollection("cunkebao_UserIncome");
|
||||
db.getCollection("cunkebao_UserIncome").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_administrator_permissions
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_administrator_permissions").drop();
|
||||
db.createCollection("cunkebao_v3_ck_administrator_permissions");
|
||||
db.getCollection("cunkebao_v3_ck_administrator_permissions").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_administrators
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_administrators").drop();
|
||||
db.createCollection("cunkebao_v3_ck_administrators");
|
||||
db.getCollection("cunkebao_v3_ck_administrators").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_call_recording
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_call_recording").drop();
|
||||
db.createCollection("cunkebao_v3_ck_call_recording");
|
||||
db.getCollection("cunkebao_v3_ck_call_recording").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_company
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_company").drop();
|
||||
db.createCollection("cunkebao_v3_ck_company");
|
||||
db.getCollection("cunkebao_v3_ck_company").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_customer_acquisition_task
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_customer_acquisition_task").drop();
|
||||
db.createCollection("cunkebao_v3_ck_customer_acquisition_task");
|
||||
db.getCollection("cunkebao_v3_ck_customer_acquisition_task").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_flow_package_order
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_flow_package_order").drop();
|
||||
db.createCollection("cunkebao_v3_ck_flow_package_order");
|
||||
db.getCollection("cunkebao_v3_ck_flow_package_order").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_flow_usage_record
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_flow_usage_record").drop();
|
||||
db.createCollection("cunkebao_v3_ck_flow_usage_record");
|
||||
db.getCollection("cunkebao_v3_ck_flow_usage_record").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_jd_promotion_site
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_jd_promotion_site").drop();
|
||||
db.createCollection("cunkebao_v3_ck_jd_promotion_site");
|
||||
db.getCollection("cunkebao_v3_ck_jd_promotion_site").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_jd_social_media
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_jd_social_media").drop();
|
||||
db.createCollection("cunkebao_v3_ck_jd_social_media");
|
||||
db.getCollection("cunkebao_v3_ck_jd_social_media").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_menus
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_menus").drop();
|
||||
db.createCollection("cunkebao_v3_ck_menus");
|
||||
db.getCollection("cunkebao_v3_ck_menus").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_plan_scene
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_plan_scene").drop();
|
||||
db.createCollection("cunkebao_v3_ck_plan_scene");
|
||||
db.getCollection("cunkebao_v3_ck_plan_scene").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_plan_tags
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_plan_tags").drop();
|
||||
db.createCollection("cunkebao_v3_ck_plan_tags");
|
||||
db.getCollection("cunkebao_v3_ck_plan_tags").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_task_customer
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_task_customer").drop();
|
||||
db.createCollection("cunkebao_v3_ck_task_customer");
|
||||
db.getCollection("cunkebao_v3_ck_task_customer").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_traffic_pool
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_traffic_pool").drop();
|
||||
db.createCollection("cunkebao_v3_ck_traffic_pool");
|
||||
db.getCollection("cunkebao_v3_ck_traffic_pool").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_traffic_source
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_traffic_source").drop();
|
||||
db.createCollection("cunkebao_v3_ck_traffic_source");
|
||||
db.getCollection("cunkebao_v3_ck_traffic_source").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_traffic_source_package
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_traffic_source_package").drop();
|
||||
db.createCollection("cunkebao_v3_ck_traffic_source_package");
|
||||
db.getCollection("cunkebao_v3_ck_traffic_source_package").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_traffic_source_package_item
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_traffic_source_package_item").drop();
|
||||
db.createCollection("cunkebao_v3_ck_traffic_source_package_item");
|
||||
db.getCollection("cunkebao_v3_ck_traffic_source_package_item").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_traffic_tag
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_traffic_tag").drop();
|
||||
db.createCollection("cunkebao_v3_ck_traffic_tag");
|
||||
db.getCollection("cunkebao_v3_ck_traffic_tag").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_user_flow_package
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_user_flow_package").drop();
|
||||
db.createCollection("cunkebao_v3_ck_user_flow_package");
|
||||
db.getCollection("cunkebao_v3_ck_user_flow_package").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_user_portrait
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_user_portrait").drop();
|
||||
db.createCollection("cunkebao_v3_ck_user_portrait");
|
||||
db.getCollection("cunkebao_v3_ck_user_portrait").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_vendor_project
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_vendor_project").drop();
|
||||
db.createCollection("cunkebao_v3_ck_vendor_project");
|
||||
db.getCollection("cunkebao_v3_ck_vendor_project").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_wechat_friendship
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_wechat_friendship").drop();
|
||||
db.createCollection("cunkebao_v3_ck_wechat_friendship");
|
||||
db.getCollection("cunkebao_v3_ck_wechat_friendship").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_wechat_group_member
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_wechat_group_member").drop();
|
||||
db.createCollection("cunkebao_v3_ck_wechat_group_member");
|
||||
db.getCollection("cunkebao_v3_ck_wechat_group_member").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_wechat_tag
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_wechat_tag").drop();
|
||||
db.createCollection("cunkebao_v3_ck_wechat_tag");
|
||||
db.getCollection("cunkebao_v3_ck_wechat_tag").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_workbench
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_workbench").drop();
|
||||
db.createCollection("cunkebao_v3_ck_workbench");
|
||||
db.getCollection("cunkebao_v3_ck_workbench").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_workbench_group_create_item
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_workbench_group_create_item").drop();
|
||||
db.createCollection("cunkebao_v3_ck_workbench_group_create_item");
|
||||
db.getCollection("cunkebao_v3_ck_workbench_group_create_item").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_workbench_group_push
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_workbench_group_push").drop();
|
||||
db.createCollection("cunkebao_v3_ck_workbench_group_push");
|
||||
db.getCollection("cunkebao_v3_ck_workbench_group_push").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_workbench_group_push_item
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_workbench_group_push_item").drop();
|
||||
db.createCollection("cunkebao_v3_ck_workbench_group_push_item");
|
||||
db.getCollection("cunkebao_v3_ck_workbench_group_push_item").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_s2_allot_rule
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_s2_allot_rule").drop();
|
||||
db.createCollection("cunkebao_v3_s2_allot_rule");
|
||||
db.getCollection("cunkebao_v3_s2_allot_rule").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_s2_device_group
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_s2_device_group").drop();
|
||||
db.createCollection("cunkebao_v3_s2_device_group");
|
||||
db.getCollection("cunkebao_v3_s2_device_group").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 好友关系网络汇总表
|
||||
// ----------------------------
|
||||
db.getCollection("好友关系网络汇总表").drop();
|
||||
db.createCollection("好友关系网络汇总表");
|
||||
db.getCollection("好友关系网络汇总表").createIndex({
|
||||
"用户微信ID": NumberInt("1")
|
||||
}, {
|
||||
name: "用户微信ID_1"
|
||||
});
|
||||
db.getCollection("好友关系网络汇总表").createIndex({
|
||||
"好友微信ID": NumberInt("1")
|
||||
}, {
|
||||
name: "好友微信ID_1"
|
||||
});
|
||||
db.getCollection("好友关系网络汇总表").createIndex({
|
||||
"关系等级": NumberInt("1")
|
||||
}, {
|
||||
name: "关系等级_1"
|
||||
});
|
||||
db.getCollection("好友关系网络汇总表").createIndex({
|
||||
"RFM评分.总分": NumberInt("1")
|
||||
}, {
|
||||
name: "RFM评分.总分_1"
|
||||
});
|
||||
db.getCollection("好友关系网络汇总表").createIndex({
|
||||
"关系强度评分": NumberInt("1")
|
||||
}, {
|
||||
name: "关系强度评分_1"
|
||||
});
|
||||
db.getCollection("好友关系网络汇总表").createIndex({
|
||||
"公司ID": NumberInt("1")
|
||||
}, {
|
||||
name: "公司ID_1"
|
||||
});
|
||||
65
Moncter/数据库/KR_存客宝_四表重构KR_KR版.js
Normal file
65
Moncter/数据库/KR_存客宝_四表重构KR_KR版.js
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_存客宝_四表重构KR_KR版
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:18:48
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 估值模式表
|
||||
// ----------------------------
|
||||
db.getCollection("估值模式表").drop();
|
||||
db.createCollection("估值模式表");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 微信号管理表
|
||||
// ----------------------------
|
||||
db.getCollection("微信号管理表").drop();
|
||||
db.createCollection("微信号管理表");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 微信好友表
|
||||
// ----------------------------
|
||||
db.getCollection("微信好友表").drop();
|
||||
db.createCollection("微信好友表");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 设备管理表
|
||||
// ----------------------------
|
||||
db.getCollection("设备管理表").drop();
|
||||
db.createCollection("设备管理表");
|
||||
db.getCollection("设备管理表").createIndex({
|
||||
"设备ID": NumberInt("1")
|
||||
}, {
|
||||
name: "设备ID_1",
|
||||
unique: true
|
||||
});
|
||||
db.getCollection("设备管理表").createIndex({
|
||||
"用户ID": NumberInt("1")
|
||||
}, {
|
||||
name: "用户ID_1"
|
||||
});
|
||||
db.getCollection("设备管理表").createIndex({
|
||||
"设备类型": NumberInt("1")
|
||||
}, {
|
||||
name: "设备类型_1"
|
||||
});
|
||||
36
Moncter/数据库/KR_微博.js
Normal file
36
Moncter/数据库/KR_微博.js
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_微博
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:20:57
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 微博uid+手机
|
||||
// ----------------------------
|
||||
db.getCollection("微博uid+手机").drop();
|
||||
db.createCollection("微博uid+手机");
|
||||
db.getCollection("微博uid+手机").createIndex({
|
||||
phone: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1"
|
||||
});
|
||||
90
Moncter/数据库/KR_快递.js
Normal file
90
Moncter/数据库/KR_快递.js
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_快递
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:19:31
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for wlzhwu_jiezhang
|
||||
// ----------------------------
|
||||
db.getCollection("wlzhwu_jiezhang").drop();
|
||||
db.createCollection("wlzhwu_jiezhang");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for wlzhwu_wlzhangwu
|
||||
// ----------------------------
|
||||
db.getCollection("wlzhwu_wlzhangwu").drop();
|
||||
db.createCollection("wlzhwu_wlzhangwu");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 可行路由
|
||||
// ----------------------------
|
||||
db.getCollection("可行路由").drop();
|
||||
db.createCollection("可行路由");
|
||||
db.getCollection("可行路由").createIndex({
|
||||
_import_time: NumberInt("1")
|
||||
}, {
|
||||
name: "_import_time_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 圆通_普通客户
|
||||
// ----------------------------
|
||||
db.getCollection("圆通_普通客户").drop();
|
||||
db.createCollection("圆通_普通客户");
|
||||
db.getCollection("圆通_普通客户").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1"
|
||||
});
|
||||
db.getCollection("圆通_普通客户").createIndex({
|
||||
phone: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1"
|
||||
});
|
||||
db.getCollection("圆通_普通客户").createIndex({
|
||||
user_score: NumberInt("-1")
|
||||
}, {
|
||||
name: "user_score_-1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 圆通_潜在客户
|
||||
// ----------------------------
|
||||
db.getCollection("圆通_潜在客户").drop();
|
||||
db.createCollection("圆通_潜在客户");
|
||||
db.getCollection("圆通_潜在客户").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1"
|
||||
});
|
||||
db.getCollection("圆通_潜在客户").createIndex({
|
||||
phone: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1"
|
||||
});
|
||||
db.getCollection("圆通_潜在客户").createIndex({
|
||||
user_score: NumberInt("-1")
|
||||
}, {
|
||||
name: "user_score_-1"
|
||||
});
|
||||
46
Moncter/数据库/KR_户口.js
Normal file
46
Moncter/数据库/KR_户口.js
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_户口
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:19:03
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 户籍数据
|
||||
// ----------------------------
|
||||
db.getCollection("户籍数据").drop();
|
||||
db.createCollection("户籍数据");
|
||||
db.getCollection("户籍数据").createIndex({
|
||||
"姓名": NumberInt("1")
|
||||
}, {
|
||||
name: "姓名_1"
|
||||
});
|
||||
db.getCollection("户籍数据").createIndex({
|
||||
"身份证号": NumberInt("1")
|
||||
}, {
|
||||
name: "身份证号_1"
|
||||
});
|
||||
db.getCollection("户籍数据").createIndex({
|
||||
"户籍地址": NumberInt("1")
|
||||
}, {
|
||||
name: "户籍地址_1"
|
||||
});
|
||||
186
Moncter/数据库/KR_手机.js
Normal file
186
Moncter/数据库/KR_手机.js
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_手机
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:20:20
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for tmpgSyh4.renameCollection
|
||||
// ----------------------------
|
||||
db.getCollection("tmpgSyh4.renameCollection").drop();
|
||||
db.createCollection("tmpgSyh4.renameCollection");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东gd5800万神州行动感地带=广州GZBran2_copy2
|
||||
// ----------------------------
|
||||
db.getCollection("广东gd5800万神州行动感地带=广州GZBran2_copy2").drop();
|
||||
db.createCollection("广东gd5800万神州行动感地带=广州GZBran2_copy2");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东广州航空数据1
|
||||
// ----------------------------
|
||||
db.getCollection("广东广州航空数据1").drop();
|
||||
db.createCollection("广东广州航空数据1");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东广州航空数据2
|
||||
// ----------------------------
|
||||
db.getCollection("广东广州航空数据2").drop();
|
||||
db.createCollection("广东广州航空数据2");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东广州航空数据3
|
||||
// ----------------------------
|
||||
db.getCollection("广东广州航空数据3").drop();
|
||||
db.createCollection("广东广州航空数据3");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东广州航空数据4
|
||||
// ----------------------------
|
||||
db.getCollection("广东广州航空数据4").drop();
|
||||
db.createCollection("广东广州航空数据4");
|
||||
db.getCollection("广东广州航空数据4").createIndex({
|
||||
"电话号码": NumberInt("1")
|
||||
}, {
|
||||
name: "电话号码_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东深圳全球通女
|
||||
// ----------------------------
|
||||
db.getCollection("广东深圳全球通女").drop();
|
||||
db.createCollection("广东深圳全球通女");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_701鹰潭
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_701鹰潭").drop();
|
||||
db.createCollection("江西移动_701鹰潭");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_792九江
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_792九江").drop();
|
||||
db.createCollection("江西移动_792九江");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_793上饶
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_793上饶").drop();
|
||||
db.createCollection("江西移动_793上饶");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_798景德镇
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_798景德镇").drop();
|
||||
db.createCollection("江西移动_798景德镇");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_799萍乡
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_799萍乡").drop();
|
||||
db.createCollection("江西移动_799萍乡");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_上绕262万
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_上绕262万").drop();
|
||||
db.createCollection("江西移动_上绕262万");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_九江222万
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_九江222万").drop();
|
||||
db.createCollection("江西移动_九江222万");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_南昌368万
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_南昌368万").drop();
|
||||
db.createCollection("江西移动_南昌368万");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_吉安206万
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_吉安206万").drop();
|
||||
db.createCollection("江西移动_吉安206万");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_宜春230万
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_宜春230万").drop();
|
||||
db.createCollection("江西移动_宜春230万");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_抚州143万
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_抚州143万").drop();
|
||||
db.createCollection("江西移动_抚州143万");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_新余67万
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_新余67万").drop();
|
||||
db.createCollection("江西移动_新余67万");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_景德镇76万
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_景德镇76万").drop();
|
||||
db.createCollection("江西移动_景德镇76万");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_江西全球通带身份证
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_江西全球通带身份证").drop();
|
||||
db.createCollection("江西移动_江西全球通带身份证");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_江西全球通身份证
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_江西全球通身份证").drop();
|
||||
db.createCollection("江西移动_江西全球通身份证");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_江西全球通身份证姓名1226056
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_江西全球通身份证姓名1226056").drop();
|
||||
db.createCollection("江西移动_江西全球通身份证姓名1226056");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_萍乡86万
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_萍乡86万").drop();
|
||||
db.createCollection("江西移动_萍乡86万");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_赣州352万
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_赣州352万").drop();
|
||||
db.createCollection("江西移动_赣州352万");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_鹰潭53万
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_鹰潭53万").drop();
|
||||
db.createCollection("江西移动_鹰潭53万");
|
||||
223
Moncter/数据库/KR_投资.js
Normal file
223
Moncter/数据库/KR_投资.js
Normal file
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_投资
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:20:51
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 上海
|
||||
// ----------------------------
|
||||
db.getCollection("上海").drop();
|
||||
db.createCollection("上海");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 云南
|
||||
// ----------------------------
|
||||
db.getCollection("云南").drop();
|
||||
db.createCollection("云南");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 内蒙古
|
||||
// ----------------------------
|
||||
db.getCollection("内蒙古").drop();
|
||||
db.createCollection("内蒙古");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 北京
|
||||
// ----------------------------
|
||||
db.getCollection("北京").drop();
|
||||
db.createCollection("北京");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 吉林
|
||||
// ----------------------------
|
||||
db.getCollection("吉林").drop();
|
||||
db.createCollection("吉林");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 商务部世界买家数据库 此数据由www_10195_com提供
|
||||
// ----------------------------
|
||||
db.getCollection("商务部世界买家数据库 此数据由www_10195_com提供").drop();
|
||||
db.createCollection("商务部世界买家数据库 此数据由www_10195_com提供");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 四川
|
||||
// ----------------------------
|
||||
db.getCollection("四川").drop();
|
||||
db.createCollection("四川");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 天津
|
||||
// ----------------------------
|
||||
db.getCollection("天津").drop();
|
||||
db.createCollection("天津");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 宁夏
|
||||
// ----------------------------
|
||||
db.getCollection("宁夏").drop();
|
||||
db.createCollection("宁夏");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 安徽
|
||||
// ----------------------------
|
||||
db.getCollection("安徽").drop();
|
||||
db.createCollection("安徽");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 山东
|
||||
// ----------------------------
|
||||
db.getCollection("山东").drop();
|
||||
db.createCollection("山东");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 山西
|
||||
// ----------------------------
|
||||
db.getCollection("山西").drop();
|
||||
db.createCollection("山西");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东
|
||||
// ----------------------------
|
||||
db.getCollection("广东").drop();
|
||||
db.createCollection("广东");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广西
|
||||
// ----------------------------
|
||||
db.getCollection("广西").drop();
|
||||
db.createCollection("广西");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 新疆
|
||||
// ----------------------------
|
||||
db.getCollection("新疆").drop();
|
||||
db.createCollection("新疆");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江苏
|
||||
// ----------------------------
|
||||
db.getCollection("江苏").drop();
|
||||
db.createCollection("江苏");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西
|
||||
// ----------------------------
|
||||
db.getCollection("江西").drop();
|
||||
db.createCollection("江西");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 河北
|
||||
// ----------------------------
|
||||
db.getCollection("河北").drop();
|
||||
db.createCollection("河北");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 河南
|
||||
// ----------------------------
|
||||
db.getCollection("河南").drop();
|
||||
db.createCollection("河南");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 浙江
|
||||
// ----------------------------
|
||||
db.getCollection("浙江").drop();
|
||||
db.createCollection("浙江");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 海南
|
||||
// ----------------------------
|
||||
db.getCollection("海南").drop();
|
||||
db.createCollection("海南");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 湖北
|
||||
// ----------------------------
|
||||
db.getCollection("湖北").drop();
|
||||
db.createCollection("湖北");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 湖南
|
||||
// ----------------------------
|
||||
db.getCollection("湖南").drop();
|
||||
db.createCollection("湖南");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 甘肃
|
||||
// ----------------------------
|
||||
db.getCollection("甘肃").drop();
|
||||
db.createCollection("甘肃");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 省份
|
||||
// ----------------------------
|
||||
db.getCollection("省份").drop();
|
||||
db.createCollection("省份");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 福建
|
||||
// ----------------------------
|
||||
db.getCollection("福建").drop();
|
||||
db.createCollection("福建");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 西藏
|
||||
// ----------------------------
|
||||
db.getCollection("西藏").drop();
|
||||
db.createCollection("西藏");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 贵州
|
||||
// ----------------------------
|
||||
db.getCollection("贵州").drop();
|
||||
db.createCollection("贵州");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 辽宁
|
||||
// ----------------------------
|
||||
db.getCollection("辽宁").drop();
|
||||
db.createCollection("辽宁");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 重庆
|
||||
// ----------------------------
|
||||
db.getCollection("重庆").drop();
|
||||
db.createCollection("重庆");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 陕西
|
||||
// ----------------------------
|
||||
db.getCollection("陕西").drop();
|
||||
db.createCollection("陕西");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 青海
|
||||
// ----------------------------
|
||||
db.getCollection("青海").drop();
|
||||
db.createCollection("青海");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 黑龙江
|
||||
// ----------------------------
|
||||
db.getCollection("黑龙江").drop();
|
||||
db.createCollection("黑龙江");
|
||||
223
Moncter/数据库/KR_淘宝.js
Normal file
223
Moncter/数据库/KR_淘宝.js
Normal file
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_淘宝
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:20:35
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for zippo1(去重复后33万)
|
||||
// ----------------------------
|
||||
db.getCollection("zippo1(去重复后33万)").drop();
|
||||
db.createCollection("zippo1(去重复后33万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for zippo2
|
||||
// ----------------------------
|
||||
db.getCollection("zippo2").drop();
|
||||
db.createCollection("zippo2");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for zippo3
|
||||
// ----------------------------
|
||||
db.getCollection("zippo3").drop();
|
||||
db.createCollection("zippo3");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for zippo4
|
||||
// ----------------------------
|
||||
db.getCollection("zippo4").drop();
|
||||
db.createCollection("zippo4");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for zippo5
|
||||
// ----------------------------
|
||||
db.getCollection("zippo5").drop();
|
||||
db.createCollection("zippo5");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 办公设备文具(去重复64.5万)
|
||||
// ----------------------------
|
||||
db.getCollection("办公设备文具(去重复64.5万)").drop();
|
||||
db.createCollection("办公设备文具(去重复64.5万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 包(去重复后71万)
|
||||
// ----------------------------
|
||||
db.getCollection("包(去重复后71万)").drop();
|
||||
db.createCollection("包(去重复后71万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卖家邮箱(去重复后300万)
|
||||
// ----------------------------
|
||||
db.getCollection("卖家邮箱(去重复后300万)").drop();
|
||||
db.createCollection("卖家邮箱(去重复后300万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 女士内衣(去重复后132.6万)
|
||||
// ----------------------------
|
||||
db.getCollection("女士内衣(去重复后132.6万)").drop();
|
||||
db.createCollection("女士内衣(去重复后132.6万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 女士箱包配件(去重复后34.8万)
|
||||
// ----------------------------
|
||||
db.getCollection("女士箱包配件(去重复后34.8万)").drop();
|
||||
db.createCollection("女士箱包配件(去重复后34.8万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 女装1(去重复后388.6万)
|
||||
// ----------------------------
|
||||
db.getCollection("女装1(去重复后388.6万)").drop();
|
||||
db.createCollection("女装1(去重复后388.6万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 女装2(去重复后83万,带旺旺号及支付宝)
|
||||
// ----------------------------
|
||||
db.getCollection("女装2(去重复后83万,带旺旺号及支付宝)").drop();
|
||||
db.createCollection("女装2(去重复后83万,带旺旺号及支付宝)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 女鞋(去重复后118.6万)
|
||||
// ----------------------------
|
||||
db.getCollection("女鞋(去重复后118.6万)").drop();
|
||||
db.createCollection("女鞋(去重复后118.6万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 家具定制代购(去重复后20万)
|
||||
// ----------------------------
|
||||
db.getCollection("家具定制代购(去重复后20万)").drop();
|
||||
db.createCollection("家具定制代购(去重复后20万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 家用电器(去重复后93.5万)
|
||||
// ----------------------------
|
||||
db.getCollection("家用电器(去重复后93.5万)").drop();
|
||||
db.createCollection("家用电器(去重复后93.5万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 床上用品(去重复后19万)
|
||||
// ----------------------------
|
||||
db.getCollection("床上用品(去重复后19万)").drop();
|
||||
db.createCollection("床上用品(去重复后19万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 彩妆护肤(去重复后138万)
|
||||
// ----------------------------
|
||||
db.getCollection("彩妆护肤(去重复后138万)").drop();
|
||||
db.createCollection("彩妆护肤(去重复后138万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 成人用品避孕用品情趣内衣(去重复后8万)
|
||||
// ----------------------------
|
||||
db.getCollection("成人用品避孕用品情趣内衣(去重复后8万)").drop();
|
||||
db.createCollection("成人用品避孕用品情趣内衣(去重复后8万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 户外军品旅游机票(去重复后64万)
|
||||
// ----------------------------
|
||||
db.getCollection("户外军品旅游机票(去重复后64万)").drop();
|
||||
db.createCollection("户外军品旅游机票(去重复后64万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 手机(去重复后80万)
|
||||
// ----------------------------
|
||||
db.getCollection("手机(去重复后80万)").drop();
|
||||
db.createCollection("手机(去重复后80万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 数码相机图形冲印(去重复后23.8万)
|
||||
// ----------------------------
|
||||
db.getCollection("数码相机图形冲印(去重复后23.8万)").drop();
|
||||
db.createCollection("数码相机图形冲印(去重复后23.8万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 数码配件电子元件(去重复后51.6万)
|
||||
// ----------------------------
|
||||
db.getCollection("数码配件电子元件(去重复后51.6万)").drop();
|
||||
db.createCollection("数码配件电子元件(去重复后51.6万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 时尚家饰工艺品(去重复后41万)
|
||||
// ----------------------------
|
||||
db.getCollection("时尚家饰工艺品(去重复后41万)").drop();
|
||||
db.createCollection("时尚家饰工艺品(去重复后41万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 汽车配件(去重复后38.6万)
|
||||
// ----------------------------
|
||||
db.getCollection("汽车配件(去重复后38.6万)").drop();
|
||||
db.createCollection("汽车配件(去重复后38.6万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 珠宝钻石翡翠(去重复后44.5万)
|
||||
// ----------------------------
|
||||
db.getCollection("珠宝钻石翡翠(去重复后44.5万)").drop();
|
||||
db.createCollection("珠宝钻石翡翠(去重复后44.5万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 电脑硬件台式机网络设备(去重复后20万)
|
||||
// ----------------------------
|
||||
db.getCollection("电脑硬件台式机网络设备(去重复后20万)").drop();
|
||||
db.createCollection("电脑硬件台式机网络设备(去重复后20万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 男装(去重复后216万)
|
||||
// ----------------------------
|
||||
db.getCollection("男装(去重复后216万)").drop();
|
||||
db.createCollection("男装(去重复后216万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 童装婴儿服鞋帽(去重复后52万)
|
||||
// ----------------------------
|
||||
db.getCollection("童装婴儿服鞋帽(去重复后52万)").drop();
|
||||
db.createCollection("童装婴儿服鞋帽(去重复后52万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 笔记本(去重复后32万)
|
||||
// ----------------------------
|
||||
db.getCollection("笔记本(去重复后32万)").drop();
|
||||
db.createCollection("笔记本(去重复后32万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 网络服务电脑软件(去重复后17万)
|
||||
// ----------------------------
|
||||
db.getCollection("网络服务电脑软件(去重复后17万)").drop();
|
||||
db.createCollection("网络服务电脑软件(去重复后17万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 装潢灯具五金(去重复后107万)
|
||||
// ----------------------------
|
||||
db.getCollection("装潢灯具五金(去重复后107万)").drop();
|
||||
db.createCollection("装潢灯具五金(去重复后107万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 食品保健(去重复后84.5万)
|
||||
// ----------------------------
|
||||
db.getCollection("食品保健(去重复后84.5万)").drop();
|
||||
db.createCollection("食品保健(去重复后84.5万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 饰品(去重复14.7万)
|
||||
// ----------------------------
|
||||
db.getCollection("饰品(去重复14.7万)").drop();
|
||||
db.createCollection("饰品(去重复14.7万)");
|
||||
156
Moncter/数据库/KR_游戏.js
Normal file
156
Moncter/数据库/KR_游戏.js
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_游戏
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:21:24
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 178游戏网
|
||||
// ----------------------------
|
||||
db.getCollection("178游戏网").drop();
|
||||
db.createCollection("178游戏网");
|
||||
db.getCollection("178游戏网").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 766游戏网
|
||||
// ----------------------------
|
||||
db.getCollection("766游戏网").drop();
|
||||
db.createCollection("766游戏网");
|
||||
db.getCollection("766游戏网").createIndex({
|
||||
"data.0": NumberInt("1")
|
||||
}, {
|
||||
name: "data.0_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 7k7k小游戏
|
||||
// ----------------------------
|
||||
db.getCollection("7k7k小游戏").drop();
|
||||
db.createCollection("7k7k小游戏");
|
||||
db.getCollection("7k7k小游戏").createIndex({
|
||||
account: NumberInt("1")
|
||||
}, {
|
||||
name: "account_1"
|
||||
});
|
||||
db.getCollection("7k7k小游戏").createIndex({
|
||||
domain: NumberInt("1")
|
||||
}, {
|
||||
name: "domain_1"
|
||||
});
|
||||
db.getCollection("7k7k小游戏").createIndex({
|
||||
account_type: NumberInt("1")
|
||||
}, {
|
||||
name: "account_type_1"
|
||||
});
|
||||
db.getCollection("7k7k小游戏").createIndex({
|
||||
is_email: NumberInt("1")
|
||||
}, {
|
||||
name: "is_email_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for DNF
|
||||
// ----------------------------
|
||||
db.getCollection("DNF").drop();
|
||||
db.createCollection("DNF");
|
||||
db.getCollection("DNF").createIndex({
|
||||
"data.0": NumberInt("1")
|
||||
}, {
|
||||
name: "data.0_1"
|
||||
});
|
||||
db.getCollection("DNF").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
db.getCollection("DNF").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1"
|
||||
});
|
||||
db.getCollection("DNF").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1"
|
||||
});
|
||||
db.getCollection("DNF").createIndex({
|
||||
phone: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1"
|
||||
});
|
||||
db.getCollection("DNF").createIndex({
|
||||
character_name: NumberInt("1")
|
||||
}, {
|
||||
name: "character_name_1"
|
||||
});
|
||||
db.getCollection("DNF").createIndex({
|
||||
server_name: NumberInt("1")
|
||||
}, {
|
||||
name: "server_name_1"
|
||||
});
|
||||
db.getCollection("DNF").createIndex({
|
||||
level: NumberInt("-1")
|
||||
}, {
|
||||
name: "level_-1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for KR_游戏_重叠用户
|
||||
// ----------------------------
|
||||
db.getCollection("KR_游戏_重叠用户").drop();
|
||||
db.createCollection("KR_游戏_重叠用户");
|
||||
db.getCollection("KR_游戏_重叠用户").createIndex({
|
||||
user_id: NumberInt("1")
|
||||
}, {
|
||||
name: "user_id_1"
|
||||
});
|
||||
db.getCollection("KR_游戏_重叠用户").createIndex({
|
||||
user_value: NumberInt("1")
|
||||
}, {
|
||||
name: "user_value_1"
|
||||
});
|
||||
db.getCollection("KR_游戏_重叠用户").createIndex({
|
||||
overlap_count: NumberInt("1")
|
||||
}, {
|
||||
name: "overlap_count_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 游戏语音ispeak_cn
|
||||
// ----------------------------
|
||||
db.getCollection("游戏语音ispeak_cn").drop();
|
||||
db.createCollection("游戏语音ispeak_cn");
|
||||
db.getCollection("游戏语音ispeak_cn").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1"
|
||||
});
|
||||
db.getCollection("游戏语音ispeak_cn").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1"
|
||||
});
|
||||
1136
Moncter/数据库/KR_腾讯.js
Normal file
1136
Moncter/数据库/KR_腾讯.js
Normal file
File diff suppressed because it is too large
Load Diff
79
Moncter/数据库/KR_酒店.js
Normal file
79
Moncter/数据库/KR_酒店.js
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_酒店
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:19:17
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 如家汉庭
|
||||
// ----------------------------
|
||||
db.getCollection("如家汉庭").drop();
|
||||
db.createCollection("如家汉庭");
|
||||
db.getCollection("如家汉庭").createIndex({
|
||||
Mobile: NumberInt("1")
|
||||
}, {
|
||||
name: "Mobile_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 酒店开房记录_2013年8月_2000万
|
||||
// ----------------------------
|
||||
db.getCollection("酒店开房记录_2013年8月_2000万").drop();
|
||||
db.createCollection("酒店开房记录_2013年8月_2000万");
|
||||
db.getCollection("酒店开房记录_2013年8月_2000万").createIndex({
|
||||
name: NumberInt("1")
|
||||
}, {
|
||||
name: "name_1"
|
||||
});
|
||||
db.getCollection("酒店开房记录_2013年8月_2000万").createIndex({
|
||||
phone: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1"
|
||||
});
|
||||
db.getCollection("酒店开房记录_2013年8月_2000万").createIndex({
|
||||
id_card: NumberInt("1")
|
||||
}, {
|
||||
name: "id_card_1"
|
||||
});
|
||||
db.getCollection("酒店开房记录_2013年8月_2000万").createIndex({
|
||||
mobile: NumberInt("1")
|
||||
}, {
|
||||
name: "mobile_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 酒店开房记录_2013年8月_2000万内
|
||||
// ----------------------------
|
||||
db.getCollection("酒店开房记录_2013年8月_2000万内").drop();
|
||||
db.createCollection("酒店开房记录_2013年8月_2000万内");
|
||||
db.getCollection("酒店开房记录_2013年8月_2000万内").createIndex({
|
||||
mobile: NumberInt("1")
|
||||
}, {
|
||||
name: "mobile_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 酒店开房记录_2013年8月_2000万内_备份
|
||||
// ----------------------------
|
||||
db.getCollection("酒店开房记录_2013年8月_2000万内_备份").drop();
|
||||
db.createCollection("酒店开房记录_2013年8月_2000万内_备份");
|
||||
85
Moncter/数据库/KR_销售额3000万元-5000万元企业名录.js
Normal file
85
Moncter/数据库/KR_销售额3000万元-5000万元企业名录.js
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_销售额3000万元-5000万元企业名录
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:21:17
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 企业名录 的副本
|
||||
// ----------------------------
|
||||
db.getCollection("企业名录 的副本").drop();
|
||||
db.createCollection("企业名录 的副本");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 企业规模代码
|
||||
// ----------------------------
|
||||
db.getCollection("企业规模代码").drop();
|
||||
db.createCollection("企业规模代码");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 控股情况代码
|
||||
// ----------------------------
|
||||
db.getCollection("控股情况代码").drop();
|
||||
db.createCollection("控股情况代码");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 机构类型代码
|
||||
// ----------------------------
|
||||
db.getCollection("机构类型代码").drop();
|
||||
db.createCollection("机构类型代码");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 经济类型代码
|
||||
// ----------------------------
|
||||
db.getCollection("经济类型代码").drop();
|
||||
db.createCollection("经济类型代码");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 营业收入代码
|
||||
// ----------------------------
|
||||
db.getCollection("营业收入代码").drop();
|
||||
db.createCollection("营业收入代码");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 行业代码
|
||||
// ----------------------------
|
||||
db.getCollection("行业代码").drop();
|
||||
db.createCollection("行业代码");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 行政区划代码
|
||||
// ----------------------------
|
||||
db.getCollection("行政区划代码").drop();
|
||||
db.createCollection("行政区划代码");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 销售额3000万元-5000万元企业名录
|
||||
// ----------------------------
|
||||
db.getCollection("销售额3000万元-5000万元企业名录").drop();
|
||||
db.createCollection("销售额3000万元-5000万元企业名录");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 隶属关系代码
|
||||
// ----------------------------
|
||||
db.getCollection("隶属关系代码").drop();
|
||||
db.createCollection("隶属关系代码");
|
||||
91
Moncter/数据库/KR_顺丰.js
Normal file
91
Moncter/数据库/KR_顺丰.js
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_顺丰
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:20:28
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 顺丰快递数据
|
||||
// ----------------------------
|
||||
db.getCollection("顺丰快递数据").drop();
|
||||
db.createCollection("顺丰快递数据");
|
||||
db.getCollection("顺丰快递数据").createIndex({
|
||||
phone: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1"
|
||||
});
|
||||
db.getCollection("顺丰快递数据").createIndex({
|
||||
province: NumberInt("1")
|
||||
}, {
|
||||
name: "province_1"
|
||||
});
|
||||
db.getCollection("顺丰快递数据").createIndex({
|
||||
city: NumberInt("1")
|
||||
}, {
|
||||
name: "city_1"
|
||||
});
|
||||
db.getCollection("顺丰快递数据").createIndex({
|
||||
province: NumberInt("1"),
|
||||
city: NumberInt("1")
|
||||
}, {
|
||||
name: "province_1_city_1"
|
||||
});
|
||||
db.getCollection("顺丰快递数据").createIndex({
|
||||
phone: NumberInt("1"),
|
||||
province: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1_province_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 顺丰快递数据_tmpForCopy1
|
||||
// ----------------------------
|
||||
db.getCollection("顺丰快递数据_tmpForCopy1").drop();
|
||||
db.createCollection("顺丰快递数据_tmpForCopy1");
|
||||
db.getCollection("顺丰快递数据_tmpForCopy1").createIndex({
|
||||
phone: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1"
|
||||
});
|
||||
db.getCollection("顺丰快递数据_tmpForCopy1").createIndex({
|
||||
province: NumberInt("1")
|
||||
}, {
|
||||
name: "province_1"
|
||||
});
|
||||
db.getCollection("顺丰快递数据_tmpForCopy1").createIndex({
|
||||
city: NumberInt("1")
|
||||
}, {
|
||||
name: "city_1"
|
||||
});
|
||||
db.getCollection("顺丰快递数据_tmpForCopy1").createIndex({
|
||||
province: NumberInt("1"),
|
||||
city: NumberInt("1")
|
||||
}, {
|
||||
name: "province_1_city_1"
|
||||
});
|
||||
db.getCollection("顺丰快递数据_tmpForCopy1").createIndex({
|
||||
phone: NumberInt("1"),
|
||||
province: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1_province_1"
|
||||
});
|
||||
217
Moncter/数据库/KR_香港在大陆投资企业名录.js
Normal file
217
Moncter/数据库/KR_香港在大陆投资企业名录.js
Normal file
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_香港在大陆投资企业名录
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:21:07
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 上海
|
||||
// ----------------------------
|
||||
db.getCollection("上海").drop();
|
||||
db.createCollection("上海");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 云南
|
||||
// ----------------------------
|
||||
db.getCollection("云南").drop();
|
||||
db.createCollection("云南");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 内蒙
|
||||
// ----------------------------
|
||||
db.getCollection("内蒙").drop();
|
||||
db.createCollection("内蒙");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 北京
|
||||
// ----------------------------
|
||||
db.getCollection("北京").drop();
|
||||
db.createCollection("北京");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 吉林
|
||||
// ----------------------------
|
||||
db.getCollection("吉林").drop();
|
||||
db.createCollection("吉林");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 四川
|
||||
// ----------------------------
|
||||
db.getCollection("四川").drop();
|
||||
db.createCollection("四川");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 天津
|
||||
// ----------------------------
|
||||
db.getCollection("天津").drop();
|
||||
db.createCollection("天津");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 宁夏
|
||||
// ----------------------------
|
||||
db.getCollection("宁夏").drop();
|
||||
db.createCollection("宁夏");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 安徽
|
||||
// ----------------------------
|
||||
db.getCollection("安徽").drop();
|
||||
db.createCollection("安徽");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 山东
|
||||
// ----------------------------
|
||||
db.getCollection("山东").drop();
|
||||
db.createCollection("山东");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 山西
|
||||
// ----------------------------
|
||||
db.getCollection("山西").drop();
|
||||
db.createCollection("山西");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东
|
||||
// ----------------------------
|
||||
db.getCollection("广东").drop();
|
||||
db.createCollection("广东");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广西
|
||||
// ----------------------------
|
||||
db.getCollection("广西").drop();
|
||||
db.createCollection("广西");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 新疆
|
||||
// ----------------------------
|
||||
db.getCollection("新疆").drop();
|
||||
db.createCollection("新疆");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江苏
|
||||
// ----------------------------
|
||||
db.getCollection("江苏").drop();
|
||||
db.createCollection("江苏");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西
|
||||
// ----------------------------
|
||||
db.getCollection("江西").drop();
|
||||
db.createCollection("江西");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 河北
|
||||
// ----------------------------
|
||||
db.getCollection("河北").drop();
|
||||
db.createCollection("河北");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 河南
|
||||
// ----------------------------
|
||||
db.getCollection("河南").drop();
|
||||
db.createCollection("河南");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 浙江
|
||||
// ----------------------------
|
||||
db.getCollection("浙江").drop();
|
||||
db.createCollection("浙江");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 海南
|
||||
// ----------------------------
|
||||
db.getCollection("海南").drop();
|
||||
db.createCollection("海南");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 湖北
|
||||
// ----------------------------
|
||||
db.getCollection("湖北").drop();
|
||||
db.createCollection("湖北");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 湖南
|
||||
// ----------------------------
|
||||
db.getCollection("湖南").drop();
|
||||
db.createCollection("湖南");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 甘肃
|
||||
// ----------------------------
|
||||
db.getCollection("甘肃").drop();
|
||||
db.createCollection("甘肃");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 福建
|
||||
// ----------------------------
|
||||
db.getCollection("福建").drop();
|
||||
db.createCollection("福建");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 西藏
|
||||
// ----------------------------
|
||||
db.getCollection("西藏").drop();
|
||||
db.createCollection("西藏");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 贵州
|
||||
// ----------------------------
|
||||
db.getCollection("贵州").drop();
|
||||
db.createCollection("贵州");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 辽宁
|
||||
// ----------------------------
|
||||
db.getCollection("辽宁").drop();
|
||||
db.createCollection("辽宁");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 重庆
|
||||
// ----------------------------
|
||||
db.getCollection("重庆").drop();
|
||||
db.createCollection("重庆");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 陕西
|
||||
// ----------------------------
|
||||
db.getCollection("陕西").drop();
|
||||
db.createCollection("陕西");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 青海
|
||||
// ----------------------------
|
||||
db.getCollection("青海").drop();
|
||||
db.createCollection("青海");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 香港投资企业名录
|
||||
// ----------------------------
|
||||
db.getCollection("香港投资企业名录").drop();
|
||||
db.createCollection("香港投资企业名录");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 黑龙江
|
||||
// ----------------------------
|
||||
db.getCollection("黑龙江").drop();
|
||||
db.createCollection("黑龙江");
|
||||
103
Moncter/数据库/KR_魔兽世界.js
Normal file
103
Moncter/数据库/KR_魔兽世界.js
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_魔兽世界
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:19:37
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for elysn欧美_魔兽世界主题库
|
||||
// ----------------------------
|
||||
db.getCollection("elysn欧美_魔兽世界主题库").drop();
|
||||
db.createCollection("elysn欧美_魔兽世界主题库");
|
||||
db.getCollection("elysn欧美_魔兽世界主题库").createIndex({
|
||||
"账号": NumberInt("1")
|
||||
}, {
|
||||
name: "账号_1"
|
||||
});
|
||||
db.getCollection("elysn欧美_魔兽世界主题库").createIndex({
|
||||
"密码": NumberInt("1")
|
||||
}, {
|
||||
name: "密码_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 网易2500W密正数据
|
||||
// ----------------------------
|
||||
db.getCollection("网易2500W密正数据").drop();
|
||||
db.createCollection("网易2500W密正数据");
|
||||
db.getCollection("网易2500W密正数据").createIndex({
|
||||
"账号": NumberInt("1")
|
||||
}, {
|
||||
name: "账号_1",
|
||||
unique: true
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 网易IS_game.sohu.com邮箱占90%
|
||||
// ----------------------------
|
||||
db.getCollection("网易IS_game.sohu.com邮箱占90%").drop();
|
||||
db.createCollection("网易IS_game.sohu.com邮箱占90%");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 网易正确数据_已确认
|
||||
// ----------------------------
|
||||
db.getCollection("网易正确数据_已确认").drop();
|
||||
db.createCollection("网易正确数据_已确认");
|
||||
db.getCollection("网易正确数据_已确认").createIndex({
|
||||
"账号": NumberInt("1")
|
||||
}, {
|
||||
name: "账号_1",
|
||||
background: true,
|
||||
unique: true
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 网易正确数据_空白数据
|
||||
// ----------------------------
|
||||
db.getCollection("网易正确数据_空白数据").drop();
|
||||
db.createCollection("网易正确数据_空白数据");
|
||||
db.getCollection("网易正确数据_空白数据").createIndex({
|
||||
"账号": NumberInt("1")
|
||||
}, {
|
||||
name: "idx_account",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("网易正确数据_空白数据").createIndex({
|
||||
"密码": NumberInt("1")
|
||||
}, {
|
||||
name: "idx_password",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("网易正确数据_空白数据").createIndex({
|
||||
"账号": NumberInt("1"),
|
||||
"密码": NumberInt("1")
|
||||
}, {
|
||||
name: "idx_account_password",
|
||||
background: true
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 魔兽世界_3月库
|
||||
// ----------------------------
|
||||
db.getCollection("魔兽世界_3月库").drop();
|
||||
db.createCollection("魔兽世界_3月库");
|
||||
@@ -201,40 +201,38 @@ class UserController extends BaseController
|
||||
* 修改密码
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function modifyPwd()
|
||||
public function modifyPwd($data = [])
|
||||
{
|
||||
// 获取并验证参数
|
||||
$params = $this->validateModifyPwdParams();
|
||||
if (!is_array($params)) {
|
||||
return $params;
|
||||
|
||||
if (empty($data)) {
|
||||
return json_encode(['code' => 400,'msg' => '参数缺失']);
|
||||
}
|
||||
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (!isset($data['id']) || !isset($data['pwd'])) {
|
||||
return json_encode(['code' => 401,'msg' => '参数缺失']);
|
||||
}
|
||||
$authorization = $this->authorization;
|
||||
|
||||
if (empty($authorization)) {
|
||||
return errorJson('缺少授权信息');
|
||||
return json_encode(['code' => 400,'msg' => '缺少授权信息']);
|
||||
}
|
||||
|
||||
$headerData = ['client:' . self::CLIENT_TYPE];
|
||||
$header = setHeader($headerData, $authorization, 'plain');
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
$params = [
|
||||
'id' => $data['id'],
|
||||
'newPw' => $data['pwd'],
|
||||
];
|
||||
|
||||
try {
|
||||
$result = requestCurl($this->baseUrl . 'api/Account/self', $params, 'PUT', $header);
|
||||
$result = requestCurl($this->baseUrl . 'api/Account/modifypw', $params, 'PUT', $header,'json');
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
if (empty($response)) {
|
||||
// 获取当前用户信息
|
||||
$currentUser = CompanyAccountModel::where('token', $authorization)->find();
|
||||
if ($currentUser) {
|
||||
recordUserLog($currentUser['id'], $currentUser['userName'], 'MODIFY_PASSWORD', '修改密码成功', [], 200, '修改成功');
|
||||
}
|
||||
return successJson(['message' => '修改成功']);
|
||||
return json_encode(['code' => 200,'msg' => '修改成功']);
|
||||
}
|
||||
|
||||
recordUserLog(0, '', 'MODIFY_PASSWORD', '修改密码失败', $params, 500, $response);
|
||||
return errorJson($response);
|
||||
return json_encode(['code' => 400,'msg' => $response]);
|
||||
} catch (\Exception $e) {
|
||||
recordUserLog(0, '', 'MODIFY_PASSWORD', '修改密码异常', $params, 500, $e->getMessage());
|
||||
return errorJson('修改密码失败:' . $e->getMessage());
|
||||
return json_encode(['code' => 400,'msg' => '修改密码失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) . '条');
|
||||
|
||||
@@ -7,5 +7,5 @@ use think\Model;
|
||||
class WechatAccountModel extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $table = 's2_wechat_account';
|
||||
protected $table = 's2_wechat_account';
|
||||
}
|
||||
@@ -14,6 +14,8 @@ Route::group('v1/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\WechatFriendController@getList'); // 获取好友列表
|
||||
Route::get('detail', 'app\chukebao\controller\WechatFriendController@getDetail'); // 获取好友详情
|
||||
Route::post('updateInfo', 'app\chukebao\controller\WechatFriendController@updateFriendInfo'); // 更新好友资料
|
||||
// 添加好友任务记录相关接口
|
||||
Route::get('addTaskList', 'app\chukebao\controller\WechatFriendController@getAddTaskList'); // 获取添加好友任务记录列表(包含添加者信息、状态、时间等,支持状态筛选,无需传好友ID)
|
||||
});
|
||||
//群相关
|
||||
Route::group('wechatChatroom/', function () {
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use app\api\model\WechatChatroomModel;
|
||||
use library\ResponseHelper;
|
||||
use app\api\model\WechatFriendModel;
|
||||
use app\api\model\WechatMessageModel;
|
||||
use app\api\controller\MessageController;
|
||||
|
||||
|
||||
@@ -33,6 +35,7 @@ class DataProcessing extends BaseController
|
||||
'CmdAllotFriend', //转让好友 {labels、wechatAccountId、wechatFriendId}
|
||||
'CmdChatroomOperate', //修改群信息 {chatroomName(群名)、announce(公告)、extra(公告)、wechatAccountId、wechatChatroomId}
|
||||
'CmdNewMessage', //接收消息
|
||||
'CmdSendMessageResult', //更新消息状态
|
||||
];
|
||||
|
||||
if (empty($type) || empty($wechatAccountId)) {
|
||||
@@ -75,15 +78,31 @@ class DataProcessing extends BaseController
|
||||
if(empty($toAccountId)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
$friend = WechatFriendModel::where(['id' => $wechatFriendId,'wechatAccountId' => $wechatAccountId])->find();
|
||||
if(empty($friend)){
|
||||
return ResponseHelper::error('好友不存在');
|
||||
if(empty($wechatFriendId) && empty($wechatChatroomId)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
$friend->accountId = $toAccountId;
|
||||
$friend->updateTime = time();
|
||||
$friend->save();
|
||||
$msg = '好友转移成功';
|
||||
|
||||
|
||||
if (!empty($wechatFriendId)){
|
||||
$data = WechatFriendModel::where(['id' => $wechatFriendId,'wechatAccountId' => $wechatAccountId])->find();
|
||||
$msg = '好友转移成功';
|
||||
if(empty($data)){
|
||||
return ResponseHelper::error('好友不存在');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!empty($wechatChatroomId)){
|
||||
$data = WechatChatroomModel::where(['id' => $wechatChatroomId,'wechatAccountId' => $wechatAccountId])->find();
|
||||
$msg = '群聊转移成功';
|
||||
if(empty($data)){
|
||||
return ResponseHelper::error('群聊不存在');
|
||||
}
|
||||
}
|
||||
|
||||
$data->accountId = $toAccountId;
|
||||
$data->updateTime = time();
|
||||
$data->save();
|
||||
break;
|
||||
case 'CmdNewMessage':
|
||||
if(empty($friendMessage) && empty($chatroomMessage)){
|
||||
@@ -105,8 +124,46 @@ class DataProcessing extends BaseController
|
||||
$msg = '消息记录成功';
|
||||
}else{
|
||||
$msg = '消息记录失败';
|
||||
$codee = 400;
|
||||
$codee = 200;
|
||||
}
|
||||
break;
|
||||
case 'CmdSendMessageResult':
|
||||
$friendMessageId = $this->request->param('friendMessageId', 0);
|
||||
$chatroomMessageId = $this->request->param('chatroomMessageId', 0);
|
||||
$sendStatus = $this->request->param('sendStatus', null);
|
||||
$wechatTime = $this->request->param('wechatTime', 0);
|
||||
|
||||
if ($sendStatus === null) {
|
||||
return ResponseHelper::error('sendStatus不能为空');
|
||||
}
|
||||
|
||||
if (empty($friendMessageId) && empty($chatroomMessageId)) {
|
||||
return ResponseHelper::error('friendMessageId或chatroomMessageId至少提供一个');
|
||||
}
|
||||
|
||||
$messageId = $friendMessageId ?: $chatroomMessageId;
|
||||
$update = [
|
||||
'sendStatus' => (int)$sendStatus,
|
||||
];
|
||||
|
||||
if (!empty($wechatTime)) {
|
||||
$update['wechatTime'] = strlen((string)$wechatTime) > 10
|
||||
? intval($wechatTime / 1000)
|
||||
: (int)$wechatTime;
|
||||
}
|
||||
|
||||
$affected = WechatMessageModel::where('id', $messageId)->update($update);
|
||||
|
||||
if ($affected === false) {
|
||||
return ResponseHelper::success('','更新消息状态失败');
|
||||
}
|
||||
|
||||
if ($affected === 0) {
|
||||
return ResponseHelper::success('','消息不存在');
|
||||
}
|
||||
|
||||
$msg = '更新消息状态成功';
|
||||
break;
|
||||
}
|
||||
return ResponseHelper::success('',$msg,$codee);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class WechatFriendController extends BaseController
|
||||
$total = $query->count();
|
||||
$list = $query->page($page, $limit)->select();
|
||||
|
||||
// 提取所有好友ID
|
||||
// 提取所有好友ID
|
||||
$friendIds = array_column($list, 'id');
|
||||
|
||||
$aiTypeData = [];
|
||||
@@ -67,7 +67,7 @@ class WechatFriendController extends BaseController
|
||||
$friend = Db::table('s2_wechat_friend')
|
||||
->where(['id' => $friendId, 'isDeleted' => 0])
|
||||
->find();
|
||||
|
||||
|
||||
if (empty($friend)) {
|
||||
return ResponseHelper::error('好友不存在');
|
||||
}
|
||||
@@ -78,11 +78,11 @@ class WechatFriendController extends BaseController
|
||||
$friend['createTime'] = !empty($friend['createTime']) ? date('Y-m-d H:i:s', $friend['createTime']) : '';
|
||||
$friend['updateTime'] = !empty($friend['updateTime']) ? date('Y-m-d H:i:s', $friend['updateTime']) : '';
|
||||
$friend['passTime'] = !empty($friend['passTime']) ? date('Y-m-d H:i:s', $friend['passTime']) : '';
|
||||
|
||||
|
||||
// 获取AI类型设置
|
||||
$aiTypeSetting = FriendSettings::where('friendId', $friendId)->find();
|
||||
$friend['aiType'] = $aiTypeSetting ? $aiTypeSetting['type'] : 0;
|
||||
|
||||
|
||||
return ResponseHelper::success(['detail' => $friend]);
|
||||
}
|
||||
|
||||
@@ -166,4 +166,112 @@ class WechatFriendController extends BaseController
|
||||
|
||||
return ResponseHelper::success(['id' => $friendId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取添加好友任务记录列表(全新功能)
|
||||
* 返回当前账号的所有添加好友任务记录,无论是否通过都展示
|
||||
* 包含:添加者头像、昵称、微信号、添加状态、添加时间、通过时间等信息
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getAddTaskList()
|
||||
{
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$status = $this->request->param('status', ''); // 可选:筛选状态 0执行中,1执行成功,2执行失败
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
|
||||
if (empty($accountId)) {
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
// 直接使用operatorAccountId查询添加好友任务记录
|
||||
$query = Db::table('s2_friend_task')
|
||||
->where('operatorAccountId', $accountId)
|
||||
->order('createTime desc');
|
||||
|
||||
// 如果指定了状态筛选
|
||||
if ($status !== '' && $status !== null) {
|
||||
$query->where('status', $status);
|
||||
}
|
||||
|
||||
$total = $query->count();
|
||||
$tasks = $query->page($page, $limit)->select();
|
||||
|
||||
|
||||
// 处理任务数据
|
||||
$list = [];
|
||||
foreach ($tasks as $task) {
|
||||
// 提取所有任务的phone、wechatId,用于查询好友信息(获取通过时间)
|
||||
$friendInfo = Db::table('s2_wechat_friend')
|
||||
->where(['isDeleted' => 0, 'ownerWechatId' => $task['wechatId']])
|
||||
->where(function ($query) use ($task) {
|
||||
$query->whereLike('phone', '%'.$task['phone'].'%')->whereOr('alias', $task['phone'])->whereOr('wechatId', $task['phone']);
|
||||
})->field('phone,wechatId,alias,passTime,nickname')->find();
|
||||
|
||||
|
||||
|
||||
$item = [
|
||||
'taskId' => $task['id'] ?? 0,
|
||||
'phone' => $task['phone'] ?? '',
|
||||
'wechatId' => $task['wechatId'] ?? '',
|
||||
'alias' => $task['alias'] ?? '',
|
||||
// 添加者信息
|
||||
'adder' => [
|
||||
'avatar' => $task['wechatAvatar'] ?? '', // 添加者头像
|
||||
'nickname' => $task['wechatNickname'] ?? '', // 添加者昵称
|
||||
'username' => $task['accountUsername'] ?? '', // 添加者微信号
|
||||
'accountNickname' => $task['accountNickname'] ?? '', // 账号昵称
|
||||
'accountRealName' => $task['accountRealName'] ?? '', // 账号真实姓名
|
||||
],
|
||||
// 添加状态
|
||||
'status' => [
|
||||
'code' => $task['status'] ?? 0, // 状态码:0执行中,1执行成功,2执行失败
|
||||
'text' => $this->getTaskStatusText($task['status'] ?? 0), // 状态文本
|
||||
'extra' => ''
|
||||
],
|
||||
// 时间信息
|
||||
'time' => [
|
||||
'addTime' => !empty($task['createTime']) ? date('Y-m-d H:i:s', $task['createTime']) : '', // 添加时间
|
||||
'addTimeStamp' => $task['createTime'] ?? 0, // 添加时间戳
|
||||
'updateTime' => !empty($task['updateTime']) ? date('Y-m-d H:i:s', $task['updateTime']) : '', // 更新时间
|
||||
'updateTimeStamp' => $task['updateTime'] ?? 0, // 更新时间戳
|
||||
'passTime' => !empty($friendInfo['passTime']) ? date('Y-m-d H:i:s', $friendInfo['passTime']) : '', // 通过时间
|
||||
'passTimeStamp' => $friendInfo['passTime'] ?? 0, // 通过时间戳
|
||||
],
|
||||
// 好友信息(如果已通过)
|
||||
'friend' => [
|
||||
'nickname' => $friendInfo['nickname'] ?? '', // 好友昵称
|
||||
'isPassed' => !empty($friendInfo['passTime']), // 是否已通过
|
||||
],
|
||||
// 其他信息
|
||||
'other' => [
|
||||
'msgContent' => $task['msgContent'] ?? '', // 验证消息
|
||||
'remark' => $task['remark'] ?? '', // 备注
|
||||
'from' => $task['from'] ?? '', // 来源
|
||||
'labels' => !empty($task['labels']) ? explode(',', $task['labels']) : [], // 标签
|
||||
]
|
||||
];
|
||||
|
||||
$list[] = $item;
|
||||
}
|
||||
|
||||
return ResponseHelper::success(['list' => $list, 'total' => $total]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取任务状态文本
|
||||
* @param int $status 状态码
|
||||
* @return string 状态文本
|
||||
*/
|
||||
private function getTaskStatusText($status)
|
||||
{
|
||||
$statusMap = [
|
||||
0 => '执行中',
|
||||
1 => '执行成功',
|
||||
2 => '执行失败',
|
||||
];
|
||||
|
||||
return isset($statusMap[$status]) ? $statusMap[$status] : '未知状态';
|
||||
}
|
||||
}
|
||||
@@ -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', // 更新微信账号评分记录
|
||||
];
|
||||
|
||||
@@ -0,0 +1,558 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\Db;
|
||||
use think\facade\Log;
|
||||
use app\common\service\WechatAccountHealthScoreService;
|
||||
|
||||
/**
|
||||
* 统一计算微信账号健康分命令
|
||||
* 一个命令完成所有评分工作:
|
||||
* 1. 初始化未计算的账号(基础分只计算一次)
|
||||
* 2. 更新评分记录(根据wechatId和alias不一致情况)
|
||||
* 3. 批量更新健康分(只更新动态分)
|
||||
*/
|
||||
class CalculateWechatAccountScoreCommand extends Command
|
||||
{
|
||||
/**
|
||||
* 数据库表名
|
||||
*/
|
||||
const TABLE_WECHAT_ACCOUNT = 's2_wechat_account';
|
||||
const TABLE_WECHAT_ACCOUNT_SCORE = 's2_wechat_account_score';
|
||||
protected function configure()
|
||||
{
|
||||
$this->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("<error>{$errorMsg}</error>");
|
||||
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<error>数据库错误: " . $errorMsg . "</error>");
|
||||
$output->writeln($e->getTraceAsString());
|
||||
|
||||
// 记录数据库错误
|
||||
Log::error("数据库错误: " . $errorMsg);
|
||||
Log::error("错误堆栈: " . $e->getTraceAsString());
|
||||
|
||||
return 2; // 数据库错误状态码
|
||||
} catch (\Exception $e) {
|
||||
// 一般异常
|
||||
$errorMsg = "命令执行失败: " . $e->getMessage();
|
||||
$output->writeln("\n<error>错误: " . $errorMsg . "</error>");
|
||||
$output->writeln($e->getTraceAsString());
|
||||
|
||||
// 记录严重错误
|
||||
Log::error($errorMsg);
|
||||
Log::error("错误堆栈: " . $e->getTraceAsString());
|
||||
|
||||
return 1; // 一般错误状态码
|
||||
} catch (\Throwable $e) {
|
||||
// 其他所有错误
|
||||
$errorMsg = "严重错误: " . $e->getMessage();
|
||||
$output->writeln("\n<error>严重错误: " . $errorMsg . "</error>");
|
||||
$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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
168
Server/application/command/UpdateWechatAccountScoreCommand.php
Normal file
168
Server/application/command/UpdateWechatAccountScoreCommand.php
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\Db;
|
||||
use app\common\service\WechatAccountHealthScoreService;
|
||||
|
||||
/**
|
||||
* 更新微信账号评分记录
|
||||
* 根据wechatId和alias是否不一致来更新isModifiedAlias字段(仅用于评分,不修复数据)
|
||||
*/
|
||||
class UpdateWechatAccountScoreCommand extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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')) {
|
||||
|
||||
@@ -82,7 +82,7 @@ class TaskServer extends Server
|
||||
if ($current_worker_id == 1) {
|
||||
// 每60秒检查一次自动问候规则
|
||||
Timer::add(60, function () use ($adapter) {
|
||||
$adapter->handleAutoGreetings();
|
||||
//$adapter->handleAutoGreetings();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
333
Server/application/common/controller/ExportController.php
Normal file
333
Server/application/common/controller/ExportController.php
Normal file
@@ -0,0 +1,333 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use PHPExcel;
|
||||
use PHPExcel_IOFactory;
|
||||
use PHPExcel_Worksheet_Drawing;
|
||||
use think\Controller;
|
||||
use think\Exception;
|
||||
|
||||
/**
|
||||
* 通用导出控制器,提供 Excel 导出与图片插入能力
|
||||
*/
|
||||
class ExportController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var array<string> 需要在请求结束时清理的临时文件
|
||||
*/
|
||||
protected static $tempFiles = [];
|
||||
|
||||
/**
|
||||
* 导出 Excel(支持指定列插入图片)
|
||||
*
|
||||
* @param string $fileName 输出文件名(可不带扩展名)
|
||||
* @param array $headers 列定义,例如 ['name' => '姓名', 'phone' => '电话']
|
||||
* @param array $rows 数据行,需与 $headers 的 key 对应
|
||||
* @param array $imageColumns 需要渲染为图片的列 key 列表
|
||||
* @param string $sheetName 工作表名称
|
||||
* @param array $options 额外选项:
|
||||
* - imageWidth(图片宽度,默认100)
|
||||
* - imageHeight(图片高度,默认100)
|
||||
* - imageColumnWidth(图片列宽,默认15)
|
||||
* - titleRow(标题行内容,支持多行文本数组)
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function exportExcelWithImages(
|
||||
$fileName,
|
||||
array $headers,
|
||||
array $rows,
|
||||
array $imageColumns = [],
|
||||
$sheetName = 'Sheet1',
|
||||
array $options = []
|
||||
) {
|
||||
if (empty($headers)) {
|
||||
throw new Exception('导出列定义不能为空');
|
||||
}
|
||||
if (empty($rows)) {
|
||||
throw new Exception('导出数据不能为空');
|
||||
}
|
||||
|
||||
// 抑制 PHPExcel 库中已废弃的大括号语法警告(PHP 7.4+)
|
||||
$oldErrorReporting = error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT);
|
||||
|
||||
// 默认选项
|
||||
$imageWidth = isset($options['imageWidth']) ? (int)$options['imageWidth'] : 100;
|
||||
$imageHeight = isset($options['imageHeight']) ? (int)$options['imageHeight'] : 100;
|
||||
$imageColumnWidth = isset($options['imageColumnWidth']) ? (float)$options['imageColumnWidth'] : 15;
|
||||
$rowHeight = isset($options['rowHeight']) ? (int)$options['rowHeight'] : ($imageHeight + 10);
|
||||
|
||||
$excel = new PHPExcel();
|
||||
$sheet = $excel->getActiveSheet();
|
||||
$sheet->setTitle($sheetName);
|
||||
|
||||
$columnKeys = array_keys($headers);
|
||||
$totalColumns = count($columnKeys);
|
||||
$lastColumnLetter = self::columnLetter($totalColumns - 1);
|
||||
|
||||
// 定义特定列的固定宽度(如果未指定则使用默认值)
|
||||
$columnWidths = isset($options['columnWidths']) ? $options['columnWidths'] : [];
|
||||
|
||||
// 检查是否有标题行
|
||||
$titleRow = isset($options['titleRow']) ? $options['titleRow'] : null;
|
||||
$dataStartRow = 1; // 数据开始行(表头行)
|
||||
|
||||
// 如果有标题行,先写入标题行(支持数组或字符串)
|
||||
if (!empty($titleRow)) {
|
||||
$dataStartRow = 2; // 数据从第2行开始(第1行是标题,第2行是表头)
|
||||
|
||||
// 合并标题行单元格(从第一列到最后一列)
|
||||
$titleRange = 'A1:' . $lastColumnLetter . '1';
|
||||
$sheet->mergeCells($titleRange);
|
||||
|
||||
// 构建标题内容(支持多行数组或字符串)
|
||||
$titleContent = '';
|
||||
if (is_array($titleRow)) {
|
||||
$titleContent = implode("\n", $titleRow);
|
||||
} else {
|
||||
$titleContent = (string)$titleRow;
|
||||
}
|
||||
|
||||
// 写入标题
|
||||
$sheet->setCellValue('A1', $titleContent);
|
||||
|
||||
// 设置标题行样式
|
||||
$sheet->getStyle('A1')->applyFromArray([
|
||||
'font' => ['bold' => true, 'size' => 16],
|
||||
'alignment' => [
|
||||
'horizontal' => \PHPExcel_Style_Alignment::HORIZONTAL_CENTER,
|
||||
'vertical' => \PHPExcel_Style_Alignment::VERTICAL_CENTER,
|
||||
'wrap' => true
|
||||
],
|
||||
'fill' => [
|
||||
'type' => \PHPExcel_Style_Fill::FILL_SOLID,
|
||||
'color' => ['rgb' => 'FFF8DC'] // 浅黄色背景
|
||||
],
|
||||
'borders' => [
|
||||
'allborders' => [
|
||||
'style' => \PHPExcel_Style_Border::BORDER_THIN,
|
||||
'color' => ['rgb' => '000000']
|
||||
]
|
||||
]
|
||||
]);
|
||||
$sheet->getRowDimension(1)->setRowHeight(80); // 标题行高度
|
||||
}
|
||||
|
||||
// 写入表头并设置列宽
|
||||
$headerRow = $dataStartRow;
|
||||
foreach ($columnKeys as $index => $key) {
|
||||
$columnLetter = self::columnLetter($index);
|
||||
$sheet->setCellValue($columnLetter . $headerRow, $headers[$key]);
|
||||
|
||||
// 如果是图片列,设置固定列宽
|
||||
if (in_array($key, $imageColumns, true)) {
|
||||
$sheet->getColumnDimension($columnLetter)->setWidth($imageColumnWidth);
|
||||
} elseif (isset($columnWidths[$key])) {
|
||||
// 如果指定了该列的宽度,使用指定宽度
|
||||
$sheet->getColumnDimension($columnLetter)->setWidth($columnWidths[$key]);
|
||||
} else {
|
||||
// 否则自动调整
|
||||
$sheet->getColumnDimension($columnLetter)->setAutoSize(true);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置表头样式
|
||||
$headerRange = 'A' . $headerRow . ':' . $lastColumnLetter . $headerRow;
|
||||
$sheet->getStyle($headerRange)->applyFromArray([
|
||||
'font' => ['bold' => true, 'size' => 11],
|
||||
'alignment' => [
|
||||
'horizontal' => \PHPExcel_Style_Alignment::HORIZONTAL_CENTER,
|
||||
'vertical' => \PHPExcel_Style_Alignment::VERTICAL_CENTER,
|
||||
'wrap' => true
|
||||
],
|
||||
'fill' => [
|
||||
'type' => \PHPExcel_Style_Fill::FILL_SOLID,
|
||||
'color' => ['rgb' => 'FFF8DC']
|
||||
],
|
||||
'borders' => [
|
||||
'allborders' => [
|
||||
'style' => \PHPExcel_Style_Border::BORDER_THIN,
|
||||
'color' => ['rgb' => '000000']
|
||||
]
|
||||
]
|
||||
]);
|
||||
$sheet->getRowDimension($headerRow)->setRowHeight(30); // 增加表头行高以确保文本完整显示
|
||||
|
||||
// 写入数据与图片
|
||||
$dataRowStart = $dataStartRow + 1; // 数据从表头行下一行开始
|
||||
foreach ($rows as $rowIndex => $rowData) {
|
||||
$excelRow = $dataRowStart + $rowIndex; // 数据行
|
||||
$maxRowHeight = $rowHeight; // 记录当前行的最大高度
|
||||
|
||||
foreach ($columnKeys as $colIndex => $key) {
|
||||
$columnLetter = self::columnLetter($colIndex);
|
||||
$cell = $columnLetter . $excelRow;
|
||||
$value = isset($rowData[$key]) ? $rowData[$key] : '';
|
||||
|
||||
if (in_array($key, $imageColumns, true) && !empty($value)) {
|
||||
$imagePath = self::resolveImagePath($value);
|
||||
if ($imagePath) {
|
||||
// 获取图片实际尺寸并等比例缩放
|
||||
$imageSize = @getimagesize($imagePath);
|
||||
if ($imageSize) {
|
||||
$originalWidth = $imageSize[0];
|
||||
$originalHeight = $imageSize[1];
|
||||
|
||||
// 计算等比例缩放后的尺寸
|
||||
$ratio = min($imageWidth / $originalWidth, $imageHeight / $originalHeight);
|
||||
$scaledWidth = $originalWidth * $ratio;
|
||||
$scaledHeight = $originalHeight * $ratio;
|
||||
|
||||
// 确保不超过最大尺寸
|
||||
if ($scaledWidth > $imageWidth) {
|
||||
$scaledWidth = $imageWidth;
|
||||
$scaledHeight = $originalHeight * ($imageWidth / $originalWidth);
|
||||
}
|
||||
if ($scaledHeight > $imageHeight) {
|
||||
$scaledHeight = $imageHeight;
|
||||
$scaledWidth = $originalWidth * ($imageHeight / $originalHeight);
|
||||
}
|
||||
|
||||
$drawing = new PHPExcel_Worksheet_Drawing();
|
||||
$drawing->setPath($imagePath);
|
||||
$drawing->setCoordinates($cell);
|
||||
|
||||
// 居中显示图片(Excel列宽1单位≈7像素,行高1单位≈0.75像素)
|
||||
$cellWidthPx = $imageColumnWidth * 7;
|
||||
$cellHeightPx = $maxRowHeight * 0.75;
|
||||
$offsetX = max(2, ($cellWidthPx - $scaledWidth) / 2);
|
||||
$offsetY = max(2, ($cellHeightPx - $scaledHeight) / 2);
|
||||
|
||||
$drawing->setOffsetX((int)$offsetX);
|
||||
$drawing->setOffsetY((int)$offsetY);
|
||||
$drawing->setWidth((int)$scaledWidth);
|
||||
$drawing->setHeight((int)$scaledHeight);
|
||||
$drawing->setWorksheet($sheet);
|
||||
|
||||
// 更新行高以适应图片(留出一些边距)
|
||||
$neededHeight = (int)($scaledHeight / 0.75) + 10;
|
||||
if ($neededHeight > $maxRowHeight) {
|
||||
$maxRowHeight = $neededHeight;
|
||||
}
|
||||
} else {
|
||||
// 如果无法获取图片尺寸,使用默认尺寸
|
||||
$drawing = new PHPExcel_Worksheet_Drawing();
|
||||
$drawing->setPath($imagePath);
|
||||
$drawing->setCoordinates($cell);
|
||||
$drawing->setOffsetX(5);
|
||||
$drawing->setOffsetY(5);
|
||||
$drawing->setWidth($imageWidth);
|
||||
$drawing->setHeight($imageHeight);
|
||||
$drawing->setWorksheet($sheet);
|
||||
}
|
||||
} else {
|
||||
$sheet->setCellValue($cell, '');
|
||||
}
|
||||
} else {
|
||||
$sheet->setCellValue($cell, $value);
|
||||
// 设置文本对齐和换行
|
||||
$style = $sheet->getStyle($cell);
|
||||
$style->getAlignment()->setVertical(\PHPExcel_Style_Alignment::VERTICAL_CENTER);
|
||||
$style->getAlignment()->setWrapText(true);
|
||||
// 根据列类型设置水平对齐
|
||||
if (in_array($key, ['date', 'postTime'])) {
|
||||
$style->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_CENTER);
|
||||
} else {
|
||||
$style->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_LEFT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设置行高
|
||||
$sheet->getRowDimension($excelRow)->setRowHeight($maxRowHeight);
|
||||
}
|
||||
|
||||
$safeName = preg_replace('/[^\w\-]/', '_', $fileName ?: 'export_' . date('Ymd_His'));
|
||||
if (stripos($safeName, '.xlsx') === false) {
|
||||
$safeName .= '.xlsx';
|
||||
}
|
||||
|
||||
if (ob_get_length()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
header('Cache-Control: max-age=0');
|
||||
header('Content-Disposition: attachment;filename="' . $safeName . '"');
|
||||
|
||||
try {
|
||||
$writer = PHPExcel_IOFactory::createWriter($excel, 'Excel2007');
|
||||
$writer->save('php://output');
|
||||
} catch (\Exception $e) {
|
||||
// 恢复错误报告级别
|
||||
error_reporting($oldErrorReporting);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// 恢复错误报告级别
|
||||
error_reporting($oldErrorReporting);
|
||||
self::cleanupTempFiles();
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据列序号生成 Excel 列字母
|
||||
*
|
||||
* @param int $index
|
||||
* @return string
|
||||
*/
|
||||
protected static function columnLetter($index)
|
||||
{
|
||||
$letters = '';
|
||||
do {
|
||||
$letters = chr($index % 26 + 65) . $letters;
|
||||
$index = intval($index / 26) - 1;
|
||||
} while ($index >= 0);
|
||||
|
||||
return $letters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将远程或本地图片路径转换为可用的本地文件路径
|
||||
*
|
||||
* @param string $path
|
||||
* @return string|null
|
||||
*/
|
||||
protected static function resolveImagePath($path)
|
||||
{
|
||||
if (empty($path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (preg_match('/^https?:\/\//i', $path)) {
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'export_img_');
|
||||
$stream = @file_get_contents($path);
|
||||
if ($stream === false) {
|
||||
return null;
|
||||
}
|
||||
file_put_contents($tempFile, $stream);
|
||||
self::$tempFiles[] = $tempFile;
|
||||
return $tempFile;
|
||||
}
|
||||
|
||||
if (file_exists($path)) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有临时文件
|
||||
*/
|
||||
protected static function cleanupTempFiles()
|
||||
{
|
||||
foreach (self::$tempFiles as $file) {
|
||||
if (file_exists($file)) {
|
||||
@unlink($file);
|
||||
}
|
||||
}
|
||||
self::$tempFiles = [];
|
||||
}
|
||||
}
|
||||
44
Server/application/common/model/WechatAccountScore.php
Normal file
44
Server/application/common/model/WechatAccountScore.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 微信账号评分记录模型类
|
||||
*/
|
||||
class WechatAccountScore extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $name = 'wechat_account_score';
|
||||
protected $table = 's2_wechat_account_score';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = false;
|
||||
|
||||
// 定义字段类型
|
||||
protected $type = [
|
||||
'accountId' => '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',
|
||||
];
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -36,13 +36,14 @@ Route::group('v1/', function () {
|
||||
Route::get(':id/summary', 'app\cunkebao\controller\wechat\GetWechatOnDeviceSummarizeV1Controller@index');
|
||||
Route::get(':id/friends', 'app\cunkebao\controller\wechat\GetWechatOnDeviceFriendsV1Controller@index');
|
||||
Route::get('getWechatInfo', 'app\cunkebao\controller\wechat\GetWechatController@getWechatInfo');
|
||||
Route::get(':wechatId', 'app\cunkebao\controller\wechat\GetWechatProfileV1Controller@index');
|
||||
Route::post('transfer-friends', 'app\cunkebao\controller\wechat\PostTransferFriends@index'); // 微信好友转移
|
||||
|
||||
Route::get('overview', 'app\cunkebao\controller\wechat\GetWechatOverviewV1Controller@index'); // 获取微信账号概览数据
|
||||
Route::get('moments', 'app\cunkebao\controller\wechat\GetWechatMomentsV1Controller@index'); // 获取微信朋友圈
|
||||
Route::get('moments/export', 'app\cunkebao\controller\wechat\GetWechatMomentsV1Controller@export'); // 导出微信朋友圈
|
||||
Route::get('count', 'app\cunkebao\controller\DeviceWechat@count');
|
||||
Route::get('device-count', 'app\cunkebao\controller\DeviceWechat@deviceCount'); // 获取有登录微信的设备数量
|
||||
Route::put('refresh', 'app\cunkebao\controller\DeviceWechat@refresh'); // 刷新设备微信状态
|
||||
|
||||
Route::post('transfer-friends', 'app\cunkebao\controller\wechat\PostTransferFriends@index'); // 微信好友转移
|
||||
Route::get(':wechatId', 'app\cunkebao\controller\wechat\GetWechatProfileV1Controller@index');
|
||||
});
|
||||
|
||||
// 获客场景相关
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace app\cunkebao\controller;
|
||||
|
||||
use app\api\controller\AccountController;
|
||||
use app\api\controller\UserController;
|
||||
use app\common\service\ClassTableService;
|
||||
use library\ResponseHelper;
|
||||
use think\Controller;
|
||||
@@ -148,6 +149,12 @@ class BaseController extends Controller
|
||||
|
||||
$res = Db::name('users')->where(['id' => $userId, 'companyId' => $companyId])->update($data);
|
||||
if (!empty($res)) {
|
||||
if ($user['typeId'] == 1 && !empty($user['s2_accountId'])) {
|
||||
$UserController = new UserController();
|
||||
$UserController->modifyPwd(['id' => $user['s2_accountId'],'pwd' => $passWord]);
|
||||
}
|
||||
|
||||
|
||||
return ResponseHelper::success('密码修改成功');
|
||||
} else {
|
||||
return ResponseHelper::error('密码修改失败');
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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] ?? '未知';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\wechat;
|
||||
|
||||
use app\common\controller\ExportController;
|
||||
use app\common\model\Device as DeviceModel;
|
||||
use app\common\model\DeviceUser as DeviceUserModel;
|
||||
use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel;
|
||||
use app\common\model\User as UserModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 查看微信朋友圈列表(仅限当前操盘手可访问的微信)
|
||||
*/
|
||||
class GetWechatMomentsV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 主操盘手获取项目下所有设备ID
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getCompanyDevicesId(): array
|
||||
{
|
||||
return DeviceModel::where('companyId', $this->getUserInfo('companyId'))
|
||||
->column('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 非主操盘手仅可查看分配到的设备
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getUserDevicesId(): array
|
||||
{
|
||||
return DeviceUserModel::where([
|
||||
'userId' => $this->getUserInfo('id'),
|
||||
'companyId' => $this->getUserInfo('companyId'),
|
||||
])->column('deviceId');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户可访问的设备ID
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDevicesId(): array
|
||||
{
|
||||
return ($this->getUserInfo('isAdmin') == UserModel::ADMIN_STP)
|
||||
? $this->getCompanyDevicesId()
|
||||
: $this->getUserDevicesId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户可访问的微信ID集合
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getAccessibleWechatIds(): array
|
||||
{
|
||||
$deviceIds = $this->getDevicesId();
|
||||
if (empty($deviceIds)) {
|
||||
throw new \Exception('暂无可用设备', 200);
|
||||
}
|
||||
|
||||
return DeviceWechatLoginModel::distinct(true)
|
||||
->where('companyId', $this->getUserInfo('companyId'))
|
||||
->whereIn('deviceId', $deviceIds)
|
||||
->column('wechatId');
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看朋友圈列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$wechatId = $this->request->param('wechatId/s', '');
|
||||
if (empty($wechatId)) {
|
||||
return ResponseHelper::error('wechatId不能为空');
|
||||
}
|
||||
|
||||
// 权限校验:只能查看当前账号可访问的微信
|
||||
$accessibleWechatIds = $this->getAccessibleWechatIds();
|
||||
if (!in_array($wechatId, $accessibleWechatIds, true)) {
|
||||
return ResponseHelper::error('无权查看该微信的朋友圈', 403);
|
||||
}
|
||||
|
||||
// 获取对应的微信账号ID
|
||||
$accountId = Db::table('s2_wechat_account')
|
||||
->where('wechatId', $wechatId)
|
||||
->value('id');
|
||||
|
||||
if (empty($accountId)) {
|
||||
return ResponseHelper::error('微信账号不存在或尚未同步', 404);
|
||||
}
|
||||
|
||||
$query = Db::table('s2_wechat_moments')
|
||||
->where('wechatAccountId', $accountId);
|
||||
|
||||
// 关键词搜索
|
||||
if ($keyword = trim((string)$this->request->param('keyword', ''))) {
|
||||
$query->whereLike('content', '%' . $keyword . '%');
|
||||
}
|
||||
|
||||
// 类型筛选
|
||||
$type = $this->request->param('type', '');
|
||||
if ($type !== '' && $type !== null) {
|
||||
$query->where('type', (int)$type);
|
||||
}
|
||||
|
||||
// 时间筛选
|
||||
$startTime = $this->request->param('startTime', '');
|
||||
$endTime = $this->request->param('endTime', '');
|
||||
if ($startTime || $endTime) {
|
||||
$start = $startTime ? strtotime($startTime) : 0;
|
||||
$end = $endTime ? strtotime($endTime) : time();
|
||||
if ($start && $end && $end < $start) {
|
||||
return ResponseHelper::error('结束时间不能早于开始时间');
|
||||
}
|
||||
$query->whereBetween('createTime', [$start ?: 0, $end ?: time()]);
|
||||
}
|
||||
|
||||
$page = (int)$this->request->param('page', 1);
|
||||
$limit = (int)$this->request->param('limit', 10);
|
||||
|
||||
$paginator = $query->order('createTime', 'desc')
|
||||
->paginate($limit, false, ['page' => $page]);
|
||||
|
||||
$list = array_map(function ($item) {
|
||||
return $this->formatMomentRow($item);
|
||||
}, $paginator->items());
|
||||
|
||||
return ResponseHelper::success([
|
||||
'list' => $list,
|
||||
'total' => $paginator->total(),
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode() ?: 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出朋友圈数据到Excel
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
try {
|
||||
$wechatId = $this->request->param('wechatId/s', '');
|
||||
if (empty($wechatId)) {
|
||||
return ResponseHelper::error('wechatId不能为空');
|
||||
}
|
||||
|
||||
// 权限校验:只能查看当前账号可访问的微信
|
||||
$accessibleWechatIds = $this->getAccessibleWechatIds();
|
||||
if (!in_array($wechatId, $accessibleWechatIds, true)) {
|
||||
return ResponseHelper::error('无权查看该微信的朋友圈', 403);
|
||||
}
|
||||
|
||||
// 获取对应的微信账号ID
|
||||
$accountId = Db::table('s2_wechat_account')
|
||||
->where('wechatId', $wechatId)
|
||||
->value('id');
|
||||
|
||||
if (empty($accountId)) {
|
||||
return ResponseHelper::error('微信账号不存在或尚未同步', 404);
|
||||
}
|
||||
|
||||
$query = Db::table('s2_wechat_moments')
|
||||
->where('wechatAccountId', $accountId);
|
||||
|
||||
// 关键词搜索
|
||||
if ($keyword = trim((string)$this->request->param('keyword', ''))) {
|
||||
$query->whereLike('content', '%' . $keyword . '%');
|
||||
}
|
||||
|
||||
// 类型筛选
|
||||
$type = $this->request->param('type', '');
|
||||
if ($type !== '' && $type !== null) {
|
||||
$query->where('type', (int)$type);
|
||||
}
|
||||
|
||||
// 时间筛选
|
||||
$startTime = $this->request->param('startTime', '');
|
||||
$endTime = $this->request->param('endTime', '');
|
||||
if ($startTime || $endTime) {
|
||||
$start = $startTime ? strtotime($startTime) : 0;
|
||||
$end = $endTime ? strtotime($endTime) : time();
|
||||
if ($start && $end && $end < $start) {
|
||||
return ResponseHelper::error('结束时间不能早于开始时间');
|
||||
}
|
||||
$query->whereBetween('createTime', [$start ?: 0, $end ?: time()]);
|
||||
}
|
||||
|
||||
// 获取所有数据(不分页)
|
||||
$moments = $query->order('createTime', 'desc')->select();
|
||||
|
||||
if (empty($moments)) {
|
||||
return ResponseHelper::error('暂无数据可导出');
|
||||
}
|
||||
|
||||
// 定义表头
|
||||
$headers = [
|
||||
'date' => '日期',
|
||||
'postTime' => '投放时间',
|
||||
'functionCategory' => '作用分类',
|
||||
'content' => '朋友圈文案',
|
||||
'selfReply' => '自回评内容',
|
||||
'displayForm' => '朋友圈展示形式',
|
||||
'image1' => '配图1',
|
||||
'image2' => '配图2',
|
||||
'image3' => '配图3',
|
||||
'image4' => '配图4',
|
||||
'image5' => '配图5',
|
||||
'image6' => '配图6',
|
||||
'image7' => '配图7',
|
||||
'image8' => '配图8',
|
||||
'image9' => '配图9',
|
||||
];
|
||||
|
||||
// 格式化数据
|
||||
$rows = [];
|
||||
foreach ($moments as $moment) {
|
||||
$resUrls = $this->decodeJson($moment['resUrls'] ?? null);
|
||||
$imageUrls = is_array($resUrls) ? $resUrls : [];
|
||||
|
||||
// 格式化日期和时间
|
||||
$createTime = !empty($moment['createTime'])
|
||||
? (is_numeric($moment['createTime']) ? $moment['createTime'] : strtotime($moment['createTime']))
|
||||
: 0;
|
||||
$date = $createTime ? date('Y年m月d日', $createTime) : '';
|
||||
$postTime = $createTime ? date('H:i', $createTime) : '';
|
||||
|
||||
// 判断展示形式
|
||||
$displayForm = '';
|
||||
if (!empty($moment['content']) && !empty($imageUrls)) {
|
||||
$displayForm = '文字+图片';
|
||||
} elseif (!empty($moment['content'])) {
|
||||
$displayForm = '文字';
|
||||
} elseif (!empty($imageUrls)) {
|
||||
$displayForm = '图片';
|
||||
}
|
||||
|
||||
$row = [
|
||||
'date' => $date,
|
||||
'postTime' => $postTime,
|
||||
'functionCategory' => '', // 暂时放空
|
||||
'content' => $moment['content'] ?? '',
|
||||
'selfReply' => '', // 暂时放空
|
||||
'displayForm' => $displayForm,
|
||||
];
|
||||
|
||||
// 分配图片到配图1-9列
|
||||
for ($i = 1; $i <= 9; $i++) {
|
||||
$imageKey = 'image' . $i;
|
||||
$row[$imageKey] = isset($imageUrls[$i - 1]) ? $imageUrls[$i - 1] : '';
|
||||
}
|
||||
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
// 定义图片列(配图1-9)
|
||||
$imageColumns = ['image1', 'image2', 'image3', 'image4', 'image5', 'image6', 'image7', 'image8', 'image9'];
|
||||
|
||||
// 生成文件名
|
||||
$fileName = '朋友圈投放_' . date('Ymd_His');
|
||||
|
||||
// 调用导出方法,优化图片显示效果
|
||||
ExportController::exportExcelWithImages(
|
||||
$fileName,
|
||||
$headers,
|
||||
$rows,
|
||||
$imageColumns,
|
||||
'朋友圈投放',
|
||||
[
|
||||
'imageWidth' => 120, // 图片宽度(像素)
|
||||
'imageHeight' => 120, // 图片高度(像素)
|
||||
'imageColumnWidth' => 18, // 图片列宽(Excel单位)
|
||||
'rowHeight' => 130, // 行高(像素)
|
||||
'columnWidths' => [ // 特定列的固定宽度
|
||||
'date' => 15, // 日期列宽
|
||||
'postTime' => 12, // 投放时间列宽
|
||||
'functionCategory' => 15, // 作用分类列宽
|
||||
'content' => 40, // 朋友圈文案列宽(自动调整可能不够)
|
||||
'selfReply' => 30, // 自回评内容列宽
|
||||
'displayForm' => 18, // 朋友圈展示形式列宽
|
||||
],
|
||||
'titleRow' => [ // 标题行内容(第一行)
|
||||
'朋友圈投放',
|
||||
'我能提供什么价值? (40%) 有谁正在和我合作 (20%) 如何和我合作? (20%) 你找我合作需要付多少钱? (20%)'
|
||||
]
|
||||
]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('导出失败:' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化朋友圈数据
|
||||
*
|
||||
* @param array $row
|
||||
* @return array
|
||||
*/
|
||||
protected function formatMomentRow(array $row): array
|
||||
{
|
||||
$formatTime = function ($timestamp) {
|
||||
if (empty($timestamp)) {
|
||||
return '';
|
||||
}
|
||||
return is_numeric($timestamp)
|
||||
? date('Y-m-d H:i:s', $timestamp)
|
||||
: date('Y-m-d H:i:s', strtotime($timestamp));
|
||||
};
|
||||
|
||||
return [
|
||||
'id' => (int)$row['id'],
|
||||
'snsId' => $row['snsId'] ?? '',
|
||||
'type' => (int)($row['type'] ?? 0),
|
||||
'content' => $row['content'] ?? '',
|
||||
'commentList' => $this->decodeJson($row['commentList'] ?? null),
|
||||
'likeList' => $this->decodeJson($row['likeList'] ?? null),
|
||||
'resUrls' => $this->decodeJson($row['resUrls'] ?? null),
|
||||
'createTime' => $formatTime($row['createTime'] ?? null),
|
||||
'momentEntity' => [
|
||||
'lat' => $row['lat'] ?? 0,
|
||||
'lng' => $row['lng'] ?? 0,
|
||||
'location' => $row['location'] ?? '',
|
||||
'picSize' => $row['picSize'] ?? 0,
|
||||
'userName' => $row['userName'] ?? '',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON字段解析
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return array
|
||||
*/
|
||||
protected function decodeJson($value): array
|
||||
{
|
||||
if (empty($value)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$decoded = json_decode($value, true);
|
||||
return $decoded ?: [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,411 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\wechat;
|
||||
|
||||
use app\common\service\WechatAccountHealthScoreService;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 微信账号概览控制器
|
||||
* 提供账号概览页面的所有数据接口
|
||||
*/
|
||||
class GetWechatOverviewV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取微信账号概览数据
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$wechatId = $this->request->param('wechatId', '');
|
||||
|
||||
if (empty($wechatId)) {
|
||||
return ResponseHelper::error('微信ID不能为空');
|
||||
}
|
||||
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 获取微信账号ID(accountId)
|
||||
$account = Db::table('s2_wechat_account')
|
||||
->where('wechatId', $wechatId)
|
||||
->find();
|
||||
|
||||
if (empty($account)) {
|
||||
return ResponseHelper::error('微信账号不存在');
|
||||
}
|
||||
|
||||
$accountId = $account['id'];
|
||||
|
||||
// 1. 健康分评估
|
||||
$healthScoreData = $this->getHealthScoreAssessment($accountId, $wechatId);
|
||||
|
||||
// 2. 账号价值(模拟数据)
|
||||
$accountValue = $this->getAccountValue($accountId);
|
||||
|
||||
// 3. 今日价值变化(模拟数据)
|
||||
$todayValueChange = $this->getTodayValueChange($accountId);
|
||||
|
||||
// 4. 好友总数
|
||||
$totalFriends = $this->getTotalFriends($wechatId, $companyId);
|
||||
|
||||
// 5. 今日新增好友
|
||||
$todayNewFriends = $this->getTodayNewFriends($wechatId);
|
||||
|
||||
// 6. 高价群聊
|
||||
$highValueChatrooms = $this->getHighValueChatrooms($wechatId, $companyId);
|
||||
|
||||
// 7. 今日新增群聊
|
||||
$todayNewChatrooms = $this->getTodayNewChatrooms($wechatId, $companyId);
|
||||
|
||||
$result = [
|
||||
'healthScoreAssessment' => $healthScoreData,
|
||||
'accountValue' => $accountValue,
|
||||
'todayValueChange' => $todayValueChange,
|
||||
'totalFriends' => $totalFriends,
|
||||
'todayNewFriends' => $todayNewFriends,
|
||||
'highValueChatrooms' => $highValueChatrooms,
|
||||
'todayNewChatrooms' => $todayNewChatrooms,
|
||||
];
|
||||
|
||||
return ResponseHelper::success($result);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode() ?: 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取健康分评估数据
|
||||
*
|
||||
* @param int $accountId 账号ID
|
||||
* @param string $wechatId 微信ID
|
||||
* @return array
|
||||
*/
|
||||
protected function getHealthScoreAssessment($accountId, $wechatId)
|
||||
{
|
||||
// 获取健康分信息
|
||||
$healthScoreService = new WechatAccountHealthScoreService();
|
||||
$healthScoreInfo = $healthScoreService->getHealthScore($accountId);
|
||||
|
||||
$healthScore = $healthScoreInfo['healthScore'] ?? 0;
|
||||
$maxAddFriendPerDay = $healthScoreInfo['maxAddFriendPerDay'] ?? 0;
|
||||
|
||||
// 获取今日已加好友数
|
||||
$todayAdded = $this->getTodayAddedCount($wechatId);
|
||||
|
||||
// 获取最后添加时间
|
||||
$lastAddTime = $this->getLastAddTime($wechatId);
|
||||
|
||||
// 判断状态标签
|
||||
$statusTag = $todayAdded > 0 ? '已添加加人' : '';
|
||||
|
||||
// 获取基础构成
|
||||
$baseComposition = $this->getBaseComposition($healthScoreInfo);
|
||||
|
||||
// 获取动态记录
|
||||
$dynamicRecords = $this->getDynamicRecords($healthScoreInfo);
|
||||
|
||||
return [
|
||||
'score' => $healthScore,
|
||||
'dailyLimit' => $maxAddFriendPerDay,
|
||||
'todayAdded' => $todayAdded,
|
||||
'lastAddTime' => $lastAddTime,
|
||||
'statusTag' => $statusTag,
|
||||
'baseComposition' => $baseComposition,
|
||||
'dynamicRecords' => $dynamicRecords,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取基础构成数据
|
||||
*
|
||||
* @param array $healthScoreInfo 健康分信息
|
||||
* @return array
|
||||
*/
|
||||
protected function getBaseComposition($healthScoreInfo)
|
||||
{
|
||||
$baseScore = $healthScoreInfo['baseScore'] ?? 0;
|
||||
$baseInfoScore = $healthScoreInfo['baseInfoScore'] ?? 0;
|
||||
$friendCountScore = $healthScoreInfo['friendCountScore'] ?? 0;
|
||||
$friendCount = $healthScoreInfo['friendCount'] ?? 0;
|
||||
|
||||
// 账号基础分(默认60分)
|
||||
$accountBaseScore = 60;
|
||||
|
||||
// 已修改微信号(如果baseInfoScore > 0,说明已修改)
|
||||
$isModifiedAlias = $baseInfoScore > 0;
|
||||
|
||||
$composition = [
|
||||
[
|
||||
'name' => '账号基础分',
|
||||
'score' => $accountBaseScore,
|
||||
'formatted' => '+' . $accountBaseScore,
|
||||
]
|
||||
];
|
||||
|
||||
// 如果已修改微信号,添加基础信息分
|
||||
if ($isModifiedAlias) {
|
||||
$composition[] = [
|
||||
'name' => '已修改微信号',
|
||||
'score' => $baseInfoScore,
|
||||
'formatted' => '+' . $baseInfoScore,
|
||||
];
|
||||
}
|
||||
|
||||
// 好友数量加成
|
||||
if ($friendCountScore > 0) {
|
||||
$composition[] = [
|
||||
'name' => '好友数量加成',
|
||||
'score' => $friendCountScore,
|
||||
'formatted' => '+' . $friendCountScore,
|
||||
'friendCount' => $friendCount, // 显示好友总数
|
||||
];
|
||||
}
|
||||
|
||||
return $composition;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取动态记录数据
|
||||
*
|
||||
* @param array $healthScoreInfo 健康分信息
|
||||
* @return array
|
||||
*/
|
||||
protected function getDynamicRecords($healthScoreInfo)
|
||||
{
|
||||
$records = [];
|
||||
|
||||
$frequentPenalty = $healthScoreInfo['frequentPenalty'] ?? 0;
|
||||
$frequentCount = $healthScoreInfo['frequentCount'] ?? 0;
|
||||
$banPenalty = $healthScoreInfo['banPenalty'] ?? 0;
|
||||
$isBanned = $healthScoreInfo['isBanned'] ?? 0;
|
||||
$noFrequentBonus = $healthScoreInfo['noFrequentBonus'] ?? 0;
|
||||
$consecutiveNoFrequentDays = $healthScoreInfo['consecutiveNoFrequentDays'] ?? 0;
|
||||
$lastFrequentTime = $healthScoreInfo['lastFrequentTime'] ?? null;
|
||||
|
||||
// 频繁扣分记录
|
||||
// 根据frequentCount判断是首次还是再次
|
||||
// frequentPenalty存储的是当前状态的扣分(-15或-25),不是累计值
|
||||
if ($frequentCount > 0 && $frequentPenalty < 0) {
|
||||
if ($frequentCount == 1) {
|
||||
// 首次频繁:-15分
|
||||
$records[] = [
|
||||
'name' => '首次触发限额',
|
||||
'score' => $frequentPenalty,
|
||||
'formatted' => (string)$frequentPenalty,
|
||||
'type' => 'penalty',
|
||||
'time' => $lastFrequentTime ? date('Y-m-d H:i:s', $lastFrequentTime) : null,
|
||||
];
|
||||
} else {
|
||||
// 再次频繁:-25分
|
||||
$records[] = [
|
||||
'name' => '再次触发限额',
|
||||
'score' => $frequentPenalty,
|
||||
'formatted' => (string)$frequentPenalty,
|
||||
'type' => 'penalty',
|
||||
'time' => $lastFrequentTime ? date('Y-m-d H:i:s', $lastFrequentTime) : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 封号扣分记录
|
||||
if ($isBanned && $banPenalty < 0) {
|
||||
$lastBanTime = $healthScoreInfo['lastBanTime'] ?? null;
|
||||
$records[] = [
|
||||
'name' => '封号',
|
||||
'score' => $banPenalty,
|
||||
'formatted' => (string)$banPenalty,
|
||||
'type' => 'penalty',
|
||||
'time' => $lastBanTime ? date('Y-m-d H:i:s', $lastBanTime) : null,
|
||||
];
|
||||
}
|
||||
|
||||
// 不频繁加分记录
|
||||
if ($noFrequentBonus > 0 && $consecutiveNoFrequentDays >= 3) {
|
||||
$lastNoFrequentTime = $healthScoreInfo['lastNoFrequentTime'] ?? null;
|
||||
$records[] = [
|
||||
'name' => '连续' . $consecutiveNoFrequentDays . '天不触发频繁',
|
||||
'score' => $noFrequentBonus,
|
||||
'formatted' => '+' . $noFrequentBonus,
|
||||
'type' => 'bonus',
|
||||
'time' => $lastNoFrequentTime ? date('Y-m-d H:i:s', $lastNoFrequentTime) : null,
|
||||
];
|
||||
}
|
||||
|
||||
return $records;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今日已加好友数
|
||||
*
|
||||
* @param string $wechatId 微信ID
|
||||
* @return int
|
||||
*/
|
||||
protected function getTodayAddedCount($wechatId)
|
||||
{
|
||||
$start = strtotime(date('Y-m-d 00:00:00'));
|
||||
$end = strtotime(date('Y-m-d 23:59:59'));
|
||||
|
||||
return Db::table('s2_friend_task')
|
||||
->where('wechatId', $wechatId)
|
||||
->whereBetween('createTime', [$start, $end])
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最后添加时间
|
||||
*
|
||||
* @param string $wechatId 微信ID
|
||||
* @return string
|
||||
*/
|
||||
protected function getLastAddTime($wechatId)
|
||||
{
|
||||
$lastTask = Db::table('s2_friend_task')
|
||||
->where('wechatId', $wechatId)
|
||||
->order('createTime', 'desc')
|
||||
->find();
|
||||
|
||||
if (empty($lastTask) || empty($lastTask['createTime'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return date('H:i:s', $lastTask['createTime']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取账号价值(模拟数据)
|
||||
*
|
||||
* @param int $accountId 账号ID
|
||||
* @return array
|
||||
*/
|
||||
protected function getAccountValue($accountId)
|
||||
{
|
||||
// TODO: 后续替换为真实计算逻辑
|
||||
// 模拟数据:¥29,800
|
||||
$value = 29800;
|
||||
|
||||
return [
|
||||
'value' => $value,
|
||||
'formatted' => '¥' . number_format($value, 0, '.', ','),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今日价值变化(模拟数据)
|
||||
*
|
||||
* @param int $accountId 账号ID
|
||||
* @return array
|
||||
*/
|
||||
protected function getTodayValueChange($accountId)
|
||||
{
|
||||
// TODO: 后续替换为真实计算逻辑
|
||||
// 模拟数据:+500
|
||||
$change = 500;
|
||||
|
||||
return [
|
||||
'change' => $change,
|
||||
'formatted' => $change > 0 ? '+' . $change : (string)$change,
|
||||
'isPositive' => $change > 0,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取好友总数
|
||||
*
|
||||
* @param string $wechatId 微信ID
|
||||
* @param int $companyId 公司ID
|
||||
* @return int
|
||||
*/
|
||||
protected function getTotalFriends($wechatId, $companyId)
|
||||
{
|
||||
// 优先从 s2_wechat_account 表获取
|
||||
$account = Db::table('s2_wechat_account')
|
||||
->where('wechatId', $wechatId)
|
||||
->field('totalFriend')
|
||||
->find();
|
||||
|
||||
if (!empty($account) && isset($account['totalFriend'])) {
|
||||
return (int)$account['totalFriend'];
|
||||
}
|
||||
|
||||
// 如果 totalFriend 为空,则从 s2_wechat_friend 表统计
|
||||
return Db::table('s2_wechat_friend')
|
||||
->where('ownerWechatId', $wechatId)
|
||||
->where('isDeleted', 0)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今日新增好友数
|
||||
*
|
||||
* @param string $wechatId 微信ID
|
||||
* @return int
|
||||
*/
|
||||
protected function getTodayNewFriends($wechatId)
|
||||
{
|
||||
$start = strtotime(date('Y-m-d 00:00:00'));
|
||||
$end = strtotime(date('Y-m-d 23:59:59'));
|
||||
|
||||
// 从 s2_wechat_friend 表统计今日新增
|
||||
return Db::table('s2_wechat_friend')
|
||||
->where('ownerWechatId', $wechatId)
|
||||
->whereBetween('createTime', [$start, $end])
|
||||
->where('isDeleted', 0)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取高价群聊数量
|
||||
* 高价群聊定义:群成员数 >= 50 的群聊
|
||||
*
|
||||
* @param string $wechatId 微信ID
|
||||
* @param int $companyId 公司ID
|
||||
* @return int
|
||||
*/
|
||||
protected function getHighValueChatrooms($wechatId, $companyId)
|
||||
{
|
||||
// 高价群聊定义:群成员数 >= 50
|
||||
$minMemberCount = 50;
|
||||
|
||||
// 查询该微信账号下的高价群聊
|
||||
// 使用子查询统计每个群的成员数
|
||||
$result = Db::query("
|
||||
SELECT COUNT(DISTINCT c.chatroomId) as count
|
||||
FROM s2_wechat_chatroom c
|
||||
INNER JOIN (
|
||||
SELECT chatroomId, COUNT(*) as memberCount
|
||||
FROM s2_wechat_chatroom_member
|
||||
GROUP BY chatroomId
|
||||
HAVING memberCount >= ?
|
||||
) m ON c.chatroomId = m.chatroomId
|
||||
WHERE c.wechatAccountWechatId = ?
|
||||
AND c.isDeleted = 0
|
||||
", [$minMemberCount, $wechatId]);
|
||||
|
||||
return !empty($result) ? (int)$result[0]['count'] : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今日新增群聊数
|
||||
*
|
||||
* @param string $wechatId 微信ID
|
||||
* @param int $companyId 公司ID
|
||||
* @return int
|
||||
*/
|
||||
protected function getTodayNewChatrooms($wechatId, $companyId)
|
||||
{
|
||||
$start = strtotime(date('Y-m-d 00:00:00'));
|
||||
$end = strtotime(date('Y-m-d 23:59:59'));
|
||||
|
||||
return Db::table('s2_wechat_chatroom')
|
||||
->where('wechatAccountWechatId', $wechatId)
|
||||
->whereBetween('createTime', [$start, $end])
|
||||
->where('isDeleted', 0)
|
||||
->count();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,14 +8,25 @@ use app\common\model\DeviceUser as DeviceUserModel;
|
||||
use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel;
|
||||
use app\common\model\User as UserModel;
|
||||
use app\common\model\WechatAccount as WechatAccountModel;
|
||||
use app\common\model\WechatCustomer as WechatCustomerModel;
|
||||
use app\common\model\WechatFriendShip as WechatFriendShipModel;
|
||||
// 不再使用WechatFriendShipModel和WechatCustomerModel,改为直接查询s2_wechat_friend和s2_wechat_account_score表
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 微信控制器
|
||||
*
|
||||
* 性能优化建议:
|
||||
* 1. 为以下字段添加索引以提高查询性能:
|
||||
* - device_wechat_login表: (companyId, wechatId), (deviceId)
|
||||
* - wechat_account表: (wechatId)
|
||||
* - wechat_customer表: (companyId, wechatId)
|
||||
* - wechat_friend_ship表: (ownerWechatId), (createTime)
|
||||
* - s2_wechat_message表: (wechatAccountId, wechatTime)
|
||||
*
|
||||
* 2. 考虑创建以下复合索引:
|
||||
* - device_wechat_login表: (companyId, deviceId, wechatId)
|
||||
* - wechat_friend_ship表: (ownerWechatId, createTime)
|
||||
*/
|
||||
class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
{
|
||||
@@ -66,6 +77,7 @@ class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
|
||||
/**
|
||||
* 获取有登录设备的微信id
|
||||
* 优化:使用索引字段,减少数据查询量
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
@@ -76,12 +88,12 @@ class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
throw new \Exception('暂无设备数据', 200);
|
||||
}
|
||||
|
||||
return DeviceWechatLoginModel::where(
|
||||
[
|
||||
// 优化:直接使用DISTINCT减少数据传输量
|
||||
return DeviceWechatLoginModel::distinct(true)
|
||||
->where([
|
||||
'companyId' => $this->getUserInfo('companyId'),
|
||||
// 'alive' => DeviceWechatLoginModel::ALIVE_WECHAT_ACTIVE,
|
||||
]
|
||||
)
|
||||
])
|
||||
->where('deviceId', 'in', $deviceIds)
|
||||
->column('wechatId');
|
||||
}
|
||||
@@ -110,24 +122,50 @@ class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
|
||||
/**
|
||||
* 获取在线微信账号列表
|
||||
* 优化:减少查询字段,使用索引,优化JOIN条件
|
||||
*
|
||||
* @param array $where
|
||||
* @return \think\Paginator 分页对象
|
||||
*/
|
||||
protected function getOnlineWechatList(array $where): \think\Paginator
|
||||
{
|
||||
// 获取微信在线状态筛选参数(1=在线,0=离线,不传=全部)
|
||||
$wechatStatus = $this->request->param('wechatStatus');
|
||||
|
||||
// 优化:只查询必要字段,使用FORCE INDEX提示数据库使用索引
|
||||
$query = WechatAccountModel::alias('w')
|
||||
->field(
|
||||
[
|
||||
'w.id', 'w.nickname', 'w.avatar', 'w.wechatId',
|
||||
'CASE WHEN w.alias IS NULL OR w.alias = "" THEN w.wechatId ELSE w.alias END AS wechatAccount',
|
||||
'l.deviceId','l.alive'
|
||||
'MAX(l.deviceId) as deviceId', 'MAX(l.alive) as alive' // 使用MAX确保GROUP BY时获取正确的在线状态
|
||||
]
|
||||
)
|
||||
->join('device_wechat_login l', 'w.wechatId = l.wechatId AND l.companyId = '. $this->getUserInfo('companyId'))
|
||||
->order('w.id desc')
|
||||
->group('w.wechatId');
|
||||
// 优化:使用INNER JOIN代替LEFT JOIN,并添加索引提示
|
||||
->join('device_wechat_login l', 'w.wechatId = l.wechatId AND l.companyId = '. $this->getUserInfo('companyId'), 'INNER')
|
||||
// 添加s2_wechat_account表的LEFT JOIN,用于筛选微信在线状态
|
||||
->join(['s2_wechat_account' => 'sa'], 'w.wechatId = sa.wechatId', 'LEFT')
|
||||
->group('w.wechatId')
|
||||
// 优化:在线状态优先排序(alive=1的排在前面),然后按wechatId排序
|
||||
// 注意:ORDER BY使用SELECT中定义的别名alive,而不是聚合函数
|
||||
->order('alive desc, w.wechatId desc');
|
||||
|
||||
// 根据wechatStatus参数筛选(1=在线,0=离线,不传=全部)
|
||||
if ($wechatStatus !== null && $wechatStatus !== '') {
|
||||
$wechatStatus = (int)$wechatStatus;
|
||||
if ($wechatStatus === 1) {
|
||||
// 筛选在线:wechatAlive = 1
|
||||
$query->where('sa.wechatAlive', 1);
|
||||
} elseif ($wechatStatus === 0) {
|
||||
// 筛选离线:wechatAlive = 0 或 NULL
|
||||
$query->where(function($query) {
|
||||
$query->where('sa.wechatAlive', 0)
|
||||
->whereOr('sa.wechatAlive', 'exp', 'IS NULL');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 应用查询条件
|
||||
foreach ($where as $key => $value) {
|
||||
if (is_numeric($key) && is_array($value) && isset($value[0]) && $value[0] === 'exp') {
|
||||
$query->whereExp('', $value[1]);
|
||||
@@ -142,7 +180,12 @@ class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
$query->where($key, $value);
|
||||
}
|
||||
|
||||
return $query->paginate($this->request->param('limit/d', 10), false, ['page' => $this->request->param('page/d', 1)]);
|
||||
// 优化:使用简单计数查询
|
||||
return $query->paginate(
|
||||
$this->request->param('limit/d', 10),
|
||||
false,
|
||||
['page' => $this->request->param('page/d', 1)]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,9 +210,15 @@ class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
$metrics = $this->collectWechatMetrics($wechatIds);
|
||||
|
||||
foreach ($items as $item) {
|
||||
$addLimit = $metrics['addLimit'][$item->wechatId] ?? 0;
|
||||
$todayAdded = $metrics['todayAdded'][$item->wechatId] ?? 0;
|
||||
// 计算今日可添加数量 = 可添加额度 - 今日已添加
|
||||
$todayCanAdd = max(0, $addLimit - $todayAdded);
|
||||
|
||||
$sections = $item->toArray() + [
|
||||
'times' => $metrics['addLimit'][$item->wechatId] ?? 0,
|
||||
'addedCount' => $metrics['todayAdded'][$item->wechatId] ?? 0,
|
||||
'times' => $addLimit,
|
||||
'addedCount' => $todayAdded,
|
||||
'todayCanAdd' => $todayCanAdd, // 今日可添加数量
|
||||
'wechatStatus' => $metrics['wechatStatus'][$item->wechatId] ?? 0,
|
||||
'totalFriend' => $metrics['totalFriend'][$item->wechatId] ?? 0,
|
||||
'deviceMemo' => $metrics['deviceMemo'][$item->wechatId] ?? '',
|
||||
@@ -184,6 +233,8 @@ class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
|
||||
/**
|
||||
* 批量收集微信账号的统计信息
|
||||
* 优化:合并查询,减少数据库访问次数,使用缓存
|
||||
*
|
||||
* @param array $wechatIds
|
||||
* @return array
|
||||
*/
|
||||
@@ -203,106 +254,167 @@ class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
}
|
||||
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 可添加好友额度
|
||||
$weightRows = WechatCustomerModel::where('companyId', $companyId)
|
||||
|
||||
// 使用缓存键,避免短时间内重复查询
|
||||
$cacheKey = 'wechat_metrics_' . md5(implode(',', $wechatIds) . '_' . $companyId);
|
||||
|
||||
// 尝试从缓存获取数据(缓存5分钟)
|
||||
$cachedMetrics = cache($cacheKey);
|
||||
if ($cachedMetrics) {
|
||||
return $cachedMetrics;
|
||||
}
|
||||
|
||||
// 优化1:可添加好友额度 - 从s2_wechat_account_score表获取maxAddFriendPerDay
|
||||
$scoreRows = Db::table('s2_wechat_account_score')
|
||||
->whereIn('wechatId', $wechatIds)
|
||||
->column('weight', 'wechatId');
|
||||
foreach ($weightRows as $wechatId => $weight) {
|
||||
$decoded = json_decode($weight, true);
|
||||
$metrics['addLimit'][$wechatId] = $decoded['addLimit'] ?? 0;
|
||||
->column('maxAddFriendPerDay', 'wechatId');
|
||||
foreach ($scoreRows as $wechatId => $maxAddFriendPerDay) {
|
||||
$metrics['addLimit'][$wechatId] = (int)($maxAddFriendPerDay ?? 0);
|
||||
}
|
||||
|
||||
// 今日新增好友
|
||||
// 优化2:今日新增好友 - 使用索引字段和预计算
|
||||
$start = strtotime(date('Y-m-d 00:00:00'));
|
||||
$end = strtotime(date('Y-m-d 23:59:59'));
|
||||
$todayRows = WechatFriendShipModel::whereIn('ownerWechatId', $wechatIds)
|
||||
->whereBetween('createTime', [$start, $end])
|
||||
->field('ownerWechatId, COUNT(*) as total')
|
||||
->group('ownerWechatId')
|
||||
->select();
|
||||
foreach ($todayRows as $row) {
|
||||
$wechatId = is_array($row) ? ($row['ownerWechatId'] ?? '') : ($row->ownerWechatId ?? '');
|
||||
|
||||
// 使用单次查询获取所有wechatIds的今日新增和总好友数
|
||||
// 根据数据库结构使用s2_wechat_friend表而不是wechat_friend_ship
|
||||
$friendshipStats = Db::query("
|
||||
SELECT
|
||||
ownerWechatId,
|
||||
SUM(IF(createTime BETWEEN {$start} AND {$end}, 1, 0)) as today_added,
|
||||
COUNT(*) as total_friend
|
||||
FROM
|
||||
s2_wechat_friend
|
||||
WHERE
|
||||
ownerWechatId IN ('" . implode("','", $wechatIds) . "')
|
||||
AND isDeleted = 0
|
||||
GROUP BY
|
||||
ownerWechatId
|
||||
");
|
||||
|
||||
// 处理结果
|
||||
foreach ($friendshipStats as $row) {
|
||||
$wechatId = $row['ownerWechatId'] ?? '';
|
||||
if ($wechatId) {
|
||||
$metrics['todayAdded'][$wechatId] = (int)(is_array($row) ? ($row['total'] ?? 0) : ($row->total ?? 0));
|
||||
$metrics['todayAdded'][$wechatId] = (int)($row['today_added'] ?? 0);
|
||||
$metrics['totalFriend'][$wechatId] = (int)($row['total_friend'] ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 总好友
|
||||
$friendRows = WechatFriendShipModel::whereIn('ownerWechatId', $wechatIds)
|
||||
->field('ownerWechatId, COUNT(*) as total')
|
||||
->group('ownerWechatId')
|
||||
// 优化3:微信在线状态 - 从s2_wechat_account表获取wechatAlive
|
||||
$wechatAccountRows = Db::table('s2_wechat_account')
|
||||
->whereIn('wechatId', $wechatIds)
|
||||
->field('wechatId, wechatAlive')
|
||||
->select();
|
||||
foreach ($friendRows as $row) {
|
||||
$wechatId = is_array($row) ? ($row['ownerWechatId'] ?? '') : ($row->ownerWechatId ?? '');
|
||||
if ($wechatId) {
|
||||
$metrics['totalFriend'][$wechatId] = (int)(is_array($row) ? ($row['total'] ?? 0) : ($row->total ?? 0));
|
||||
|
||||
foreach ($wechatAccountRows as $row) {
|
||||
$wechatId = $row['wechatId'] ?? '';
|
||||
if (!empty($wechatId)) {
|
||||
$metrics['wechatStatus'][$wechatId] = (int)($row['wechatAlive'] ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 设备状态与备注
|
||||
// 优化4:设备状态与备注 - 使用INNER JOIN和索引
|
||||
$loginRows = Db::name('device_wechat_login')
|
||||
->alias('l')
|
||||
->leftJoin('device d', 'd.id = l.deviceId')
|
||||
->field('l.wechatId,l.alive,d.memo')
|
||||
->join('device d', 'd.id = l.deviceId', 'LEFT')
|
||||
->field('l.wechatId, l.alive, d.memo')
|
||||
->where('l.companyId', $companyId)
|
||||
->whereIn('l.wechatId', $wechatIds)
|
||||
->order('l.id', 'desc')
|
||||
->select();
|
||||
|
||||
// 使用临时数组避免重复处理
|
||||
$processedWechatIds = [];
|
||||
foreach ($loginRows as $row) {
|
||||
$wechatId = is_array($row) ? ($row['wechatId'] ?? '') : ($row->wechatId ?? '');
|
||||
if (empty($wechatId) || isset($metrics['wechatStatus'][$wechatId])) {
|
||||
continue;
|
||||
$wechatId = $row['wechatId'] ?? '';
|
||||
// 只处理每个wechatId的第一条记录(最新的)
|
||||
if (!empty($wechatId) && !in_array($wechatId, $processedWechatIds)) {
|
||||
// 如果s2_wechat_account表中没有wechatAlive,则使用device_wechat_login的alive作为备用
|
||||
if (!isset($metrics['wechatStatus'][$wechatId])) {
|
||||
$metrics['wechatStatus'][$wechatId] = (int)($row['alive'] ?? 0);
|
||||
}
|
||||
$metrics['deviceMemo'][$wechatId] = $row['memo'] ?? '';
|
||||
$processedWechatIds[] = $wechatId;
|
||||
}
|
||||
$metrics['wechatStatus'][$wechatId] = (int)(is_array($row) ? ($row['alive'] ?? 0) : ($row->alive ?? 0));
|
||||
$metrics['deviceMemo'][$wechatId] = is_array($row) ? ($row['memo'] ?? '') : ($row->memo ?? '');
|
||||
}
|
||||
|
||||
// 活跃时间
|
||||
$accountMap = Db::table('s2_wechat_account')
|
||||
->whereIn('wechatId', $wechatIds)
|
||||
->column('id', 'wechatId');
|
||||
if (!empty($accountMap)) {
|
||||
$accountRows = Db::table('s2_wechat_message')
|
||||
->whereIn('wechatAccountId', array_values($accountMap))
|
||||
->field('wechatAccountId, MAX(wechatTime) as lastTime')
|
||||
->group('wechatAccountId')
|
||||
->select();
|
||||
$accountLastTime = [];
|
||||
foreach ($accountRows as $row) {
|
||||
$accountId = is_array($row) ? ($row['wechatAccountId'] ?? 0) : ($row->wechatAccountId ?? 0);
|
||||
if ($accountId) {
|
||||
$accountLastTime[$accountId] = (int)(is_array($row) ? ($row['lastTime'] ?? 0) : ($row->lastTime ?? 0));
|
||||
}
|
||||
}
|
||||
foreach ($accountMap as $wechatId => $accountId) {
|
||||
if (isset($accountLastTime[$accountId]) && $accountLastTime[$accountId] > 0) {
|
||||
$metrics['activeTime'][$wechatId] = date('Y-m-d H:i:s', $accountLastTime[$accountId]);
|
||||
}
|
||||
// 优化5:活跃时间 - 使用JOIN减少查询次数
|
||||
$activeTimeResults = Db::query("
|
||||
SELECT
|
||||
a.wechatId,
|
||||
MAX(m.wechatTime) as lastTime
|
||||
FROM
|
||||
s2_wechat_account a
|
||||
LEFT JOIN
|
||||
s2_wechat_message m ON a.id = m.wechatAccountId
|
||||
WHERE
|
||||
a.wechatId IN ('" . implode("','", $wechatIds) . "')
|
||||
GROUP BY
|
||||
a.wechatId
|
||||
");
|
||||
|
||||
foreach ($activeTimeResults as $row) {
|
||||
$wechatId = $row['wechatId'] ?? '';
|
||||
$lastTime = (int)($row['lastTime'] ?? 0);
|
||||
if (!empty($wechatId) && $lastTime > 0) {
|
||||
$metrics['activeTime'][$wechatId] = date('Y-m-d H:i:s', $lastTime);
|
||||
} else {
|
||||
$metrics['activeTime'][$wechatId] = '-';
|
||||
}
|
||||
}
|
||||
|
||||
// 确保所有wechatId都有wechatStatus值(默认0)
|
||||
foreach ($wechatIds as $wechatId) {
|
||||
if (!isset($metrics['wechatStatus'][$wechatId])) {
|
||||
$metrics['wechatStatus'][$wechatId] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 存入缓存,有效期5分钟
|
||||
cache($cacheKey, $metrics, 300);
|
||||
|
||||
return $metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在线微信账号列表
|
||||
* 优化:添加缓存,优化分页逻辑
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
// 获取分页参数
|
||||
$page = $this->request->param('page/d', 1);
|
||||
$limit = $this->request->param('limit/d', 10);
|
||||
$keyword = $this->request->param('keyword');
|
||||
$wechatStatus = $this->request->param('wechatStatus');
|
||||
|
||||
// 创建缓存键(基于用户、分页、搜索条件和在线状态筛选)
|
||||
$cacheKey = 'wechat_list_' . $this->getUserInfo('id') . '_' . $page . '_' . $limit . '_' . md5($keyword ?? '') . '_' . ($wechatStatus ?? 'all');
|
||||
|
||||
// 尝试从缓存获取数据(缓存2分钟)
|
||||
$cachedData = cache($cacheKey);
|
||||
if ($cachedData) {
|
||||
return ResponseHelper::success($cachedData);
|
||||
}
|
||||
|
||||
// 如果没有缓存,执行查询
|
||||
$result = $this->getOnlineWechatList(
|
||||
$this->makeWhere()
|
||||
);
|
||||
|
||||
$responseData = [
|
||||
'list' => $this->makeResultedSet($result),
|
||||
'total' => $result->total(),
|
||||
];
|
||||
|
||||
// 存入缓存,有效期2分钟
|
||||
cache($cacheKey, $responseData, 120);
|
||||
|
||||
return ResponseHelper::success(
|
||||
[
|
||||
'list' => $this->makeResultedSet($result),
|
||||
'total' => $result->total(),
|
||||
]
|
||||
);
|
||||
return ResponseHelper::success($responseData);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode());
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ class PostTransferFriends extends BaseController
|
||||
|
||||
$taskId = Db::name('customer_acquisition_task')->insertGetId([
|
||||
'name' => '迁移好友('. $wechat['nickname'] .')',
|
||||
'sceneId' => 1,
|
||||
'sceneId' => 10,
|
||||
'sceneConf' => json_encode($sceneConf),
|
||||
'reqConf' => json_encode($reqConf),
|
||||
'tagConf' => json_encode([]),
|
||||
|
||||
49
Server/application/http/middleware/Jwt.php
Normal file
49
Server/application/http/middleware/Jwt.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
namespace app\http\middleware;
|
||||
|
||||
use app\common\util\JwtUtil;
|
||||
use think\facade\Log;
|
||||
|
||||
/**
|
||||
* JWT认证中间件
|
||||
*/
|
||||
class Jwt
|
||||
{
|
||||
/**
|
||||
* 处理请求
|
||||
* @param \think\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, \Closure $next)
|
||||
{
|
||||
// 获取Token
|
||||
$token = JwtUtil::getRequestToken();
|
||||
|
||||
// 验证Token
|
||||
if (!$token) {
|
||||
return json([
|
||||
'code' => 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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -23,7 +23,8 @@ return [
|
||||
|
||||
// 全局中间件
|
||||
'alias' => [
|
||||
'cors' => 'app\\common\\middleware\\AllowCrossDomain'
|
||||
'cors' => 'app\\common\\middleware\\AllowCrossDomain',
|
||||
'jwt' => 'app\\http\\middleware\\Jwt'
|
||||
],
|
||||
|
||||
// 应用中间件
|
||||
|
||||
@@ -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
|
||||
```
|
||||
125
Server/extend/Eison/Utils/Helper/ArrHelper.php
Normal file
125
Server/extend/Eison/Utils/Helper/ArrHelper.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace Eison\Utils\Helper;
|
||||
|
||||
/**
|
||||
* 数组辅助类
|
||||
*/
|
||||
class ArrHelper
|
||||
{
|
||||
/**
|
||||
* 从数组中提取指定的键值
|
||||
*
|
||||
* @param string $keys 键名,多个用逗号分隔,支持键名映射(如:account=userName)
|
||||
* @param array $array 源数组
|
||||
* @param mixed $default 默认值,如果键不存在时返回此值
|
||||
* @return array
|
||||
*/
|
||||
public static function getValue(string $keys, array $array, $default = null): array
|
||||
{
|
||||
$result = [];
|
||||
$keyList = explode(',', $keys);
|
||||
|
||||
foreach ($keyList as $key) {
|
||||
$key = trim($key);
|
||||
|
||||
// 支持键名映射:account=userName
|
||||
if (strpos($key, '=') !== false) {
|
||||
list($sourceKey, $targetKey) = explode('=', $key, 2);
|
||||
$sourceKey = trim($sourceKey);
|
||||
$targetKey = trim($targetKey);
|
||||
|
||||
// 如果源键存在,使用源键的值;否则使用目标键的值;都不存在则使用默认值
|
||||
if (isset($array[$sourceKey])) {
|
||||
$result[$targetKey] = $array[$sourceKey];
|
||||
} elseif (isset($array[$targetKey])) {
|
||||
$result[$targetKey] = $array[$targetKey];
|
||||
} else {
|
||||
// 如果提供了默认值,使用默认值;否则不添加该键
|
||||
if ($default !== null) {
|
||||
$result[$targetKey] = $default;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 普通键名
|
||||
if (isset($array[$key])) {
|
||||
$result[$key] = $array[$key];
|
||||
} else {
|
||||
// 如果提供了默认值,使用默认值;否则不添加该键
|
||||
if ($default !== null) {
|
||||
$result[$key] = $default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除数组中的空值(null、空字符串、空数组)
|
||||
*
|
||||
* @param array $array 源数组
|
||||
* @return array
|
||||
*/
|
||||
public static function rmValue(array $array): array
|
||||
{
|
||||
return array_filter($array, function($value) {
|
||||
if (is_array($value)) {
|
||||
return !empty($value);
|
||||
}
|
||||
return $value !== null && $value !== '';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 左连接两个数组
|
||||
*
|
||||
* @param array $leftArray 左数组
|
||||
* @param array $rightArray 右数组
|
||||
* @param string $key 关联键名
|
||||
* @return array
|
||||
*/
|
||||
public static function leftJoin(array $leftArray, array $rightArray, string $key): array
|
||||
{
|
||||
// 将右数组按关联键索引
|
||||
$rightIndexed = [];
|
||||
foreach ($rightArray as $item) {
|
||||
if (isset($item[$key])) {
|
||||
$rightIndexed[$item[$key]] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
// 左连接
|
||||
$result = [];
|
||||
foreach ($leftArray as $leftItem) {
|
||||
$leftKeyValue = $leftItem[$key] ?? null;
|
||||
if ($leftKeyValue !== null && isset($rightIndexed[$leftKeyValue])) {
|
||||
$result[] = array_merge($leftItem, $rightIndexed[$leftKeyValue]);
|
||||
} else {
|
||||
$result[] = $leftItem;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数组的某一列作为键,重新组织数组
|
||||
*
|
||||
* @param string $key 作为键的列名
|
||||
* @param array $array 源数组
|
||||
* @return array
|
||||
*/
|
||||
public static function columnTokey(string $key, array $array): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($array as $item) {
|
||||
if (isset($item[$key])) {
|
||||
$result[$item[$key]] = $item;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ use think\facade\Config;
|
||||
use think\facade\Log;
|
||||
use app\api\controller\FriendTaskController;
|
||||
use app\common\service\AuthService;
|
||||
use app\common\service\WechatAccountHealthScoreService;
|
||||
use app\api\controller\WebSocketController;
|
||||
use Workerman\Lib\Timer;
|
||||
|
||||
@@ -180,13 +181,11 @@ class Adapter implements WeChatServiceInterface
|
||||
->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;
|
||||
}
|
||||
|
||||
@@ -231,7 +307,7 @@ class Adapter implements WeChatServiceInterface
|
||||
$conf = array_merge($task_info['reqConf'], ['task_name' => $task_info['name'], 'tags' => $tags]);
|
||||
|
||||
|
||||
$this->createFriendAddTask($accountId, $task['phone'], $conf);
|
||||
$this->createFriendAddTask($accountId, $task['phone'], $conf, $task['remark']);
|
||||
$friendAddTaskCreated = true;
|
||||
$task['processed_wechat_ids'] = $task['processed_wechat_ids'] . ',' . $wechatId; // 处理失败任务用,用于过滤已处理的微信号
|
||||
break;
|
||||
@@ -828,6 +904,7 @@ class Adapter implements WeChatServiceInterface
|
||||
if (empty($deviceIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$records = Db::table('s2_wechat_account')
|
||||
->where('deviceAlive', 1)
|
||||
->where('wechatAlive', 1)
|
||||
@@ -874,28 +951,29 @@ class Adapter implements WeChatServiceInterface
|
||||
}
|
||||
|
||||
// 创建添加好友任务/执行添加
|
||||
public function createFriendAddTask(int $wechatAccountId, string $phone, array $conf)
|
||||
public function createFriendAddTask(int $wechatAccountId, string $phone, array $conf, $remark = '')
|
||||
{
|
||||
if (empty($wechatAccountId) || empty($phone) || empty($conf)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($conf['remarkType']) {
|
||||
case 'phone':
|
||||
$remark = $phone . '-' . $conf['task_name'];
|
||||
break;
|
||||
case 'nickname':
|
||||
$remark = '';
|
||||
break;
|
||||
case 'source':
|
||||
$remark = $conf['task_name'];
|
||||
break;
|
||||
default:
|
||||
$remark = '';
|
||||
break;
|
||||
if (empty($remark)){
|
||||
switch ($conf['remarkType']) {
|
||||
case 'phone':
|
||||
$remark = $phone . '-' . $conf['task_name'];
|
||||
break;
|
||||
case 'nickname':
|
||||
$remark = '';
|
||||
break;
|
||||
case 'source':
|
||||
$remark = $conf['task_name'];
|
||||
break;
|
||||
default:
|
||||
$remark = '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$tags = [];
|
||||
if (!empty($conf['tags'])) {
|
||||
if (is_array($conf['tags'])) {
|
||||
|
||||
2392
Server/sql.sql
Normal file
2392
Server/sql.sql
Normal file
File diff suppressed because it is too large
Load Diff
58
Server/微信健康分规则v2.md
Normal file
58
Server/微信健康分规则v2.md
Normal file
@@ -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/日
|
||||
@@ -11,6 +11,10 @@
|
||||
</style>
|
||||
<!-- 引入 uni-app web-view SDK(必须) -->
|
||||
<script type="text/javascript" src="/websdk.js"></script>
|
||||
<script
|
||||
charset="utf-8"
|
||||
src="https://map.qq.com/api/gljs?v=1.exp&libraries=service&key=7DZBZ-ZSRK3-QJN3W-O5VTV-4E2P6-7GFYX"
|
||||
></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -18,11 +18,11 @@ export interface DataProcessingParams {
|
||||
/**
|
||||
* 群消息
|
||||
*/
|
||||
chatroomMessage?: string[];
|
||||
chatroomMessage?: any[];
|
||||
/**
|
||||
* 个人信息
|
||||
*/
|
||||
friendMessage?: string[];
|
||||
friendMessage?: any[];
|
||||
/**
|
||||
* 类型固定值
|
||||
*/
|
||||
@@ -30,7 +30,7 @@ export interface DataProcessingParams {
|
||||
/**
|
||||
* 公共
|
||||
*/
|
||||
wechatAccountId?: string;
|
||||
wechatAccountId?: number;
|
||||
[property: string]: any;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,10 +28,20 @@ instance.interceptors.request.use((config: any) => {
|
||||
|
||||
instance.interceptors.response.use(
|
||||
(res: AxiosResponse) => {
|
||||
const { code, success, msg } = res.data || {};
|
||||
if (code === 200 || success) {
|
||||
return res.data.data ?? res.data;
|
||||
const payload = res.data || {};
|
||||
const { code, success, msg } = payload;
|
||||
const hasBizCode = typeof code === "number";
|
||||
const hasBizSuccess = typeof success === "boolean";
|
||||
const bizSuccess = hasBizCode
|
||||
? code === 200
|
||||
: hasBizSuccess
|
||||
? success
|
||||
: undefined;
|
||||
|
||||
if (bizSuccess === true || (!hasBizCode && !hasBizSuccess)) {
|
||||
return payload.data ?? payload;
|
||||
}
|
||||
|
||||
Toast.show({ content: msg || "接口错误", position: "top" });
|
||||
if (code === 401) {
|
||||
localStorage.removeItem("token");
|
||||
|
||||
431
Touchkebao/src/pages/pc/ckbox/components/NavCommon/Notice.tsx
Normal file
431
Touchkebao/src/pages/pc/ckbox/components/NavCommon/Notice.tsx
Normal file
@@ -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<MessageItem[]>([]);
|
||||
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 (
|
||||
<>
|
||||
<div className={styles.messageButton} onClick={handleMessageClick}>
|
||||
<Badge count={messageCount} size="small">
|
||||
<BellOutlined style={{ fontSize: 20 }} />
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<Drawer
|
||||
title="通知中心"
|
||||
placement="right"
|
||||
onClose={handleMessageDrawerClose}
|
||||
open={messageDrawerVisible}
|
||||
width={400}
|
||||
className={styles.messageDrawer}
|
||||
extra={
|
||||
activeTab === "messages" && (
|
||||
<Space>
|
||||
<Button type="text" size="small" onClick={handleReadAll}>
|
||||
全部已读
|
||||
</Button>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
>
|
||||
<div style={{ padding: "0 20px" }}>
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
onChange={handleTabChange}
|
||||
items={[
|
||||
{
|
||||
key: "messages",
|
||||
label: "消息列表",
|
||||
children: (
|
||||
<div className={styles.messageContent}>
|
||||
{loading ? (
|
||||
<div style={{ textAlign: "center", padding: "20px" }}>
|
||||
加载中...
|
||||
</div>
|
||||
) : messageList.length === 0 ? (
|
||||
<Empty description="暂无消息" />
|
||||
) : (
|
||||
messageList.map(item => (
|
||||
<div
|
||||
key={item.id}
|
||||
className={`${styles.messageItem} ${
|
||||
item.isRead === 0 ? styles.unread : ""
|
||||
}`}
|
||||
onClick={() => handleReadMessage(item.id)}
|
||||
>
|
||||
<div className={styles.messageAvatar}>
|
||||
<Avatar
|
||||
size={40}
|
||||
src={item.friendData?.avatar}
|
||||
style={{ backgroundColor: "#87d068" }}
|
||||
>
|
||||
{item.friendData?.nickname?.charAt(0) || "U"}
|
||||
</Avatar>
|
||||
</div>
|
||||
<div className={styles.messageInfo}>
|
||||
<div className={styles.messageTitle}>
|
||||
<span className={styles.messageType}>
|
||||
{item.title}
|
||||
</span>
|
||||
{item.isRead === 0 && (
|
||||
<div className={styles.messageStatus}></div>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.messageText}>
|
||||
{item.message}
|
||||
</div>
|
||||
{item.isRead === 0 && (
|
||||
<div className={styles.messageTime}>
|
||||
{formatTime(item.createTime)}
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={event => {
|
||||
event.stopPropagation();
|
||||
handleReadMessage(item.id);
|
||||
}}
|
||||
>
|
||||
标记已读
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "friendRequests",
|
||||
label: "好友添加记录",
|
||||
children: (
|
||||
<div className={styles.messageContent}>
|
||||
{friendRequestLoading ? (
|
||||
<div style={{ textAlign: "center", padding: "20px" }}>
|
||||
加载中...
|
||||
</div>
|
||||
) : friendRequestList.length === 0 ? (
|
||||
<Empty description="暂无好友添加记录" />
|
||||
) : (
|
||||
friendRequestList.map(item => (
|
||||
<div
|
||||
key={getFriendRequestKey(item)}
|
||||
className={styles.messageItem}
|
||||
>
|
||||
<div className={styles.messageAvatar}>
|
||||
<Avatar
|
||||
size={40}
|
||||
src={item.adder?.avatar}
|
||||
style={{ backgroundColor: "#87d068" }}
|
||||
>
|
||||
{item.adder?.nickname?.charAt(0) || "U"}
|
||||
</Avatar>
|
||||
</div>
|
||||
<div className={styles.messageInfo}>
|
||||
<div className={styles.messageTitle}>
|
||||
<span className={styles.messageType}>
|
||||
添加好友:
|
||||
<Tag color="blue">{getAddedUserName(item)}</Tag>
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
color: getStatusColor(item.status?.code),
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{getStatusText(
|
||||
item.status?.code,
|
||||
item.status?.text,
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={styles.messageText}
|
||||
style={{ color: "#595959" }}
|
||||
>
|
||||
申请人:{getAdderName(item)}
|
||||
</div>
|
||||
<div className={styles.messageText}>
|
||||
验证信息:{item.other?.msgContent || "无"}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={styles.messageText}
|
||||
style={{ color: "#999", fontSize: 12 }}
|
||||
>
|
||||
{item.other?.remark && (
|
||||
<Tag color="orange" style={{ marginTop: 4 }}>
|
||||
备注:{item.other.remark}
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.messageTime}>
|
||||
{formatTime(item.time?.addTime)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Notice;
|
||||
@@ -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");
|
||||
};
|
||||
|
||||
@@ -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<NavCommonProps> = ({ title = "触客宝" }) => {
|
||||
const [messageDrawerVisible, setMessageDrawerVisible] = useState(false);
|
||||
const [messageList, setMessageList] = useState<MessageItem[]>([]);
|
||||
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<NavCommonProps> = ({ 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<void> => {
|
||||
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<void>((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<NavCommonProps> = ({ title = "触客宝" }) => {
|
||||
navigate("/pc/commonConfig");
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "clearCache",
|
||||
icon: <ClearOutlined style={{ fontSize: 16 }} />,
|
||||
label: clearingCache ? "清除缓存中..." : "清除缓存",
|
||||
onClick: handleClearCache,
|
||||
disabled: clearingCache,
|
||||
},
|
||||
{
|
||||
key: "logout",
|
||||
icon: <LogoutOutlined style={{ fontSize: 14 }} />,
|
||||
@@ -220,10 +181,10 @@ const NavCommon: React.FC<NavCommonProps> = ({ title = "触客宝" }) => {
|
||||
></Button>
|
||||
|
||||
<Button
|
||||
icon={<CalendarOutlined />}
|
||||
icon={<SendOutlined />}
|
||||
onClick={handleContentManagementClick}
|
||||
>
|
||||
内容管理
|
||||
发朋友圈
|
||||
</Button>
|
||||
<span className={styles.title}>{title}</span>
|
||||
</div>
|
||||
@@ -236,11 +197,7 @@ const NavCommon: React.FC<NavCommonProps> = ({ title = "触客宝" }) => {
|
||||
</span>
|
||||
{user?.tokens}
|
||||
</span>
|
||||
<div className={styles.messageButton} onClick={handleMessageClick}>
|
||||
<Badge count={messageCount} size="small">
|
||||
<BellOutlined style={{ fontSize: 20 }} />
|
||||
</Badge>
|
||||
</div>
|
||||
<Notice />
|
||||
|
||||
<Dropdown
|
||||
menu={{ items: userMenuItems }}
|
||||
@@ -264,69 +221,6 @@ const NavCommon: React.FC<NavCommonProps> = ({ title = "触客宝" }) => {
|
||||
</Space>
|
||||
</div>
|
||||
</Header>
|
||||
|
||||
<Drawer
|
||||
title="通知中心"
|
||||
placement="right"
|
||||
onClose={handleMessageDrawerClose}
|
||||
open={messageDrawerVisible}
|
||||
width={400}
|
||||
className={styles.messageDrawer}
|
||||
extra={
|
||||
<Space>
|
||||
<Button type="text" size="small" onClick={handleReadAll}>
|
||||
全部已读
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<div className={styles.messageContent}>
|
||||
{loading ? (
|
||||
<div style={{ textAlign: "center", padding: "20px" }}>
|
||||
加载中...
|
||||
</div>
|
||||
) : messageList.length === 0 ? (
|
||||
<Empty description="暂无消息" />
|
||||
) : (
|
||||
messageList.map(item => (
|
||||
<div
|
||||
key={item.id}
|
||||
className={`${styles.messageItem} ${
|
||||
item.isRead === 0 ? styles.unread : ""
|
||||
}`}
|
||||
onClick={() => handleReadMessage(item.id)}
|
||||
>
|
||||
<div className={styles.messageAvatar}>
|
||||
<Avatar
|
||||
size={40}
|
||||
src={item.friendData?.avatar}
|
||||
style={{ backgroundColor: "#87d068" }}
|
||||
>
|
||||
{item.friendData?.nickname?.charAt(0) || "U"}
|
||||
</Avatar>
|
||||
</div>
|
||||
<div className={styles.messageInfo}>
|
||||
<div className={styles.messageTitle}>
|
||||
<span className={styles.messageType}>{item.title}</span>
|
||||
{item.isRead === 0 && (
|
||||
<div className={styles.messageStatus}></div>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.messageText}>{item.message}</div>
|
||||
{item.isRead === 0 && (
|
||||
<div className={styles.messageTime}>
|
||||
{formatTime(item.createTime)}
|
||||
<Button type="link" size="small">
|
||||
标记已读
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@ const ContentManagement: React.FC = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<PowerNavigation
|
||||
title="内容管理"
|
||||
title="发朋友圈"
|
||||
subtitle="可以讲聊天过程的信息收录到素材库中,也调用。"
|
||||
showBackButton={true}
|
||||
backButtonText="返回功能中心"
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { TeamOutlined, CommentOutlined, BookOutlined, SendOutlined } from "@ant-design/icons";
|
||||
import {
|
||||
TeamOutlined,
|
||||
CommentOutlined,
|
||||
BookOutlined,
|
||||
SendOutlined,
|
||||
} from "@ant-design/icons";
|
||||
|
||||
// 数据类型定义
|
||||
export interface FeatureCard {
|
||||
@@ -56,21 +61,21 @@ export const featureCategories: FeatureCard[] = [
|
||||
],
|
||||
path: "/pc/powerCenter/ai-reception",
|
||||
},
|
||||
{
|
||||
id: "content-library",
|
||||
title: "AI内容库配置",
|
||||
description: "管理AI内容库,配置调用权限,优化AI推送效果和内容质量",
|
||||
icon: <BookOutlined style={{ fontSize: "32px", color: "#52c41a" }} />,
|
||||
color: "#52c41a",
|
||||
tag: "内容管理",
|
||||
features: [
|
||||
"多库管理与分类",
|
||||
"AI调用权限配置",
|
||||
"内容检索规则设置",
|
||||
"手动内容上传",
|
||||
],
|
||||
path: "/pc/powerCenter/content-library",
|
||||
},
|
||||
// {
|
||||
// id: "content-library",
|
||||
// title: "AI内容库配置",
|
||||
// description: "管理AI内容库,配置调用权限,优化AI推送效果和内容质量",
|
||||
// icon: <BookOutlined style={{ fontSize: "32px", color: "#52c41a" }} />,
|
||||
// color: "#52c41a",
|
||||
// tag: "内容管理",
|
||||
// features: [
|
||||
// "多库管理与分类",
|
||||
// "AI调用权限配置",
|
||||
// "内容检索规则设置",
|
||||
// "手动内容上传",
|
||||
// ],
|
||||
// path: "/pc/powerCenter/content-library",
|
||||
// },
|
||||
{
|
||||
id: "message-push-assistant",
|
||||
title: "消息推送助手",
|
||||
|
||||
@@ -39,18 +39,6 @@ const PowerCenter: React.FC = () => {
|
||||
return (
|
||||
<div className={styles.powerCenter}>
|
||||
{/* 页面标题区域 */}
|
||||
<div className={styles.pageHeader}>
|
||||
<div className={styles.titleSection}>
|
||||
<div className={styles.mainTitle}>
|
||||
<div className={styles.titleIcon}>⭐</div>
|
||||
<h1>功能中心</h1>
|
||||
</div>
|
||||
<p className={styles.subtitle}>
|
||||
AI智能营销·一站式客户管理·高效业务增长
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* KPI统计区域(置顶,按图展示) */}
|
||||
<div className={styles.kpiSection}>
|
||||
<Row gutter={16}>
|
||||
@@ -157,9 +145,11 @@ const PowerCenter: React.FC = () => {
|
||||
{card.features.map((feature, index) => (
|
||||
<li
|
||||
key={index}
|
||||
style={{
|
||||
"--dot-color": card.color,
|
||||
} as React.CSSProperties}
|
||||
style={
|
||||
{
|
||||
"--dot-color": card.color,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{feature}
|
||||
</li>
|
||||
@@ -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) => (
|
||||
<li
|
||||
key={index}
|
||||
style={{
|
||||
"--dot-color": featureCategories[3].color,
|
||||
} as React.CSSProperties}
|
||||
style={
|
||||
{
|
||||
"--dot-color": featureCategories[3].color,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{feature}
|
||||
</li>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import request from "@/api/request";
|
||||
import type { CreatePushTaskPayload } from "./types";
|
||||
|
||||
// 获取客服列表
|
||||
export function queryWorkbenchCreate(params) {
|
||||
// 创建推送任务
|
||||
export function queryWorkbenchCreate(
|
||||
params: CreatePushTaskPayload,
|
||||
): Promise<any> {
|
||||
return request("/v1/workbench/create", params, "POST");
|
||||
}
|
||||
|
||||
@@ -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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<StepPushParamsProps> = ({
|
||||
selectedAccounts,
|
||||
selectedContacts,
|
||||
targetLabel,
|
||||
friendInterval,
|
||||
onFriendIntervalChange,
|
||||
messageInterval,
|
||||
onMessageIntervalChange,
|
||||
selectedTag,
|
||||
onSelectedTagChange,
|
||||
savedScriptGroups,
|
||||
}) => {
|
||||
return (
|
||||
<div className={styles.stepContent}>
|
||||
<div className={styles.step4Content}>
|
||||
<div className={styles.settingsPanel}>
|
||||
<div className={styles.settingsTitle}>相关设置</div>
|
||||
<div className={styles.settingItem}>
|
||||
<div className={styles.settingLabel}>好友间间隔</div>
|
||||
<div className={styles.settingControl}>
|
||||
<span>间隔时间(秒)</span>
|
||||
<Slider
|
||||
range
|
||||
min={1}
|
||||
max={60}
|
||||
value={friendInterval}
|
||||
onChange={value =>
|
||||
onFriendIntervalChange(value as [number, number])
|
||||
}
|
||||
style={{ flex: 1, margin: "0 16px" }}
|
||||
/>
|
||||
<span>
|
||||
{friendInterval[0]} - {friendInterval[1]}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.settingItem}>
|
||||
<div className={styles.settingLabel}>消息间间隔</div>
|
||||
<div className={styles.settingControl}>
|
||||
<span>间隔时间(秒)</span>
|
||||
<Slider
|
||||
range
|
||||
min={1}
|
||||
max={60}
|
||||
value={messageInterval}
|
||||
onChange={value =>
|
||||
onMessageIntervalChange(value as [number, number])
|
||||
}
|
||||
style={{ flex: 1, margin: "0 16px" }}
|
||||
/>
|
||||
<span>
|
||||
{messageInterval[0]} - {messageInterval[1]}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.tagSection}>
|
||||
<div className={styles.settingLabel}>完成打标签</div>
|
||||
<Select
|
||||
value={selectedTag}
|
||||
onChange={onSelectedTagChange}
|
||||
placeholder="选择标签"
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
<Select.Option value="potential">潜在客户</Select.Option>
|
||||
<Select.Option value="customer">客户</Select.Option>
|
||||
<Select.Option value="partner">合作伙伴</Select.Option>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className={styles.pushPreview}>
|
||||
<div className={styles.previewTitle}>推送预览</div>
|
||||
<ul>
|
||||
<li>推送账号: {selectedAccounts.length}个</li>
|
||||
<li>
|
||||
推送{targetLabel}: {selectedContacts.length}个
|
||||
</li>
|
||||
<li>话术组数: {savedScriptGroups.length}个</li>
|
||||
<li>随机推送: 否</li>
|
||||
<li>预计耗时: ~1分钟</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StepPushParams;
|
||||
@@ -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<InputMessageProps> = ({
|
||||
defaultValue = "",
|
||||
onContentChange,
|
||||
onSend,
|
||||
onAddMessage,
|
||||
clearOnSend = false,
|
||||
placeholder = "输入消息...",
|
||||
hint,
|
||||
@@ -169,9 +172,44 @@ const InputMessage: React.FC<InputMessageProps> = ({
|
||||
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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<StepSendMessageProps> = ({
|
||||
targetLabel,
|
||||
messageContent,
|
||||
onMessageContentChange,
|
||||
friendInterval,
|
||||
onFriendIntervalChange,
|
||||
messageInterval,
|
||||
onMessageIntervalChange,
|
||||
selectedTag,
|
||||
onSelectedTagChange,
|
||||
aiRewriteEnabled,
|
||||
onAiRewriteToggle,
|
||||
aiPrompt,
|
||||
@@ -88,47 +79,111 @@ const StepSendMessage: React.FC<StepSendMessageProps> = ({
|
||||
const [savingScriptGroup, setSavingScriptGroup] = useState(false);
|
||||
const [aiRewriting, setAiRewriting] = useState(false);
|
||||
const [deletingGroupIds, setDeletingGroupIds] = useState<string[]>([]);
|
||||
const [aiRewriteModalVisible, setAiRewriteModalVisible] = useState(false);
|
||||
const [aiRewriteModalIndex, setAiRewriteModalIndex] = useState<number | null>(
|
||||
null,
|
||||
);
|
||||
const [aiRewriteModalPrompt, setAiRewriteModalPrompt] = useState("");
|
||||
const [aiRewritingMessage, setAiRewritingMessage] = useState(false);
|
||||
const [aiRewriteResult, setAiRewriteResult] = useState<string | null>(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<MessageItem[]>(() =>
|
||||
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<StepSendMessageProps> = ({
|
||||
messages,
|
||||
};
|
||||
onSavedScriptGroupsChange([...savedScriptGroups, newGroup]);
|
||||
setMessageItems([]);
|
||||
onCurrentScriptMessagesChange([]);
|
||||
onCurrentScriptNameChange("");
|
||||
onMessageContentChange("");
|
||||
@@ -169,7 +225,7 @@ const StepSendMessage: React.FC<StepSendMessageProps> = ({
|
||||
}, [
|
||||
aiPrompt,
|
||||
aiRewriteEnabled,
|
||||
currentScriptMessages,
|
||||
messageItems,
|
||||
currentScriptName,
|
||||
onCurrentScriptMessagesChange,
|
||||
onCurrentScriptNameChange,
|
||||
@@ -177,6 +233,7 @@ const StepSendMessage: React.FC<StepSendMessageProps> = ({
|
||||
onSavedScriptGroupsChange,
|
||||
savedScriptGroups,
|
||||
savingScriptGroup,
|
||||
itemsToMessages,
|
||||
]);
|
||||
|
||||
const handleAiRewrite = useCallback(async () => {
|
||||
@@ -272,6 +329,138 @@ const StepSendMessage: React.FC<StepSendMessageProps> = ({
|
||||
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<StepSendMessageProps> = ({
|
||||
<div className={styles.stepContent}>
|
||||
<div className={styles.step3Content}>
|
||||
<div className={styles.leftColumn}>
|
||||
{/* 1. 模拟推送内容 */}
|
||||
<div className={styles.previewHeader}>
|
||||
<div className={styles.previewHeaderTitle}>模拟推送内容</div>
|
||||
</div>
|
||||
|
||||
{/* 2. 消息列表 */}
|
||||
<div className={styles.messagePreview}>
|
||||
{messageItems.length === 0 ? (
|
||||
<div className={styles.messagePlaceholder}>
|
||||
开始添加消息内容...
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.messageList}>
|
||||
{messageItems.map((msgItem, index) => (
|
||||
<div className={styles.messageBubbleWrapper} key={index}>
|
||||
<div className={styles.messageBubble}>
|
||||
<div className={styles.messageAvatar}>
|
||||
<UserOutlined />
|
||||
</div>
|
||||
<div className={styles.messageContent}>
|
||||
<div className={styles.messageBubbleInner}>
|
||||
{msgItem.type === "text" && (
|
||||
<div className={styles.messageText}>
|
||||
{msgItem.content}
|
||||
</div>
|
||||
)}
|
||||
{msgItem.type === "image" && (
|
||||
<div className={styles.messageMedia}>
|
||||
<div className={styles.messageMediaIcon}>
|
||||
<PictureOutlined />
|
||||
</div>
|
||||
<img
|
||||
src={msgItem.content}
|
||||
alt={msgItem.fileName || "图片"}
|
||||
className={styles.messageImage}
|
||||
onError={e => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.style.display = "none";
|
||||
}}
|
||||
/>
|
||||
{msgItem.fileName && (
|
||||
<div className={styles.messageFileName}>
|
||||
{msgItem.fileName}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{msgItem.type === "file" && (
|
||||
<div className={styles.messageMedia}>
|
||||
<div className={styles.messageMediaIcon}>
|
||||
<FileOutlined />
|
||||
</div>
|
||||
<div className={styles.messageFileInfo}>
|
||||
<div className={styles.messageFileName}>
|
||||
{msgItem.fileName || "文件"}
|
||||
</div>
|
||||
{msgItem.fileSize && (
|
||||
<div className={styles.messageFileSize}>
|
||||
{msgItem.fileSize >= 1024 * 1024
|
||||
? `${(msgItem.fileSize / 1024 / 1024).toFixed(2)} MB`
|
||||
: `${(msgItem.fileSize / 1024).toFixed(2)} KB`}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{msgItem.type === "audio" && (
|
||||
<div className={styles.messageMedia}>
|
||||
<div className={styles.messageMediaIcon}>
|
||||
<SoundOutlined />
|
||||
</div>
|
||||
<div className={styles.messageFileInfo}>
|
||||
<div className={styles.messageFileName}>
|
||||
{msgItem.fileName || "语音消息"}
|
||||
</div>
|
||||
{msgItem.durationMs && (
|
||||
<div className={styles.messageFileSize}>
|
||||
{Math.floor(msgItem.durationMs / 1000)}秒
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.messageActions}>
|
||||
{msgItem.type === "text" && (
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={() => handleOpenAiRewriteModal(index)}
|
||||
className={styles.aiRewriteButton}
|
||||
>
|
||||
AI改写
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
type="text"
|
||||
danger
|
||||
size="small"
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => handleRemoveMessage(index)}
|
||||
className={styles.messageAction}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 3. 消息输入组件 */}
|
||||
<div className={styles.messageInputArea}>
|
||||
<InputMessage
|
||||
defaultValue={messageContent}
|
||||
onContentChange={onMessageContentChange}
|
||||
onSend={value => handleAddMessage(value)}
|
||||
onAddMessage={message => handleAddMessage(message)}
|
||||
clearOnSend
|
||||
placeholder="请输入内容"
|
||||
hint={`按ENTER发送,按住CTRL+ENTER换行,已配置${savedScriptGroups.length}个话术组,已选择${selectedScriptGroupIds.length}个进行推送,已选${selectedContentLibraries.length}个内容库`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 4. 话术组标题 */}
|
||||
<div className={styles.scriptNameInput}>
|
||||
<Input
|
||||
placeholder="话术组名称(可选)"
|
||||
value={currentScriptName}
|
||||
onChange={event => onCurrentScriptNameChange(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 5. 创建话术组按钮 */}
|
||||
<div className={styles.createScriptGroupButton}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={handleSaveScriptGroup}
|
||||
disabled={currentScriptMessages.length === 0 || savingScriptGroup}
|
||||
loading={savingScriptGroup}
|
||||
block
|
||||
>
|
||||
保存为话术组
|
||||
创建为话术组
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.messagePreview}>
|
||||
<div className={styles.messageBubble}>
|
||||
<div className={styles.currentEditingLabel}>当前编辑话术</div>
|
||||
{currentScriptMessages.length === 0 ? (
|
||||
<div className={styles.messagePlaceholder}>
|
||||
开始添加消息内容...
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.messageList}>
|
||||
{currentScriptMessages.map((msg, index) => (
|
||||
<div className={styles.messageItem} key={index}>
|
||||
<div className={styles.messageText}>{msg}</div>
|
||||
<Button
|
||||
type="text"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => handleRemoveMessage(index)}
|
||||
className={styles.messageAction}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.scriptNameInput}>
|
||||
<Input
|
||||
placeholder="话术组名称(可选)"
|
||||
value={currentScriptName}
|
||||
onChange={event =>
|
||||
onCurrentScriptNameChange(event.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.savedScriptGroups}>
|
||||
{/* 内容库选择组件 */}
|
||||
<div className={styles.rightColumn}>
|
||||
<div className={styles.pushContentHeader}>
|
||||
<div className={styles.pushContentTitle}>推送内容</div>
|
||||
<ContentLibrarySelector
|
||||
selectedContentLibraries={selectedContentLibraries}
|
||||
onSelectedContentLibrariesChange={
|
||||
onSelectedContentLibrariesChange
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.savedScriptGroups}>
|
||||
<div className={styles.scriptGroupHeaderRow}>
|
||||
<div className={styles.scriptGroupTitle}>
|
||||
已保存话术组 ({savedScriptGroups.length})
|
||||
@@ -451,137 +746,119 @@ const StepSendMessage: React.FC<StepSendMessageProps> = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.scriptGroupContent}>
|
||||
{group.messages[0]}
|
||||
{group.messages.length > 1 && " ..."}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.messageInputArea}>
|
||||
<InputMessage
|
||||
defaultValue={messageContent}
|
||||
onContentChange={onMessageContentChange}
|
||||
onSend={value => handleAddMessage(value)}
|
||||
clearOnSend
|
||||
placeholder="请输入内容"
|
||||
hint={`按住CTRL+ENTER换行,已配置${savedScriptGroups.length}个话术组,已选择${selectedScriptGroupIds.length}个进行推送,已选${selectedContentLibraries.length}个内容库`}
|
||||
/>
|
||||
<div className={styles.aiRewriteSection}>
|
||||
<div className={styles.aiRewriteToggle}>
|
||||
<Switch
|
||||
checked={aiRewriteEnabled}
|
||||
onChange={onAiRewriteToggle}
|
||||
/>
|
||||
<div className={styles.aiRewriteLabel}>AI智能话术改写</div>
|
||||
<div>
|
||||
{aiRewriteEnabled && (
|
||||
<Input
|
||||
placeholder="输入改写提示词"
|
||||
value={aiPrompt}
|
||||
onChange={event => onAiPromptChange(event.target.value)}
|
||||
className={styles.aiRewriteInput}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.aiRewriteActions}>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={handleAiRewrite}
|
||||
disabled={!aiRewriteEnabled}
|
||||
loading={aiRewriting}
|
||||
className={styles.aiRewriteButton}
|
||||
>
|
||||
AI改写
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => handleAddMessage(undefined, true)}
|
||||
>
|
||||
添加
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.rightColumn}>
|
||||
<div className={styles.settingsPanel}>
|
||||
<div className={styles.settingsTitle}>相关设置</div>
|
||||
<div className={styles.settingItem}>
|
||||
<div className={styles.settingLabel}>好友间间隔</div>
|
||||
<div className={styles.settingControl}>
|
||||
<span>间隔时间(秒)</span>
|
||||
<Slider
|
||||
range
|
||||
min={1}
|
||||
max={60}
|
||||
value={friendInterval}
|
||||
onChange={value =>
|
||||
onFriendIntervalChange(value as [number, number])
|
||||
}
|
||||
style={{ flex: 1, margin: "0 16px" }}
|
||||
/>
|
||||
<span>
|
||||
{friendInterval[0]} - {friendInterval[1]}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.settingItem}>
|
||||
<div className={styles.settingLabel}>消息间间隔</div>
|
||||
<div className={styles.settingControl}>
|
||||
<span>间隔时间(秒)</span>
|
||||
<Slider
|
||||
range
|
||||
min={1}
|
||||
max={60}
|
||||
value={messageInterval}
|
||||
onChange={value =>
|
||||
onMessageIntervalChange(value as [number, number])
|
||||
}
|
||||
style={{ flex: 1, margin: "0 16px" }}
|
||||
/>
|
||||
<span>
|
||||
{messageInterval[0]} - {messageInterval[1]}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.tagSection}>
|
||||
<div className={styles.settingLabel}>完成打标签</div>
|
||||
<Select
|
||||
value={selectedTag}
|
||||
onChange={onSelectedTagChange}
|
||||
placeholder="选择标签"
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
<Select.Option value="potential">潜在客户</Select.Option>
|
||||
<Select.Option value="customer">客户</Select.Option>
|
||||
<Select.Option value="partner">合作伙伴</Select.Option>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className={styles.pushPreview}>
|
||||
<div className={styles.previewTitle}>推送预览</div>
|
||||
<ul>
|
||||
<li>推送账号: {selectedAccounts.length}个</li>
|
||||
<li>
|
||||
推送{targetLabel}: {selectedContacts.length}个
|
||||
</li>
|
||||
<li>话术组数: {savedScriptGroups.length}个</li>
|
||||
<li>随机推送: 否</li>
|
||||
<li>预计耗时: ~1分钟</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* AI改写弹窗 */}
|
||||
<Modal
|
||||
title={
|
||||
<div className={styles.aiRewriteModalTitle}>
|
||||
<span className={styles.aiRewriteModalTitleIcon}>✨</span>
|
||||
<span>AI智能改写</span>
|
||||
</div>
|
||||
}
|
||||
open={aiRewriteModalVisible}
|
||||
onCancel={handleCloseAiRewriteModal}
|
||||
width={680}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={handleCloseAiRewriteModal}>
|
||||
取消
|
||||
</Button>,
|
||||
<Button
|
||||
key="execute"
|
||||
type="primary"
|
||||
className={styles.aiRewriteExecuteButton}
|
||||
loading={aiRewritingMessage}
|
||||
onClick={handleAiRewriteExecute}
|
||||
disabled={!aiRewriteModalPrompt.trim()}
|
||||
>
|
||||
AI改写
|
||||
</Button>,
|
||||
<Button
|
||||
key="confirm"
|
||||
type="primary"
|
||||
className={styles.confirmButton}
|
||||
onClick={handleConfirmAiRewrite}
|
||||
disabled={!aiRewriteResult || aiRewritingMessage}
|
||||
>
|
||||
确认改写
|
||||
</Button>,
|
||||
]}
|
||||
className={styles.aiRewriteModal}
|
||||
wrapClassName={styles.aiRewriteModalWrap}
|
||||
>
|
||||
<div className={styles.aiRewriteModalContent}>
|
||||
{/* 原文和结果对比区域 */}
|
||||
<div className={styles.aiRewriteModalCompareSection}>
|
||||
{/* 原消息内容区域 */}
|
||||
{aiRewriteModalIndex !== null && (
|
||||
<div className={styles.aiRewriteModalSection}>
|
||||
<div className={styles.aiRewriteModalSectionHeader}>
|
||||
<span className={styles.aiRewriteModalSectionIcon}>📝</span>
|
||||
<span className={styles.aiRewriteModalLabel}>
|
||||
1原消息内容
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.aiRewriteModalOriginalText}>
|
||||
{messageItems[aiRewriteModalIndex]?.type === "text"
|
||||
? messageItems[aiRewriteModalIndex].content
|
||||
: "非文本消息不支持AI改写"}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Loading 状态 */}
|
||||
{aiRewritingMessage && (
|
||||
<div className={styles.aiRewriteModalLoading}>
|
||||
<div className={styles.aiRewriteModalLoadingIcon}>⏳</div>
|
||||
<div className={styles.aiRewriteModalLoadingText}>
|
||||
AI正在改写中,请稍候...
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 分隔线 */}
|
||||
{aiRewriteModalIndex !== null && aiRewriteResult && (
|
||||
<div className={styles.aiRewriteModalDivider} />
|
||||
)}
|
||||
|
||||
{/* 改写结果区域 */}
|
||||
{aiRewriteResult && (
|
||||
<div className={styles.aiRewriteModalSection}>
|
||||
<div className={styles.aiRewriteModalSectionHeader}>
|
||||
<span className={styles.aiRewriteModalSectionIcon}>✨</span>
|
||||
<span className={styles.aiRewriteModalLabel}>改写结果</span>
|
||||
</div>
|
||||
<div className={styles.aiRewriteModalResultText}>
|
||||
{aiRewriteResult}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 提示词输入区域 - 放在最下面 */}
|
||||
<div className={styles.aiRewriteModalSection}>
|
||||
<div className={styles.aiRewriteModalSectionHeader}>
|
||||
<span className={styles.aiRewriteModalSectionIcon}>💡</span>
|
||||
<span className={styles.aiRewriteModalLabel}>改写提示词</span>
|
||||
</div>
|
||||
<Input.TextArea
|
||||
placeholder="默认提示词为: 1、原本的字数和意思不要修改超过10% 2、出现品牌名或个人名字就去除。"
|
||||
value={aiRewriteModalPrompt}
|
||||
onChange={event => setAiRewriteModalPrompt(event.target.value)}
|
||||
rows={3}
|
||||
autoFocus
|
||||
disabled={aiRewritingMessage}
|
||||
className={styles.aiRewriteModalTextArea}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<string, any> = {
|
||||
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<typeof message.loading> | undefined;
|
||||
try {
|
||||
setCreatingTask(true);
|
||||
@@ -386,7 +505,7 @@ const CreatePushTask: React.FC = () => {
|
||||
<Layout
|
||||
header={
|
||||
<>
|
||||
<div style={{ padding: "20px" }}>
|
||||
<div style={{ padding: "0 20px" }}>
|
||||
<PowerNavigation
|
||||
title={title}
|
||||
subtitle={subtitle}
|
||||
@@ -395,26 +514,33 @@ const CreatePushTask: React.FC = () => {
|
||||
onBackClick={handleClose}
|
||||
/>
|
||||
</div>
|
||||
<StepIndicator
|
||||
currentStep={currentStep}
|
||||
steps={[
|
||||
{
|
||||
id: 1,
|
||||
title: "选择微信",
|
||||
subtitle: "选择微信",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: `选择${step2Title}`,
|
||||
subtitle: `选择${step2Title}`,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "一键群发",
|
||||
subtitle: "一键群发",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<div style={{ margin: "0 20px" }}>
|
||||
<StepIndicator
|
||||
currentStep={currentStep}
|
||||
steps={[
|
||||
{
|
||||
id: 1,
|
||||
title: "选择微信",
|
||||
subtitle: "选择微信",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: `选择${step2Title}`,
|
||||
subtitle: `选择${step2Title}`,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "推送内容",
|
||||
subtitle: "推送内容",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "推送参数",
|
||||
subtitle: "推送参数",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
footer={
|
||||
@@ -434,6 +560,12 @@ const CreatePushTask: React.FC = () => {
|
||||
{selectedContacts.length}个
|
||||
</span>
|
||||
)}
|
||||
{currentStep === 4 && (
|
||||
<span>
|
||||
推送账号: {selectedAccounts.length}个, 推送{step2Title}:{" "}
|
||||
{selectedContacts.length}个
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.footerRight}>
|
||||
{currentStep === 1 && (
|
||||
@@ -458,6 +590,14 @@ const CreatePushTask: React.FC = () => {
|
||||
</>
|
||||
)}
|
||||
{currentStep === 3 && (
|
||||
<>
|
||||
<Button onClick={handlePrev}>上一步</Button>
|
||||
<Button type="primary" onClick={handleNext}>
|
||||
下一步 >
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{currentStep === 4 && (
|
||||
<>
|
||||
<Button onClick={handlePrev}>上一步</Button>
|
||||
<Button
|
||||
@@ -511,16 +651,24 @@ const CreatePushTask: React.FC = () => {
|
||||
onSelectedScriptGroupIdsChange={setSelectedScriptGroupIds}
|
||||
selectedContentLibraries={selectedContentLibraries}
|
||||
onSelectedContentLibrariesChange={setSelectedContentLibraries}
|
||||
aiRewriteEnabled={aiRewriteEnabled}
|
||||
onAiRewriteToggle={setAiRewriteEnabled}
|
||||
aiPrompt={aiPrompt}
|
||||
onAiPromptChange={setAiPrompt}
|
||||
/>
|
||||
)}
|
||||
{currentStep === 4 && (
|
||||
<StepPushParams
|
||||
selectedAccounts={selectedAccounts}
|
||||
selectedContacts={selectedContacts}
|
||||
targetLabel={step2Title}
|
||||
friendInterval={friendInterval}
|
||||
onFriendIntervalChange={setFriendInterval}
|
||||
messageInterval={messageInterval}
|
||||
onMessageIntervalChange={setMessageInterval}
|
||||
selectedTag={selectedTag}
|
||||
onSelectedTagChange={setSelectedTag}
|
||||
aiRewriteEnabled={aiRewriteEnabled}
|
||||
onAiRewriteToggle={setAiRewriteEnabled}
|
||||
aiPrompt={aiPrompt}
|
||||
onAiPromptChange={setAiPrompt}
|
||||
savedScriptGroups={savedScriptGroups}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -20,8 +20,67 @@ export interface ContactItem {
|
||||
extendFields?: Record<string, any>;
|
||||
}
|
||||
|
||||
// 消息类型定义
|
||||
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[];
|
||||
}>; // 话术组对象数组
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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<GetPushHistoryResponse> => {
|
||||
// 转换参数格式,确保 limit 和 page 是字符串
|
||||
const requestParams: Record<string, any> = {
|
||||
...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");
|
||||
};
|
||||
|
||||
@@ -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<string, string> = {
|
||||
好友消息: "#1890ff",
|
||||
群消息: "#52c41a",
|
||||
群公告: "#722ed1",
|
||||
};
|
||||
return (
|
||||
<Tag
|
||||
color={colorMap[pushType] || "#666"}
|
||||
style={{ borderRadius: "12px" }}
|
||||
>
|
||||
{pushType}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
// 如果没有中文名称,根据代码映射
|
||||
const codeMap: Record<string, { text: string; color: string }> = {
|
||||
[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 (
|
||||
<Tag color={config.color} style={{ borderRadius: "12px" }}>
|
||||
{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: <CheckCircleOutlined />,
|
||||
},
|
||||
[PushStatus.IN_PROGRESS]: {
|
||||
completed: {
|
||||
text: "已完成",
|
||||
color: "#52c41a",
|
||||
icon: <CheckCircleOutlined />,
|
||||
},
|
||||
[PushStatus.PENDING]: {
|
||||
text: "进行中",
|
||||
color: "#1890ff",
|
||||
icon: <ClockCircleOutlined />,
|
||||
},
|
||||
pending: {
|
||||
text: "进行中",
|
||||
color: "#1890ff",
|
||||
icon: <ClockCircleOutlined />,
|
||||
@@ -189,12 +229,43 @@ const PushHistory: React.FC = () => {
|
||||
color: "#ff4d4f",
|
||||
icon: <CloseCircleOutlined />,
|
||||
},
|
||||
failed: {
|
||||
text: "失败",
|
||||
color: "#ff4d4f",
|
||||
icon: <CloseCircleOutlined />,
|
||||
},
|
||||
};
|
||||
const config = statusMap[status] || {
|
||||
text: "未知",
|
||||
color: "#666",
|
||||
icon: null,
|
||||
|
||||
// 根据状态文本匹配
|
||||
const textMap: Record<
|
||||
string,
|
||||
{ text: string; color: string; icon: React.ReactNode }
|
||||
> = {
|
||||
已完成: {
|
||||
text: "已完成",
|
||||
color: "#52c41a",
|
||||
icon: <CheckCircleOutlined />,
|
||||
},
|
||||
进行中: {
|
||||
text: "进行中",
|
||||
color: "#1890ff",
|
||||
icon: <ClockCircleOutlined />,
|
||||
},
|
||||
失败: {
|
||||
text: "失败",
|
||||
color: "#ff4d4f",
|
||||
icon: <CloseCircleOutlined />,
|
||||
},
|
||||
};
|
||||
|
||||
const config = textMap[displayText] ||
|
||||
statusMap[status] ||
|
||||
statusMap[status.toLowerCase()] || {
|
||||
text: displayText,
|
||||
color: "#666",
|
||||
icon: null,
|
||||
};
|
||||
|
||||
return (
|
||||
<Tag
|
||||
color={config.color}
|
||||
@@ -217,15 +288,26 @@ const PushHistory: React.FC = () => {
|
||||
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) => <span style={{ color: "#333" }}>{text}</span>,
|
||||
},
|
||||
{
|
||||
title: "内容库",
|
||||
dataIndex: "contentLibraryName",
|
||||
key: "contentLibraryName",
|
||||
width: 150,
|
||||
ellipsis: true,
|
||||
render: (text: string) => (
|
||||
<span style={{ color: "#666", fontSize: "13px" }}>{text || "-"}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
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={<span>▼</span>}
|
||||
>
|
||||
<Option value="all">全部类型</Option>
|
||||
<Option value={PushType.FRIEND_MESSAGE}>好友消息</Option>
|
||||
<Option value={PushType.GROUP_MESSAGE}>群消息</Option>
|
||||
<Option value={PushType.GROUP_ANNOUNCEMENT}>群公告</Option>
|
||||
<Option value={PushTypeCode.FRIEND}>好友消息</Option>
|
||||
<Option value={PushTypeCode.GROUP}>群消息</Option>
|
||||
<Option value={PushTypeCode.ANNOUNCEMENT}>群公告</Option>
|
||||
</Select>
|
||||
<Select
|
||||
value={statusFilter}
|
||||
@@ -340,8 +423,8 @@ const PushHistory: React.FC = () => {
|
||||
suffixIcon={<span>▼</span>}
|
||||
>
|
||||
<Option value="all">全部状态</Option>
|
||||
<Option value={PushStatus.PENDING}>进行中</Option>
|
||||
<Option value={PushStatus.COMPLETED}>已完成</Option>
|
||||
<Option value={PushStatus.IN_PROGRESS}>进行中</Option>
|
||||
<Option value={PushStatus.FAILED}>失败</Option>
|
||||
</Select>
|
||||
</div>
|
||||
@@ -353,7 +436,7 @@ const PushHistory: React.FC = () => {
|
||||
columns={columns}
|
||||
dataSource={dataSource}
|
||||
loading={loading}
|
||||
rowKey="id"
|
||||
rowKey="workbenchId"
|
||||
pagination={false}
|
||||
className={styles.dataTable}
|
||||
/>
|
||||
|
||||
@@ -28,14 +28,30 @@ export function getTrafficPoolList() {
|
||||
"GET",
|
||||
);
|
||||
}
|
||||
type ListRequestOptions = {
|
||||
debounceGap?: number;
|
||||
};
|
||||
|
||||
// 好友列表
|
||||
export function getContactList(params) {
|
||||
return request("/v1/kefu/wechatFriend/list", params, "GET");
|
||||
export function getContactList(params, options?: ListRequestOptions) {
|
||||
return request(
|
||||
"/v1/kefu/wechatFriend/list",
|
||||
params,
|
||||
"GET",
|
||||
undefined,
|
||||
options?.debounceGap,
|
||||
);
|
||||
}
|
||||
|
||||
// 群列表
|
||||
export function getGroupList(params) {
|
||||
return request("/v1/kefu/wechatChatroom/list", params, "GET");
|
||||
export function getGroupList(params, options?: ListRequestOptions) {
|
||||
return request(
|
||||
"/v1/kefu/wechatChatroom/list",
|
||||
params,
|
||||
"GET",
|
||||
undefined,
|
||||
options?.debounceGap,
|
||||
);
|
||||
}
|
||||
// 分组列表
|
||||
export function getLabelsListByGroup(params) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user