diff --git a/Cunkebao/src/pages/mobile/workspace/moments-sync/new/index.tsx b/Cunkebao/src/pages/mobile/workspace/moments-sync/new/index.tsx
index b6d8d467..aa15519d 100644
--- a/Cunkebao/src/pages/mobile/workspace/moments-sync/new/index.tsx
+++ b/Cunkebao/src/pages/mobile/workspace/moments-sync/new/index.tsx
@@ -31,7 +31,7 @@ const defaultForm = {
syncCount: 5,
syncInterval: 30,
syncType: 1, // 1=业务号 2=人设号
- accountType: "business" as "business" | "personal", // 仅UI用
+ accountType: 1, // 仅UI用
enabled: true,
deviceGroups: [] as any[],
contentGroups: [] as any[], // 存完整内容库对象数组
@@ -67,8 +67,8 @@ const NewMomentsSync: React.FC = () => {
endTime: res.timeRange?.end || "23:59",
syncCount: res.config?.syncCount || res.syncCount || 5,
syncInterval: res.config?.syncInterval || res.syncInterval || 30,
- syncType: res.accountType === 1 ? 1 : 2,
- accountType: res.accountType === 1 ? "business" : "personal",
+ syncType: res.config?.syncType,
+ accountType: res.config?.accountType,
enabled: res.status === 1,
deviceGroups: res.config?.deviceGroups || [],
// 关键:用id字符串数组回填
@@ -101,11 +101,11 @@ const NewMomentsSync: React.FC = () => {
};
// UI选择账号类型时同步syncType和accountType
- const handleAccountTypeChange = (type: "business" | "personal") => {
+ const handleAccountTypeChange = (type: number) => {
setFormData(prev => ({
...prev,
accountType: type,
- syncType: type === "business" ? 1 : 2,
+ syncType: type,
}));
};
const handleDevicesChange = (devices: DeviceSelectionItem[]) => {
@@ -135,11 +135,11 @@ const NewMomentsSync: React.FC = () => {
const params = {
name: formData.taskName,
deviceGroups: formData.deviceGroups,
- contentGroups: formData.contentGroups.map((lib: any) => lib.id),
+ contentGroups: contentGroupsOptions.map((lib: any) => lib.id),
syncInterval: formData.syncInterval,
syncCount: formData.syncCount,
syncType: formData.syncType, // 账号类型真实传参
- accountType: formData.accountType === "business" ? 1 : 2, // 也要传
+ accountType: formData.accountType, // 也要传
startTime: formData.startTime,
endTime: formData.endTime,
contentTypes: formData.contentTypes,
@@ -227,14 +227,14 @@ const NewMomentsSync: React.FC = () => {
账号类型
diff --git a/Touchkebao/src/components/MetailSelection/api.ts b/Touchkebao/src/components/MetailSelection/api.ts
new file mode 100644
index 00000000..2df6bc32
--- /dev/null
+++ b/Touchkebao/src/components/MetailSelection/api.ts
@@ -0,0 +1,10 @@
+import request from "@/api/request";
+
+// 获取群组列表
+export function getGroupList(params: {
+ page: number;
+ limit: number;
+ keyword?: string;
+}) {
+ return request("/v1/kefu/content/material/list", params, "GET");
+}
diff --git a/Touchkebao/src/components/MetailSelection/data.ts b/Touchkebao/src/components/MetailSelection/data.ts
new file mode 100644
index 00000000..3061247a
--- /dev/null
+++ b/Touchkebao/src/components/MetailSelection/data.ts
@@ -0,0 +1,27 @@
+export interface GroupSelectionItem {
+ id: string;
+ title: string;
+ cover?: string;
+ status: number;
+ [key: string]: any;
+}
+
+// 组件属性接口
+export interface GroupSelectionProps {
+ selectedOptions: GroupSelectionItem[];
+ onSelect: (groups: GroupSelectionItem[]) => void;
+ onSelectDetail?: (groups: GroupSelectionItem[]) => void;
+ placeholder?: string;
+ className?: string;
+ visible?: boolean;
+ onVisibleChange?: (visible: boolean) => void;
+ selectedListMaxHeight?: number;
+ showInput?: boolean;
+ showSelectedList?: boolean;
+ readonly?: boolean;
+ selectionMode?: "multiple" | "single"; // 新增:选择模式,默认为多选
+ onConfirm?: (
+ selectedIds: string[],
+ selectedItems: GroupSelectionItem[],
+ ) => void;
+}
diff --git a/Touchkebao/src/components/MetailSelection/index.module.scss b/Touchkebao/src/components/MetailSelection/index.module.scss
new file mode 100644
index 00000000..bedba3ef
--- /dev/null
+++ b/Touchkebao/src/components/MetailSelection/index.module.scss
@@ -0,0 +1,206 @@
+.inputWrapper {
+ position: relative;
+}
+.inputIcon {
+ position: absolute;
+ left: 12px;
+ top: 50%;
+ transform: translateY(-50%);
+ color: #bdbdbd;
+ font-size: 20px;
+}
+.input {
+ padding-left: 38px !important;
+ height: 48px;
+ border-radius: 16px !important;
+ border: 1px solid #e5e6eb !important;
+ font-size: 16px;
+ background: #f8f9fa;
+}
+.selectedListRow {
+ padding: 8px;
+ border-bottom: 1px solid #f0f0f0;
+ font-size: 14px;
+}
+.selectedListRowContent {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+}
+.selectedListRowContentText {
+ flex: 1;
+}
+
+.popupContainer {
+ display: flex;
+ flex-direction: column;
+ height: 100vh;
+ background: #fff;
+}
+.popupHeader {
+ padding: 24px;
+}
+.popupTitle {
+ text-align: center;
+ font-size: 20px;
+ font-weight: 600;
+ margin-bottom: 24px;
+}
+.searchWrapper {
+ position: relative;
+ margin-bottom: 16px;
+}
+.searchInput {
+ padding-left: 40px !important;
+ padding-top: 8px !important;
+ padding-bottom: 8px !important;
+ border-radius: 24px !important;
+ border: 1px solid #e5e6eb !important;
+ font-size: 15px;
+ background: #f8f9fa;
+}
+.searchIcon {
+ position: absolute;
+ left: 12px;
+ top: 50%;
+ transform: translateY(-50%);
+ color: #bdbdbd;
+ font-size: 16px;
+}
+.clearBtn {
+ position: absolute;
+ right: 8px;
+ top: 50%;
+ transform: translateY(-50%);
+ height: 24px;
+ width: 24px;
+ border-radius: 50%;
+ min-width: 24px;
+}
+
+.groupList {
+ flex: 1;
+ overflow-y: auto;
+}
+.groupListInner {
+ border-top: 1px solid #f0f0f0;
+}
+.groupItem {
+ display: flex;
+ align-items: center;
+ padding: 16px 24px;
+ border-bottom: 1px solid #f0f0f0;
+ transition: background 0.2s;
+ &:hover {
+ background: #f5f6fa;
+ }
+}
+.groupInfo {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ flex: 1;
+}
+.groupAvatar {
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #fff;
+ font-size: 14px;
+ font-weight: 500;
+ overflow: hidden;
+}
+.avatarImg {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+.groupDetail {
+ flex: 1;
+}
+.groupName {
+ font-weight: 500;
+ font-size: 16px;
+ color: #222;
+ margin-bottom: 2px;
+}
+.groupId {
+ font-size: 13px;
+ color: #888;
+ margin-bottom: 2px;
+}
+.groupOwner {
+ font-size: 13px;
+ color: #bdbdbd;
+}
+
+.loadingBox {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+}
+.loadingText {
+ color: #888;
+ font-size: 15px;
+}
+.emptyBox {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+}
+.emptyText {
+ color: #888;
+ font-size: 15px;
+}
+
+.paginationRow {
+ border-top: 1px solid #f0f0f0;
+ padding: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ background: #fff;
+}
+.totalCount {
+ font-size: 14px;
+ color: #888;
+}
+.paginationControls {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+.pageBtn {
+ padding: 0 8px;
+ height: 32px;
+ min-width: 32px;
+}
+.pageInfo {
+ font-size: 14px;
+ color: #222;
+}
+
+.popupFooter {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px;
+ border-top: 1px solid #f0f0f0;
+ background: #fff;
+}
+.selectedCount {
+ font-size: 14px;
+ color: #888;
+}
+.footerBtnGroup {
+ display: flex;
+ gap: 12px;
+}
diff --git a/Touchkebao/src/components/MetailSelection/index.tsx b/Touchkebao/src/components/MetailSelection/index.tsx
new file mode 100644
index 00000000..f7600027
--- /dev/null
+++ b/Touchkebao/src/components/MetailSelection/index.tsx
@@ -0,0 +1,138 @@
+import React, { useState } from "react";
+import { SearchOutlined, DeleteOutlined } from "@ant-design/icons";
+import { Button, Input } from "antd";
+import { Avatar } from "antd-mobile";
+import style from "./index.module.scss";
+import SelectionPopup from "./selectionPopup";
+import { GroupSelectionProps } from "./data";
+export default function GroupSelection({
+ selectedOptions,
+ onSelect,
+ onSelectDetail,
+ placeholder = "选择素材",
+ className = "",
+ visible,
+ onVisibleChange,
+ selectedListMaxHeight = 300,
+ showInput = true,
+ showSelectedList = true,
+ readonly = false,
+ selectionMode = "single", // 默认为多选模式
+ onConfirm,
+}: GroupSelectionProps) {
+ const [popupVisible, setPopupVisible] = useState(false);
+
+ // 删除已选素材
+ const handleRemoveGroup = (id: string) => {
+ if (readonly) return;
+ onSelect(selectedOptions.filter(g => g.id !== id));
+ };
+
+ // 受控弹窗逻辑
+ const realVisible = visible !== undefined ? visible : popupVisible;
+ const setRealVisible = (v: boolean) => {
+ if (onVisibleChange) onVisibleChange(v);
+ if (visible === undefined) setPopupVisible(v);
+ };
+
+ // 打开弹窗
+ const openPopup = () => {
+ if (readonly) return;
+ setRealVisible(true);
+ };
+
+ // 清空已选择的素材
+ const handleClear = () => {
+ if (readonly) return;
+ onSelect([]);
+ };
+
+ // 获取显示文本
+ const getDisplayText = () => {
+ if (selectedOptions.length === 0) return "";
+ if (selectionMode === "single") {
+ return selectedOptions[0]?.title || "已选择素材";
+ }
+ return `已选择 ${selectedOptions.length} 个素材`;
+ };
+
+ return (
+ <>
+ {/* 输入框 */}
+ {showInput && (
+
+ }
+ allowClear={!readonly && selectedOptions.length > 0}
+ onClear={handleClear}
+ size="large"
+ readOnly={readonly}
+ disabled={readonly}
+ style={
+ readonly ? { background: "#f5f5f5", cursor: "not-allowed" } : {}
+ }
+ />
+
+ )}
+ {/* 已选素材列表窗口 */}
+ {showSelectedList && selectedOptions.length > 0 && (
+
+ {selectedOptions.map(group => (
+
+
+
+
+
{group.title}
+
ID: {group.id}
+
+ {!readonly && (
+
}
+ size="small"
+ style={{
+ marginLeft: 4,
+ color: "#ff4d4f",
+ border: "none",
+ background: "none",
+ minWidth: 24,
+ height: 24,
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ }}
+ onClick={() => handleRemoveGroup(group.id)}
+ />
+ )}
+
+
+ ))}
+
+ )}
+ {/* 弹窗 */}
+
+ >
+ );
+}
diff --git a/Touchkebao/src/components/MetailSelection/selectionPopup.tsx b/Touchkebao/src/components/MetailSelection/selectionPopup.tsx
new file mode 100644
index 00000000..e318faf0
--- /dev/null
+++ b/Touchkebao/src/components/MetailSelection/selectionPopup.tsx
@@ -0,0 +1,257 @@
+import React, { useState, useEffect } from "react";
+import { Popup, Checkbox, Radio } from "antd-mobile";
+
+import { getGroupList } from "./api";
+import style from "./index.module.scss";
+import Layout from "@/components/Layout/Layout";
+import PopupHeader from "@/components/PopuLayout/header";
+import PopupFooter from "@/components/PopuLayout/footer";
+import { GroupSelectionItem } from "./data";
+
+// 弹窗属性接口
+interface SelectionPopupProps {
+ visible: boolean;
+ onVisibleChange: (visible: boolean) => void;
+ selectedOptions: GroupSelectionItem[];
+ onSelect: (groups: GroupSelectionItem[]) => void;
+ onSelectDetail?: (groups: GroupSelectionItem[]) => void;
+ readonly?: boolean;
+ selectionMode?: "multiple" | "single"; // 新增:选择模式,默认为多选
+ onConfirm?: (
+ selectedIds: string[],
+ selectedItems: GroupSelectionItem[],
+ ) => void;
+}
+
+export default function SelectionPopup({
+ visible,
+ onVisibleChange,
+ selectedOptions,
+ onSelect,
+ onSelectDetail,
+ readonly = false,
+ selectionMode = "multiple", // 默认为多选模式
+ onConfirm,
+}: SelectionPopupProps) {
+ const [groups, setGroups] = useState
([]);
+ const [searchQuery, setSearchQuery] = useState("");
+ const [currentPage, setCurrentPage] = useState(1);
+ const [totalPages, setTotalPages] = useState(1);
+ const [totalGroups, setTotalGroups] = useState(0);
+ const [loading, setLoading] = useState(false);
+ const [tempSelectedOptions, setTempSelectedOptions] = useState<
+ GroupSelectionItem[]
+ >([]);
+
+ // 获取素材列表API
+ const fetchGroups = async (page: number, keyword: string = "") => {
+ setLoading(true);
+ try {
+ const params: any = {
+ page,
+ limit: 20,
+ };
+
+ if (keyword.trim()) {
+ params.keyword = keyword.trim();
+ }
+
+ const response = await getGroupList(params);
+ if (response && response.list) {
+ setGroups(response.list);
+ setTotalGroups(response.total || 0);
+ setTotalPages(Math.ceil((response.total || 0) / 20));
+ }
+ } catch (error) {
+ console.error("获取素材列表失败:", error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ // 处理素材选择
+ const handleGroupToggle = (group: GroupSelectionItem) => {
+ if (readonly) return;
+
+ if (selectionMode === "single") {
+ // 单选模式:直接设置为当前选中的项
+ setTempSelectedOptions([group]);
+ } else {
+ // 多选模式:切换选中状态
+ const newSelectedGroups = tempSelectedOptions.some(g => g.id === group.id)
+ ? tempSelectedOptions.filter(g => g.id !== group.id)
+ : tempSelectedOptions.concat(group);
+
+ setTempSelectedOptions(newSelectedGroups);
+ }
+ };
+
+ // 全选当前页(仅在多选模式下有效)
+ const handleSelectAllCurrentPage = (checked: boolean) => {
+ if (readonly || selectionMode === "single") return;
+
+ if (checked) {
+ // 全选:添加当前页面所有未选中的素材
+ const currentPageGroups = groups.filter(
+ group => !tempSelectedOptions.some(g => g.id === group.id),
+ );
+ setTempSelectedOptions(prev => [...prev, ...currentPageGroups]);
+ } else {
+ // 取消全选:移除当前页面的所有素材
+ const currentPageGroupIds = groups.map(g => g.id);
+ setTempSelectedOptions(prev =>
+ prev.filter(g => !currentPageGroupIds.includes(g.id)),
+ );
+ }
+ };
+
+ // 检查当前页是否全选(仅在多选模式下有效)
+ const isCurrentPageAllSelected =
+ selectionMode === "multiple" &&
+ groups.length > 0 &&
+ groups.every(group => tempSelectedOptions.some(g => g.id === group.id));
+
+ // 确认选择
+ const handleConfirm = () => {
+ // 用户点击确认时,才更新实际的selectedOptions
+ onSelect(tempSelectedOptions);
+
+ // 如果有 onSelectDetail 回调,传递完整的素材对象
+ if (onSelectDetail) {
+ const selectedGroupObjs = groups.filter(group =>
+ tempSelectedOptions.some(g => g.id === group.id),
+ );
+ onSelectDetail(selectedGroupObjs);
+ }
+
+ if (onConfirm) {
+ onConfirm(
+ tempSelectedOptions.map(g => g.id),
+ tempSelectedOptions,
+ );
+ }
+ onVisibleChange(false);
+ };
+
+ // 弹窗打开时初始化数据(只执行一次)
+ useEffect(() => {
+ if (visible) {
+ setCurrentPage(1);
+ setSearchQuery("");
+ // 复制一份selectedOptions到临时变量
+ setTempSelectedOptions([...selectedOptions]);
+ fetchGroups(1, "");
+ }
+ }, [visible]);
+
+ // 搜索防抖(只在弹窗打开且搜索词变化时执行)
+ useEffect(() => {
+ if (!visible || searchQuery === "") return;
+
+ const timer = setTimeout(() => {
+ setCurrentPage(1);
+ fetchGroups(1, searchQuery);
+ }, 500);
+
+ return () => clearTimeout(timer);
+ }, [searchQuery, visible]);
+
+ // 页码变化时请求数据(只在弹窗打开且页码不是1时执行)
+ useEffect(() => {
+ if (!visible) return;
+ fetchGroups(currentPage, searchQuery);
+ }, [currentPage, visible, searchQuery]);
+
+ return (
+ onVisibleChange(false)}
+ position="bottom"
+ bodyStyle={{ height: "100vh" }}
+ >
+ fetchGroups(currentPage, searchQuery)}
+ />
+ }
+ footer={
+ onVisibleChange(false)}
+ onConfirm={handleConfirm}
+ isAllSelected={isCurrentPageAllSelected}
+ onSelectAll={handleSelectAllCurrentPage}
+ showSelectAll={selectionMode === "multiple"} // 只在多选模式下显示全选功能
+ />
+ }
+ >
+
+ {loading ? (
+
+ ) : groups.length > 0 ? (
+
+ {groups.map(group => (
+
+ {selectionMode === "single" ? (
+
g.id === group.id)}
+ onChange={() => !readonly && handleGroupToggle(group)}
+ disabled={readonly}
+ style={{ marginRight: 12 }}
+ />
+ ) : (
+ g.id === group.id)}
+ onChange={() => !readonly && handleGroupToggle(group)}
+ disabled={readonly}
+ style={{ marginRight: 12 }}
+ />
+ )}
+
+
+ {group.cover ? (
+

+ ) : (
+ group.title.charAt(0)
+ )}
+
+
+
{group.title}
+
+ 创建人: {group.userName}
+
+
+
+
+ ))}
+
+ ) : (
+
+
+ {searchQuery
+ ? `没有找到包含"${searchQuery}"的素材`
+ : "没有找到素材"}
+
+
+ )}
+
+
+
+ );
+}
diff --git a/Touchkebao/src/components/PopuLayout/footer.tsx b/Touchkebao/src/components/PopuLayout/footer.tsx
index 60e562be..a4dbe0cd 100644
--- a/Touchkebao/src/components/PopuLayout/footer.tsx
+++ b/Touchkebao/src/components/PopuLayout/footer.tsx
@@ -14,6 +14,7 @@ interface PopupFooterProps {
// 全选功能相关
isAllSelected?: boolean;
onSelectAll?: (checked: boolean) => void;
+ showSelectAll?: boolean; // 新增:控制全选功能显示,默认为true
}
const PopupFooter: React.FC = ({
@@ -26,19 +27,22 @@ const PopupFooter: React.FC = ({
onConfirm,
isAllSelected = false,
onSelectAll,
+ showSelectAll = true, // 默认为true,显示全选功能
}) => {
return (
<>
{/* 分页栏 */}
- onSelectAll(e.target.checked)}
- className={style.selectAllCheckbox}
- >
- 全选当前页
-
+ {showSelectAll && (
+ onSelectAll?.(e.target.checked)}
+ className={style.selectAllCheckbox}
+ >
+ 全选当前页
+
+ )}
}
type={isWeChat() ? "primary" : "default"}
- onClick={handleMenuClick}
+ onClick={() => handleMenuClick(1)}
>
Ai智能客服
@@ -120,14 +240,7 @@ const NavCommon: React.FC
= ({ title = "触客宝" }) => {
-
{
- navigate("/pc/commonConfig");
- }}
- icon={}
- >
- 全局配置
-
+
= ({ title = "触客宝" }) => {
className={styles.messageDrawer}
extra={
-
+
全部已读
}
>
-
-
-
- 林
-
+ {loading ? (
+
+ 加载中...
-
-
-
新消息
-
+ ) : messageList.length === 0 ? (
+
+ ) : (
+ messageList.map(item => (
+
handleReadMessage(item.id)}
+ >
+
+
+ {item.friendData?.nickname?.charAt(0) || "U"}
+
+
+
+
+
{item.title}
+ {item.isRead === 0 && (
+
+ )}
+
+
{item.message}
+ {item.isRead === 0 && (
+
+ {formatTime(item.createTime)}
+
+ 标记已读
+
+
+ )}
+
-
- 林秀杰:关于自由主时间特惠,原价1999元,现在只需1699元
-
-
- 03-05
-
- 回复
-
-
-
-
-
-
-
-
-
-
- Eric在「云归营私域银行项目群」中@了您
-
-
03-05
-
-
-
-
-
-
-
- 好友申请
-
-
李翔想请求添加您为好友
-
03-04
-
-
- 接受
-
- 拒绝
-
-
-
+ ))
+ )}
>
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/api.ts b/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/api.ts
index 62d156ce..8b90daa4 100644
--- a/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/api.ts
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/api.ts
@@ -118,7 +118,7 @@ export function updateSensitiveWord(data: SensitiveWordUpdateRequest) {
// 违禁词管理-修改状态
export function setSensitiveWordStatus(data: SensitiveWordSetStatusRequest) {
- return request("/v1/kefu/content/sensitiveWord/setStatus", data, "POST");
+ return request("/v1/kefu/content/sensitiveWord/setStatus", data, "GET");
}
// 关键词回复管理相关接口
@@ -132,18 +132,19 @@ export interface KeywordAddRequest {
title: string;
keywords: string;
content: string;
- matchType: string; // 匹配类型:模糊匹配、精确匹配
- priority: string; // 优先级
- replyType: string; // 回复类型:文本回复、模板回复
+ type: number; // 匹配类型:模糊匹配、精确匹配
+ level: number; // 优先级
+ replyType: number; // 回复类型:文本回复、模板回复
status: string;
+ metailGroups: any[];
}
export interface KeywordUpdateRequest extends KeywordAddRequest {
- id?: string;
+ id?: number;
}
export interface KeywordSetStatusRequest {
- id: string;
+ id: number;
}
// 关键词回复-列表
@@ -157,12 +158,12 @@ export function addKeyword(data: KeywordAddRequest) {
}
// 关键词回复-详情
-export function getKeywordDetails(id: string) {
+export function getKeywordDetails(id: number) {
return request("/v1/kefu/content/keywords/details", { id }, "GET");
}
// 关键词回复-删除
-export function deleteKeyword(id: string) {
+export function deleteKeyword(id: number) {
return request("/v1/kefu/content/keywords/del", { id }, "DELETE");
}
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/components/management/KeywordManagement.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/components/management/KeywordManagement.tsx
index 99be1aaa..e3b94ad8 100644
--- a/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/components/management/KeywordManagement.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/components/management/KeywordManagement.tsx
@@ -4,14 +4,21 @@ import React, {
forwardRef,
useImperativeHandle,
} from "react";
-import { Button, Input, Tag, Switch, message } from "antd";
+import {
+ Button,
+ Input,
+ Tag,
+ Switch,
+ message,
+ Popconfirm,
+ Pagination,
+} from "antd";
import {
SearchOutlined,
- FilterOutlined,
FormOutlined,
DeleteOutlined,
} from "@ant-design/icons";
-import styles from "../../index.module.scss";
+import styles from "./index.module.scss";
import {
getKeywordList,
deleteKeyword,
@@ -23,15 +30,15 @@ import KeywordModal from "../modals/KeywordModal";
const { Search } = Input;
interface KeywordItem {
- id: string;
+ id?: number;
+ type: number;
+ replyType: number;
title: string;
keywords: string;
+ status: number;
content: string;
- matchType: string;
- priority: string;
- replyType: string;
- status: string;
- enabled: boolean;
+ metailGroupsOptions: { title: string; id: number }[];
+ level: number;
}
const KeywordManagement = forwardRef
>(
@@ -40,41 +47,79 @@ const KeywordManagement = forwardRef>(
const [keywordsList, setKeywordsList] = useState([]);
const [loading, setLoading] = useState(false);
const [editModalVisible, setEditModalVisible] = useState(false);
- const [editingKeywordId, setEditingKeywordId] = useState(
+ const [editingKeywordId, setEditingKeywordId] = useState(
null,
);
+ const [pagination, setPagination] = useState({
+ current: 1,
+ pageSize: 10,
+ total: 0,
+ });
+ // 已提交的搜索关键词(仅在点击搜索时更新,用于服务端查询)
+ const [keywordQuery, setKeywordQuery] = useState("");
+ //匹配类型
+ const getMatchTypeText = (type: number) => {
+ switch (type) {
+ case 0:
+ return "模糊匹配";
+ case 1:
+ return "精确匹配";
+ }
+ };
+
+ //匹配优先级
+ const getPriorityText = (level: number) => {
+ switch (level) {
+ case 0:
+ return "低优先级";
+ case 1:
+ return "中优先级";
+ case 2:
+ return "高优先级";
+ }
+ };
// 回复类型映射
- const getReplyTypeText = (replyType: string) => {
+ const getReplyTypeText = (replyType: number) => {
switch (replyType) {
- case "text":
- return "文本回复";
- case "template":
- return "模板回复";
+ case 0:
+ return "素材回复";
+ case 1:
+ return "自定义";
default:
return "未知类型";
}
};
// 回复类型颜色
- const getReplyTypeColor = (replyType: string) => {
+ const getReplyTypeColor = (replyType: number) => {
switch (replyType) {
- case "text":
- return "#1890ff";
- case "template":
- return "#722ed1";
+ case 0:
+ return "blue";
+ case 1:
+ return "purple";
default:
- return "#8c8c8c";
+ return "gray";
}
};
- // 获取关键词列表
+ // 获取关键词列表(服务端搜索)
const fetchKeywords = async (params?: KeywordListParams) => {
try {
setLoading(true);
- const response = await getKeywordList(params || {});
+ const requestParams = {
+ page: pagination.current.toString(),
+ limit: pagination.pageSize.toString(),
+ keyword: keywordQuery || undefined,
+ ...params,
+ } as KeywordListParams;
+ const response = await getKeywordList(requestParams);
if (response) {
setKeywordsList(response.list || []);
+ setPagination(prev => ({
+ ...prev,
+ total: response.total || 0,
+ }));
} else {
setKeywordsList([]);
message.error(response?.message || "获取关键词列表失败");
@@ -94,26 +139,24 @@ const KeywordManagement = forwardRef>(
}));
// 关键词管理相关函数
- const handleToggleKeyword = async (id: string) => {
+ const handleToggleKeyword = async (id: number) => {
try {
- const response = await setKeywordStatus({ id });
- if (response) {
- setKeywordsList(prev =>
- prev.map(item =>
- item.id === id ? { ...item, enabled: !item.enabled } : item,
- ),
- );
- message.success("状态更新成功");
- } else {
- message.error(response?.message || "状态更新失败");
- }
+ await setKeywordStatus({ id });
+ setKeywordsList(prev =>
+ prev.map(item =>
+ item.id === id
+ ? { ...item, status: item.status === 1 ? 0 : 1 }
+ : item,
+ ),
+ );
+ message.success("状态更新成功");
} catch (error) {
console.error("状态更新失败:", error);
message.error("状态更新失败");
}
};
- const handleEditKeyword = (id: string) => {
+ const handleEditKeyword = (id: number) => {
setEditingKeywordId(id);
setEditModalVisible(true);
};
@@ -123,40 +166,39 @@ const KeywordManagement = forwardRef>(
fetchKeywords(); // 重新获取数据
};
- const handleDeleteKeyword = async (id: string) => {
+ const handleDeleteKeyword = async (id: number) => {
try {
- const response = await deleteKeyword(id);
- if (response) {
- setKeywordsList(prev => prev.filter(item => item.id !== id));
- message.success("删除成功");
- } else {
- message.error(response?.message || "删除失败");
- }
+ await deleteKeyword(id);
+ setKeywordsList(prev => prev.filter(item => item.id !== id));
+ message.success("删除成功");
} catch (error) {
console.error("删除失败:", error);
message.error("删除失败");
}
};
- // 搜索和筛选功能
- const filteredKeywords = keywordsList.filter(item => {
- if (!searchValue) return true;
- return (
- item.title.toLowerCase().includes(searchValue.toLowerCase()) ||
- item.keywords.toLowerCase().includes(searchValue.toLowerCase()) ||
- item.content.toLowerCase().includes(searchValue.toLowerCase())
- );
- });
+ // 移除本地筛选,改为服务端搜索,列表直接使用 keywordsList
// 搜索处理函数
const handleSearch = (value: string) => {
- fetchKeywords({ keyword: value });
+ setKeywordQuery(value || "");
+ setPagination(prev => ({ ...prev, current: 1 }));
};
- // 组件挂载时获取数据
+ // 分页处理函数
+ const handlePageChange = (page: number, pageSize?: number) => {
+ setPagination(prev => ({
+ ...prev,
+ current: page,
+ pageSize: pageSize || prev.pageSize,
+ }));
+ };
+
+ // 初始化与依赖变化时获取数据(依赖分页与搜索关键字)
useEffect(() => {
fetchKeywords();
- }, []);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [pagination.current, pagination.pageSize, keywordQuery]);
return (
@@ -169,59 +211,92 @@ const KeywordManagement = forwardRef
>(
style={{ width: 300 }}
prefix={}
/>
- }>筛选
{loading ? (
加载中...
- ) : filteredKeywords.length === 0 ? (
+ ) : keywordsList.length === 0 ? (
暂无关键词数据
) : (
- filteredKeywords.map(item => (
+ keywordsList.map(item => (
-
{item.title}
-
-
{item.matchType}
-
- 优先级{item.priority}
-
+
+
+
{item.title}
+
{getMatchTypeText(item.type)}
+
{getPriorityText(item.level)}
+
+ {item.content.length ? (
+
{item.content}
+ ) : (
+
+ 已选素材:
+ {item.metailGroupsOptions.map(v => (
+
+ {v.title}
+
+ ))}
+
+ )}
+
+
+ {getReplyTypeText(item.replyType)}
+
+
+
+
+
handleToggleKeyword(item.id)}
+ className={styles.toggleSwitch}
+ />
+ }
+ onClick={() => handleEditKeyword(item.id)}
+ className={styles.actionBtn}
+ />
+ handleDeleteKeyword(item.id)}
+ okText="确定"
+ cancelText="取消"
+ okType="danger"
+ >
+ }
+ className={styles.actionBtn}
+ />
+
-
{item.content}
-
- {getReplyTypeText(item.replyType)}
-
-
-
- handleToggleKeyword(item.id)}
- className={styles.toggleSwitch}
- />
- }
- onClick={() => handleEditKeyword(item.id)}
- className={styles.actionBtn}
- />
- }
- onClick={() => handleDeleteKeyword(item.id)}
- className={styles.actionBtn}
- />
))
)}
+ {/* 分页组件 */}
+
+
+ `第 ${range[0]}-${range[1]} 条/共 ${total} 条`
+ }
+ onChange={handlePageChange}
+ onShowSizeChange={handlePageChange}
+ />
+
+
{/* 编辑弹窗 */}
>(
const [editingMaterialId, setEditingMaterialId] = useState(
null,
);
+ const [pagination, setPagination] = useState({
+ current: 1,
+ pageSize: 10,
+ total: 0,
+ });
+
+ // 使用 ref 来存储最新的分页状态
+ const paginationRef = useRef(pagination);
+ paginationRef.current = pagination;
// 获取类型图标
const getTypeIcon = (type: string) => {
@@ -68,9 +78,19 @@ const MaterialManagement = forwardRef>(
const fetchMaterials = async (params?: MaterialListParams) => {
try {
setLoading(true);
- const response = await getMaterialList(params || {});
+ const currentPagination = paginationRef.current;
+ const requestParams = {
+ page: currentPagination.current.toString(),
+ limit: currentPagination.pageSize.toString(),
+ ...params,
+ };
+ const response = await getMaterialList(requestParams);
if (response) {
setMaterialsList(response.list || []);
+ setPagination(prev => ({
+ ...prev,
+ total: response.total || 0,
+ }));
} else {
setMaterialsList([]);
message.error(response?.message || "获取素材列表失败");
@@ -91,22 +111,13 @@ const MaterialManagement = forwardRef>(
// 素材管理相关函数
const handleDeleteMaterial = async (id: number) => {
- Modal.confirm({
- title: "确认删除",
- content: "确定要删除这个素材吗?删除后无法恢复。",
- okText: "确定",
- cancelText: "取消",
- okType: "danger",
- onOk: async () => {
- try {
- await deleteMaterial(id.toString());
- setMaterialsList(prev => prev.filter(item => item.id !== id));
- message.success("删除成功");
- } catch (error) {
- message.error("删除失败");
- }
- },
- });
+ try {
+ await deleteMaterial(id.toString());
+ setMaterialsList(prev => prev.filter(item => item.id !== id));
+ message.success("删除成功");
+ } catch (error) {
+ message.error("删除失败");
+ }
};
// 编辑素材
@@ -122,9 +133,23 @@ const MaterialManagement = forwardRef>(
// 搜索处理函数
const handleSearch = (value: string) => {
+ setPagination(prev => ({ ...prev, current: 1 }));
fetchMaterials({ keyword: value });
};
+ // 分页处理函数
+ const handlePageChange = (page: number, pageSize?: number) => {
+ setPagination(prev => ({
+ ...prev,
+ current: page,
+ pageSize: pageSize || prev.pageSize,
+ }));
+ // 分页变化后立即获取数据
+ setTimeout(() => {
+ fetchMaterials();
+ }, 0);
+ };
+
// 组件挂载时获取数据
useEffect(() => {
fetchMaterials();
@@ -167,18 +192,24 @@ const MaterialManagement = forwardRef>(
>
编辑
,
- }
- onClick={e => {
- e.stopPropagation();
- handleDeleteMaterial(item.id);
- }}
+ title="确认删除"
+ description="确定要删除这个素材吗?删除后无法恢复。"
+ onConfirm={() => handleDeleteMaterial(item.id)}
+ okText="确定"
+ cancelText="取消"
+ okType="danger"
>
- 删除
- ,
+ }
+ onClick={e => e.stopPropagation()}
+ >
+ 删除
+
+ ,
]}
>
>(
)}
+ {/* 分页组件 */}
+
+
+ `第 ${range[0]}-${range[1]} 条/共 ${total} 条`
+ }
+ onChange={handlePageChange}
+ onShowSizeChange={handlePageChange}
+ />
+
+
{/* 编辑弹窗 */}
{
- const [searchValue, setSearchValue] = useState("");
- const [sensitiveWordsList, setSensitiveWordsList] = useState<
- SensitiveWordItem[]
- >([]);
- const [loading, setLoading] = useState(false);
- const [editModalVisible, setEditModalVisible] = useState(false);
- const [editingSensitiveWordId, setEditingSensitiveWordId] = useState<
- string | null
- >(null);
+const SensitiveWordManagement = forwardRef>(
+ (props, ref) => {
+ const [searchValue, setSearchValue] = useState("");
+ const [sensitiveWordsList, setSensitiveWordsList] = useState<
+ SensitiveWordItem[]
+ >([]);
+ const [loading, setLoading] = useState(false);
+ const [editModalVisible, setEditModalVisible] = useState(false);
+ const [editingSensitiveWordId, setEditingSensitiveWordId] = useState<
+ string | null
+ >(null);
+ const [pagination, setPagination] = useState({
+ current: 1,
+ pageSize: 10,
+ total: 0,
+ });
- const getTagColor = (tag: string) => {
- switch (tag) {
- case "政治":
- return "#ff4d4f";
- case "色情":
- return "#ff4d4f";
- case "暴力":
- return "#ff4d4f";
- default:
- return "#ff4d4f";
- }
- };
+ const getTagColor = (tag: string) => {
+ switch (tag) {
+ case "政治":
+ return "#ff4d4f";
+ case "色情":
+ return "#ff4d4f";
+ case "暴力":
+ return "#ff4d4f";
+ default:
+ return "#ff4d4f";
+ }
+ };
- // 操作类型映射
- const getOperationText = (operation: string) => {
- switch (operation) {
- case "0":
- return "不操作";
- case "1":
- return "替换";
- case "2":
- return "删除";
- case "3":
- return "警告";
- case "4":
- return "禁止发送";
- default:
- return "未知操作";
- }
- };
+ // 操作类型映射
+ const getOperationText = (operation: number) => {
+ switch (operation) {
+ case 0:
+ return "不操作";
+ case 1:
+ return "替换";
+ case 2:
+ return "删除";
+ case 3:
+ return "警告";
+ case 4:
+ return "禁止发送";
+ default:
+ return "未知操作";
+ }
+ };
- // 获取敏感词列表
- const fetchSensitiveWords = async (params?: SensitiveWordListParams) => {
- try {
- setLoading(true);
- const response = await getSensitiveWordList(params || {});
- if (response) {
- setSensitiveWordsList(response.list || []);
- } else {
+ // 获取敏感词列表
+ const fetchSensitiveWords = async (params?: SensitiveWordListParams) => {
+ try {
+ setLoading(true);
+ const requestParams = {
+ page: pagination.current.toString(),
+ limit: pagination.pageSize.toString(),
+ ...params,
+ };
+ const response = await getSensitiveWordList(requestParams);
+ if (response) {
+ setSensitiveWordsList(response.list || []);
+ setPagination(prev => ({
+ ...prev,
+ total: response.total || 0,
+ }));
+ } else {
+ setSensitiveWordsList([]);
+ message.error(response?.message || "获取敏感词列表失败");
+ }
+ } catch (error) {
+ console.error("获取敏感词列表失败:", error);
setSensitiveWordsList([]);
- message.error(response?.message || "获取敏感词列表失败");
+ message.error("获取敏感词列表失败");
+ } finally {
+ setLoading(false);
}
- } catch (error) {
- console.error("获取敏感词列表失败:", error);
- setSensitiveWordsList([]);
- message.error("获取敏感词列表失败");
- } finally {
- setLoading(false);
- }
- };
+ };
- // 敏感词管理相关函数
- const handleToggleSensitiveWord = async (id: string) => {
- try {
- const response = await setSensitiveWordStatus({ id });
- if (response) {
- setSensitiveWordsList(prev =>
- prev.map(item =>
- item.id === id ? { ...item, enabled: !item.enabled } : item,
- ),
- );
- message.success("状态更新成功");
- } else {
- message.error(response?.message || "状态更新失败");
+ // 暴露方法给父组件
+ useImperativeHandle(ref, () => ({
+ fetchSensitiveWords,
+ }));
+
+ // 敏感词管理相关函数
+ const handleToggleSensitiveWord = async (id: string) => {
+ try {
+ const response = await setSensitiveWordStatus({ id });
+ if (response) {
+ setSensitiveWordsList(prev =>
+ prev.map(item =>
+ item.id === id
+ ? { ...item, status: item.status === 1 ? 0 : 1 }
+ : item,
+ ),
+ );
+ message.success("状态更新成功");
+ } else {
+ message.error(response?.message || "状态更新失败");
+ }
+ } catch (error) {
+ console.error("状态更新失败:", error);
+ message.error("状态更新失败");
}
- } catch (error) {
- console.error("状态更新失败:", error);
- message.error("状态更新失败");
- }
- };
+ };
- const handleEditSensitiveWord = (id: string) => {
- setEditingSensitiveWordId(id);
- setEditModalVisible(true);
- };
+ const handleEditSensitiveWord = (id: string) => {
+ setEditingSensitiveWordId(id);
+ setEditModalVisible(true);
+ };
- // 编辑弹窗成功回调
- const handleEditSuccess = () => {
- fetchSensitiveWords(); // 重新获取数据
- };
+ // 编辑弹窗成功回调
+ const handleEditSuccess = () => {
+ fetchSensitiveWords(); // 重新获取数据
+ };
- const handleDeleteSensitiveWord = async (id: string) => {
- try {
- const response = await deleteSensitiveWord(id);
- if (response) {
+ const handleDeleteSensitiveWord = async (id: string) => {
+ try {
+ await deleteSensitiveWord(id);
setSensitiveWordsList(prev => prev.filter(item => item.id !== id));
message.success("删除成功");
- } else {
- message.error(response?.message || "删除失败");
+ } catch (error) {
+ console.error("删除失败:", error);
+ message.error("删除失败");
}
- } catch (error) {
- console.error("删除失败:", error);
- message.error("删除失败");
- }
- };
+ };
+
+ // 搜索和筛选功能
+ const filteredSensitiveWords = sensitiveWordsList.filter(item => {
+ if (!searchValue) return true;
+ return (
+ item.title.toLowerCase().includes(searchValue.toLowerCase()) ||
+ item.keywords.toLowerCase().includes(searchValue.toLowerCase()) ||
+ item.content.toLowerCase().includes(searchValue.toLowerCase())
+ );
+ });
+
+ // 搜索处理函数
+ const handleSearch = (value: string) => {
+ setPagination(prev => ({ ...prev, current: 1 }));
+ fetchSensitiveWords({ keyword: value });
+ };
+
+ // 分页处理函数
+ const handlePageChange = (page: number, pageSize?: number) => {
+ setPagination(prev => ({
+ ...prev,
+ current: page,
+ pageSize: pageSize || prev.pageSize,
+ }));
+ };
+
+ // 组件挂载和分页变化时获取数据
+ useEffect(() => {
+ fetchSensitiveWords();
+ }, [pagination.current, pagination.pageSize]);
- // 搜索和筛选功能
- const filteredSensitiveWords = sensitiveWordsList.filter(item => {
- if (!searchValue) return true;
return (
- item.title.toLowerCase().includes(searchValue.toLowerCase()) ||
- item.keywords.toLowerCase().includes(searchValue.toLowerCase()) ||
- item.content.toLowerCase().includes(searchValue.toLowerCase())
- );
- });
+
+
+ setSearchValue(e.target.value)}
+ onSearch={handleSearch}
+ style={{ width: 300 }}
+ prefix={}
+ />
+ }>筛选
+
- // 搜索处理函数
- const handleSearch = (value: string) => {
- fetchSensitiveWords({ keyword: value });
- };
-
- // 组件挂载时获取数据
- useEffect(() => {
- fetchSensitiveWords();
- }, []);
-
- return (
-
-
- setSearchValue(e.target.value)}
- onSearch={handleSearch}
- style={{ width: 300 }}
- prefix={}
- />
- }>筛选
-
-
-
- {loading ? (
-
加载中...
- ) : filteredSensitiveWords.length === 0 ? (
-
暂无敏感词数据
- ) : (
- filteredSensitiveWords.map(item => (
-
-
-
{item.title}
-
- {item.keywords}
-
-
- {getOperationText(item.operation)}
+
+ {loading ? (
+
加载中...
+ ) : filteredSensitiveWords.length === 0 ? (
+
暂无敏感词数据
+ ) : (
+ filteredSensitiveWords.map(item => (
+
+
+
{item.title}
+
+ {getOperationText(item.operation)}
+
+
+
+
handleToggleSensitiveWord(item.id)}
+ className={styles.toggleSwitch}
+ />
+ }
+ onClick={() => handleEditSensitiveWord(item.id)}
+ className={styles.actionBtn}
+ />
+ handleDeleteSensitiveWord(item.id)}
+ okText="确定"
+ cancelText="取消"
+ okType="danger"
+ >
+ }
+ className={styles.actionBtn}
+ />
+
-
- handleToggleSensitiveWord(item.id)}
- className={styles.toggleSwitch}
- />
- }
- onClick={() => handleEditSensitiveWord(item.id)}
- className={styles.actionBtn}
- />
- }
- onClick={() => handleDeleteSensitiveWord(item.id)}
- className={styles.actionBtn}
- />
-
-
- ))
- )}
-
+ ))
+ )}
+
- {/* 编辑弹窗 */}
-
{
- setEditModalVisible(false);
- setEditingSensitiveWordId(null);
- }}
- onSuccess={handleEditSuccess}
- />
-
- );
-};
+ {/* 分页组件 */}
+
+
+ `第 ${range[0]}-${range[1]} 条/共 ${total} 条`
+ }
+ onChange={handlePageChange}
+ onShowSizeChange={handlePageChange}
+ />
+
+
+ {/* 编辑弹窗 */}
+
{
+ setEditModalVisible(false);
+ setEditingSensitiveWordId(null);
+ }}
+ onSuccess={handleEditSuccess}
+ />
+
+ );
+ },
+);
+
+SensitiveWordManagement.displayName = "SensitiveWordManagement";
export default SensitiveWordManagement;
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/components/management/index.module.scss b/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/components/management/index.module.scss
new file mode 100644
index 00000000..52943078
--- /dev/null
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/components/management/index.module.scss
@@ -0,0 +1,252 @@
+// 关键词管理样式
+.keywordContent {
+ .searchSection {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 24px;
+
+ :global(.ant-input-search) {
+ width: 300px;
+ }
+
+ :global(.ant-btn) {
+ height: 32px;
+ border-radius: 6px;
+ }
+ }
+
+ .keywordList {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ }
+
+ .keywordItem {
+ padding: 16px 20px;
+ background: #fff;
+ border: 1px solid #f0f0f0;
+ border-radius: 8px;
+ transition: all 0.3s;
+
+ &:hover {
+ border-color: #d9d9d9;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+ }
+
+ .itemContent {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ gap: 16px;
+
+ .leftSection {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+
+ .titleRow {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 4px;
+
+ .title {
+ font-size: 16px;
+ font-weight: 500;
+ color: #262626;
+ margin: 0;
+ }
+
+ .tags {
+ display: flex;
+ gap: 8px;
+
+ .matchTag,
+ .priorityTag {
+ font-size: 12px;
+ padding: 2px 8px;
+ border-radius: 12px;
+ background: #f0f0f0;
+ color: #666;
+ border: none;
+ }
+ }
+ }
+
+ .description {
+ font-size: 14px;
+ color: #666;
+ line-height: 1.5;
+ margin-bottom: 8px;
+ }
+
+ .replyTypeTag {
+ font-size: 12px;
+ padding: 2px 8px;
+ border-radius: 20px;
+ background: #fff;
+ color: #fff;
+ }
+ }
+
+ .rightSection {
+ display: flex;
+
+ align-items: center;
+ gap: 8px;
+
+ .toggleSwitch {
+ :global(.ant-switch) {
+ background-color: #d9d9d9;
+ }
+
+ :global(.ant-switch-checked) {
+ background-color: #1890ff;
+ }
+ }
+
+ .actionBtn {
+ width: 28px;
+ height: 28px;
+ padding: 0;
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ &:hover {
+ background-color: #f5f5f5;
+ }
+
+ .editIcon {
+ font-size: 14px;
+ color: #1890ff;
+ }
+
+ .deleteIcon {
+ font-size: 14px;
+ color: #ff4d4f;
+ }
+ }
+ }
+ }
+ }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+ .container {
+ padding: 16px;
+ }
+
+ .headerActions {
+ flex-direction: column;
+ align-items: stretch;
+
+ :global(.ant-btn) {
+ width: 100%;
+ }
+ }
+
+ .tabs {
+ flex-direction: column;
+ padding: 0;
+
+ .tab {
+ border-bottom: 1px solid #e8e8e8;
+ border-radius: 0;
+
+ &:last-child {
+ border-bottom: none;
+ }
+ }
+ }
+
+ .materialContent {
+ .searchSection {
+ flex-direction: column;
+ gap: 12px;
+ align-items: stretch;
+
+ :global(.ant-input-search) {
+ width: 100%;
+ }
+ }
+
+ .materialGrid {
+ grid-template-columns: 1fr;
+ gap: 16px;
+ }
+ }
+
+ .sensitiveContent {
+ .searchSection {
+ flex-direction: column;
+ gap: 12px;
+ align-items: stretch;
+
+ :global(.ant-input-search) {
+ width: 100%;
+ }
+ }
+
+ .sensitiveItem {
+ flex-direction: column;
+ align-items: stretch;
+ gap: 12px;
+
+ .itemContent {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 8px;
+
+ .categoryName {
+ min-width: auto;
+ }
+ }
+
+ .itemActions {
+ justify-content: flex-end;
+ }
+ }
+ }
+
+ .keywordContent {
+ .searchSection {
+ flex-direction: column;
+ gap: 12px;
+ align-items: stretch;
+
+ :global(.ant-input-search) {
+ width: 100%;
+ }
+ }
+
+ .keywordItem {
+ .itemContent {
+ flex-direction: column;
+ gap: 12px;
+
+ .leftSection {
+ .titleRow {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 8px;
+
+ .tags {
+ flex-wrap: wrap;
+ }
+ }
+ }
+
+ .rightSection {
+ flex-direction: row;
+ justify-content: flex-end;
+ align-items: center;
+ }
+ }
+ }
+ }
+}
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/components/modals/KeywordModal.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/components/modals/KeywordModal.tsx
index 209977fb..6dccd250 100644
--- a/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/components/modals/KeywordModal.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/components/modals/KeywordModal.tsx
@@ -7,14 +7,14 @@ import {
type KeywordAddRequest,
type KeywordUpdateRequest,
} from "../../api";
-
+import MetailSelection from "@/components/MetailSelection";
const { TextArea } = Input;
const { Option } = Select;
interface KeywordModalProps {
visible: boolean;
mode: "add" | "edit";
- keywordId?: string | null;
+ keywordId?: number | null;
onCancel: () => void;
onSuccess: () => void;
}
@@ -28,10 +28,12 @@ const KeywordModal: React.FC
= ({
}) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
+ const title = mode === "add" ? "添加关键词回复" : "编辑关键词回复";
+ const [selectedOptions, setSelectedOptions] = useState([]);
// 获取关键词详情
const fetchKeywordDetails = useCallback(
- async (id: string) => {
+ async (id: number) => {
try {
const response = await getKeywordDetails(id);
if (response) {
@@ -40,11 +42,13 @@ const KeywordModal: React.FC = ({
title: keyword.title,
keywords: keyword.keywords,
content: keyword.content,
- matchType: keyword.matchType,
- priority: keyword.priority,
+ type: keyword.type,
+ level: keyword.level,
replyType: keyword.replyType,
status: keyword.status,
+ metailGroups: keyword.metailGroups,
});
+ setSelectedOptions(keyword.metailGroupsOptions);
}
} catch (error) {
console.error("获取关键词详情失败:", error);
@@ -63,6 +67,7 @@ const KeywordModal: React.FC = ({
} else if (mode === "add") {
// 添加模式:重置表单
form.resetFields();
+ setSelectedOptions([]);
}
}
}, [visible, mode, keywordId, fetchKeywordDetails, form]);
@@ -76,10 +81,11 @@ const KeywordModal: React.FC = ({
title: values.title,
keywords: values.keywords,
content: values.content,
- matchType: values.matchType,
- priority: values.priority,
+ type: values.type,
+ level: values.level,
replyType: values.replyType,
status: values.status || "1",
+ metailGroups: values.metailGroups,
};
const response = await addKeyword(data);
@@ -97,10 +103,11 @@ const KeywordModal: React.FC = ({
title: values.title,
keywords: values.keywords,
content: values.content,
- matchType: values.matchType,
- priority: values.priority,
+ type: values.type,
+ level: values.level,
replyType: values.replyType,
status: values.status,
+ metailGroups: values.metailGroups,
};
const response = await updateKeyword(data);
@@ -123,11 +130,42 @@ const KeywordModal: React.FC = ({
const handleCancel = () => {
form.resetFields();
+ setSelectedOptions([]);
onCancel();
};
- const title = mode === "add" ? "添加关键词回复" : "编辑关键词回复";
+ const handSelectMaterial = (options: any[]) => {
+ if (options.length === 0) {
+ form.setFieldsValue({
+ metailGroups: [],
+ });
+ } else {
+ // 在单选模式下,只取第一个选项的ID
+ form.setFieldsValue({
+ metailGroups: options.map(v => v.id),
+ });
+ }
+ setSelectedOptions(options);
+ };
+ // 监听表单值变化
+ const handleFormValuesChange = (changedValues: any) => {
+ // 当回复类型切换时,清空素材选择
+ if (changedValues.replyType !== undefined) {
+ setSelectedOptions([]);
+ if (changedValues.replyType === 1) {
+ // 切换到自定义回复时,清空materialId
+ form.setFieldsValue({
+ materialId: null,
+ });
+ } else {
+ // 切换到素材回复时,清空content
+ form.setFieldsValue({
+ content: null,
+ });
+ }
+ }
+ };
return (
= ({
form={form}
layout="vertical"
onFinish={handleSubmit}
+ onValuesChange={handleFormValuesChange}
initialValues={{
- status: "1",
- matchType: "模糊匹配",
- priority: "1",
- replyType: "text",
+ status: 1,
+ type: "模糊匹配",
+ level: 1,
+ replyType: 0,
}}
>
= ({
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ {form.getFieldValue("replyType") === 1 ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+
+
+
+
+
@@ -213,8 +265,8 @@ const KeywordModal: React.FC = ({
rules={[{ required: true, message: "请选择状态" }]}
>
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/components/modals/SensitiveWordModal.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/components/modals/SensitiveWordModal.tsx
index 22628f4e..5e125b4a 100644
--- a/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/components/modals/SensitiveWordModal.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/components/modals/SensitiveWordModal.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from "react";
+import React, { useState, useEffect, useCallback } from "react";
import { Modal, Form, Input, Button, message, Select } from "antd";
import {
addSensitiveWord,
@@ -30,34 +30,40 @@ const SensitiveWordModal: React.FC = ({
const [loading, setLoading] = useState(false);
// 获取敏感词详情
- const fetchSensitiveWordDetails = async (id: string) => {
- try {
- const response = await getSensitiveWordDetails(id);
- if (response) {
- const sensitiveWord = response;
- form.setFieldsValue({
- title: sensitiveWord.title,
- keywords: sensitiveWord.keywords,
- content: sensitiveWord.content,
- operation: sensitiveWord.operation,
- status: sensitiveWord.status,
- });
+ const fetchSensitiveWordDetails = useCallback(
+ async (id: string) => {
+ try {
+ const response = await getSensitiveWordDetails(id);
+ if (response) {
+ const sensitiveWord = response;
+ form.setFieldsValue({
+ title: sensitiveWord.title,
+ keywords: sensitiveWord.keywords,
+ content: sensitiveWord.content,
+ operation: sensitiveWord.operation,
+ status: sensitiveWord.status,
+ });
+ }
+ } catch (error) {
+ console.error("获取敏感词详情失败:", error);
+ message.error("获取敏感词详情失败");
}
- } catch (error) {
- console.error("获取敏感词详情失败:", error);
- message.error("获取敏感词详情失败");
- }
- };
+ },
+ [form],
+ );
- // 当弹窗打开且为编辑模式时,获取详情
+ // 当弹窗打开时处理数据
useEffect(() => {
- if (visible && mode === "edit" && sensitiveWordId) {
- fetchSensitiveWordDetails(sensitiveWordId);
- } else if (visible && mode === "add") {
- // 添加模式时重置表单
- form.resetFields();
+ if (visible) {
+ if (mode === "edit" && sensitiveWordId) {
+ // 编辑模式:获取详情
+ fetchSensitiveWordDetails(sensitiveWordId);
+ } else if (mode === "add") {
+ // 添加模式:重置表单
+ form.resetFields();
+ }
}
- }, [visible, mode, sensitiveWordId]);
+ }, [visible, mode, sensitiveWordId, fetchSensitiveWordDetails, form]);
const handleSubmit = async (values: any) => {
try {
@@ -160,11 +166,11 @@ const SensitiveWordModal: React.FC = ({
rules={[{ required: true, message: "请选择操作类型" }]}
>
@@ -174,8 +180,8 @@ const SensitiveWordModal: React.FC = ({
rules={[{ required: true, message: "请选择状态" }]}
>
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/index.module.scss b/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/index.module.scss
index 72934dce..7fa37698 100644
--- a/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/index.module.scss
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/index.module.scss
@@ -291,133 +291,6 @@
}
}
-// 关键词管理样式
-.keywordContent {
- .searchSection {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 24px;
-
- :global(.ant-input-search) {
- width: 300px;
- }
-
- :global(.ant-btn) {
- height: 32px;
- border-radius: 6px;
- }
- }
-
- .keywordList {
- display: flex;
- flex-direction: column;
- gap: 12px;
- }
-
- .keywordItem {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- padding: 16px 20px;
- background: #fff;
- border: 1px solid #f0f0f0;
- border-radius: 8px;
- transition: all 0.3s;
-
- &:hover {
- border-color: #d9d9d9;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
- }
-
- .itemContent {
- display: flex;
- flex-direction: column;
- gap: 8px;
- flex: 1;
-
- .title {
- font-size: 16px;
- font-weight: 500;
- color: #262626;
- margin: 0;
- }
-
- .tags {
- display: flex;
- gap: 8px;
- margin-bottom: 4px;
-
- .matchTag,
- .priorityTag {
- font-size: 12px;
- padding: 2px 8px;
- border-radius: 12px;
- background: #f0f0f0;
- color: #666;
- border: none;
- }
- }
-
- .description {
- font-size: 14px;
- color: #666;
- line-height: 1.5;
- margin-bottom: 8px;
- }
-
- .replyTypeTag {
- font-size: 12px;
- padding: 2px 8px;
- border-radius: 4px;
- border: none;
- align-self: flex-start;
- }
- }
-
- .itemActions {
- display: flex;
- align-items: center;
- gap: 8px;
- margin-left: 16px;
-
- .toggleSwitch {
- :global(.ant-switch) {
- background-color: #d9d9d9;
- }
-
- :global(.ant-switch-checked) {
- background-color: #1890ff;
- }
- }
-
- .actionBtn {
- width: 28px;
- height: 28px;
- padding: 0;
- border-radius: 4px;
- display: flex;
- align-items: center;
- justify-content: center;
-
- &:hover {
- background-color: #f5f5f5;
- }
-
- .editIcon {
- font-size: 14px;
- color: #1890ff;
- }
-
- .deleteIcon {
- font-size: 14px;
- color: #ff4d4f;
- }
- }
- }
- }
-}
-
// 弹窗中的图片上传组件样式
:global(.material-cover-upload) {
.uploadContainer {
@@ -525,82 +398,4 @@
}
}
}
-
- .materialContent {
- .searchSection {
- flex-direction: column;
- gap: 12px;
- align-items: stretch;
-
- :global(.ant-input-search) {
- width: 100%;
- }
- }
-
- .materialGrid {
- grid-template-columns: 1fr;
- gap: 16px;
- }
- }
-
- .sensitiveContent {
- .searchSection {
- flex-direction: column;
- gap: 12px;
- align-items: stretch;
-
- :global(.ant-input-search) {
- width: 100%;
- }
- }
-
- .sensitiveItem {
- flex-direction: column;
- align-items: stretch;
- gap: 12px;
-
- .itemContent {
- flex-direction: column;
- align-items: flex-start;
- gap: 8px;
-
- .categoryName {
- min-width: auto;
- }
- }
-
- .itemActions {
- justify-content: flex-end;
- }
- }
- }
-
- .keywordContent {
- .searchSection {
- flex-direction: column;
- gap: 12px;
- align-items: stretch;
-
- :global(.ant-input-search) {
- width: 100%;
- }
- }
-
- .keywordItem {
- flex-direction: column;
- align-items: stretch;
- gap: 12px;
-
- .itemContent {
- .tags {
- flex-wrap: wrap;
- }
- }
-
- .itemActions {
- justify-content: flex-end;
- margin-left: 0;
- }
- }
- }
}
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/index.tsx
index 4f8b4fa0..ead8b0bc 100644
--- a/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/index.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/index.tsx
@@ -22,6 +22,7 @@ const ContentManagement: React.FC = () => {
// 引用管理组件
const materialManagementRef = useRef(null);
const keywordManagementRef = useRef(null);
+ const sensitiveWordManagementRef = useRef(null);
const tabs = [
{ key: "material", label: "素材资源库" },
@@ -44,12 +45,20 @@ const ContentManagement: React.FC = () => {
// 弹窗成功回调
const handleModalSuccess = () => {
+ console.log("handleModalSuccess");
// 刷新素材列表
if (materialManagementRef.current?.fetchMaterials) {
+ console.log("刷新素材列表");
materialManagementRef.current.fetchMaterials();
}
+ // 刷新敏感词列表
+ if (sensitiveWordManagementRef.current?.fetchSensitiveWords) {
+ console.log("刷新敏感词列表");
+ sensitiveWordManagementRef.current.fetchSensitiveWords();
+ }
// 刷新关键词列表
if (keywordManagementRef.current?.fetchKeywords) {
+ console.log("刷新关键词列表");
keywordManagementRef.current.fetchKeywords();
}
};
@@ -61,7 +70,12 @@ const ContentManagement: React.FC = () => {
);
case "sensitive":
- return ;
+ return (
+
+ );
case "keyword":
return (
@@ -89,12 +103,12 @@ const ContentManagement: React.FC = () => {
>
添加素材
- } onClick={handleAddKeyword}>
- 添加关键词回复
-
} onClick={handleAddSensitiveWord}>
添加敏感词
+ } onClick={handleAddKeyword}>
+ 添加关键词回复
+
}
/>
@@ -136,6 +150,7 @@ const ContentManagement: React.FC = () => {
setKeywordModalVisible(false)}
onSuccess={handleModalSuccess}
/>
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.module.scss b/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.module.scss
index 2eb4278c..aee6f4b5 100644
--- a/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.module.scss
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.module.scss
@@ -114,9 +114,9 @@
// 标签页
.tabs {
+ padding: 20px;
display: flex;
- gap: 0;
- border-bottom: 1px solid #f0f0f0;
+ gap: 10px;
.tab {
padding: 12px 24px;
@@ -127,15 +127,6 @@
color: #8c8c8c;
cursor: pointer;
transition: all 0.3s;
-
- &:hover {
- color: #1890ff;
- }
-
- &.activeTab {
- color: #1890ff;
- border-bottom-color: #1890ff;
- }
}
}
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.tsx
index 07a71c7f..1eaf5cd2 100644
--- a/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.tsx
@@ -1,13 +1,8 @@
import React, { useState, useEffect } from "react";
import PowerNavigation from "@/components/PowerNavtion";
-import {
- SearchOutlined,
- FilterOutlined,
- MessageOutlined,
- PhoneOutlined,
-} from "@ant-design/icons";
+import { SearchOutlined, FilterOutlined } from "@ant-design/icons";
import styles from "./index.module.scss";
-import { Button, Input, Row, Col, Pagination, Spin, message } from "antd";
+import { Button, Input, Table, message } from "antd";
import { getContactList } from "@/pages/pc/ckbox/weChat/api";
import { ContractData } from "@/pages/pc/ckbox/data";
import Layout from "@/components/Layout/LayoutFiexd";
@@ -196,132 +191,129 @@ const CustomerManagement: React.FC = () => {
刷新
- {/* 标签页 */}
+ {/* 标签按钮组 */}
{tabs.map(tab => (
- setActiveTab(tab.key)}
>
- {tab.label} ({tab.count})
-
+ {tab.label}
+
+ {tab.count}
+
+
))}
>
}
- footer={
-
-
- `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
- }
- onChange={(page, pageSize) => {
- loadContacts(page, pageSize || pagination.pageSize);
- }}
- onShowSizeChange={(current, size) => {
- loadContacts(1, size);
- }}
- pageSizeOptions={["6", "12", "24", "48"]}
- />
-
- }
+ footer={null}
>
- {/* 联系人卡片列表 */}
-
- {loading ? (
-
- ) : filteredContacts.length === 0 ? (
-
- ) : (
- <>
-
- {filteredContacts.map(contact => (
-
-
-
-
-
-
-
- {contact.conRemark ||
- contact.nickname ||
- contact.alias ||
- "未知用户"}
-
-
- 客户 {"·"} {contact.desc || "未设置公司"}
-
-
-
-
-
-
-
-
- 电话:{" "}
- {contact.phone || "未设置电话"}
-
-
- 地区:{" "}
- {contact.region || contact.city || "未设置地区"}
-
-
- 微信ID:{" "}
- {contact.wechatId}
-
-
-
-
-
-
- {contact?.labels?.map(
- (tag: string, index: number) => (
-
- {tag}
-
- ),
- )}
-
-
-
-
-
-
- 聊天
-
-
+ {/* 联系人表格 */}
+
record.id || record.serverId}
+ loading={loading}
+ dataSource={filteredContacts as any}
+ columns={[
+ {
+ title: "客户姓名",
+ key: "name",
+ render: (_: any, record: any) => {
+ const displayName =
+ record.conRemark ||
+ record.nickname ||
+ record.alias ||
+ "未知用户";
+ return (
+
+
+
+
{displayName}
+
+ 客户 · {record.desc || "未设置公司"}
+
-
- ))}
-
- >
- )}
-
+
+ );
+ },
+ },
+ {
+ title: "RFM评分",
+ dataIndex: "rfmScore",
+ key: "rfmScore",
+ width: 100,
+ render: (val: any) => val ?? "-",
+ },
+ {
+ title: "电话",
+ dataIndex: "phone",
+ key: "phone",
+ width: 180,
+ render: (val: string) => val || "未设置电话",
+ },
+ {
+ title: "微信号",
+ dataIndex: "wechatId",
+ key: "wechatId",
+ width: 200,
+ },
+ {
+ title: "地址",
+ key: "address",
+ ellipsis: true,
+ render: (_: any, record: any) =>
+ record.region || record.city || "未设置地区",
+ },
+ {
+ title: "标签",
+ key: "labels",
+ render: (_: any, record: any) => (
+
+ {(record?.labels || []).map(
+ (tag: string, index: number) => (
+
+ {tag}
+
+ ),
+ )}
+
+ ),
+ },
+ {
+ title: "操作",
+ key: "action",
+ width: 120,
+ render: () => (
+
+ 聊天
+
+ ),
+ },
+ ]}
+ pagination={{
+ current: pagination.current,
+ pageSize: pagination.pageSize,
+ total: pagination.total,
+ showSizeChanger: true,
+ showQuickJumper: true,
+ showTotal: (total: number, range: [number, number]) =>
+ `第 ${range[0]}-${range[1]} 条,共 ${total} 条`,
+ pageSizeOptions: ["6", "12", "24", "48"],
+ }}
+ onChange={(pager: any) => {
+ const nextCurrent = pager.current || 1;
+ const nextSize = pager.pageSize || pagination.pageSize;
+ loadContacts(nextCurrent, nextSize);
+ }}
+ />
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/index.data.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/index.data.tsx
index a3468602..34c14dd2 100644
--- a/Touchkebao/src/pages/pc/ckbox/powerCenter/index.data.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/index.data.tsx
@@ -1,13 +1,4 @@
-import {
- AimOutlined,
- ThunderboltOutlined,
- RiseOutlined,
- TeamOutlined,
- CommentOutlined,
- FileTextOutlined,
- SoundOutlined,
- EditOutlined,
-} from "@ant-design/icons";
+import { TeamOutlined, CommentOutlined, BookOutlined } from "@ant-design/icons";
// 数据类型定义
export interface FeatureCard {
@@ -16,89 +7,97 @@ export interface FeatureCard {
description: string;
icon: React.ReactNode;
color: string;
+ tag: string;
+ features: string[];
path?: string;
- isNew?: boolean;
- isHot?: boolean;
}
-export interface FeatureCategory {
+export interface KPIData {
id: string;
- title: string;
- icon: React.ReactNode;
- color: string;
- count: number;
- features: FeatureCard[];
+ value: string;
+ label: string;
+ trend?: {
+ icon: string;
+ text: string;
+ };
}
-// 功能数据
-export const featureCategories: FeatureCategory[] = [
+// 功能数据 - 匹配图片中的三个核心模块
+export const featureCategories: FeatureCard[] = [
{
- id: "core",
- title: "核心功能",
- icon: ,
+ id: "customer-management",
+ title: "客户好友管理",
+ description: "管理客户关系,维护好友信息,查看沟通记录,提升客户满意度",
+ icon: ,
color: "#1890ff",
- count: 2,
+ tag: "核心功能",
features: [
- {
- id: "customer-management",
- title: "客户好友管理",
- description: "管理客户关系,维护好友信息,提升客户满意度",
- icon: ,
- color: "#1890ff",
- path: "/pc/powerCenter/customer-management",
- isHot: true,
- },
- {
- id: "communication-record",
- title: "沟通记录",
- description: "记录和分析所有客户沟通历史,优化服务质量",
- icon: ,
- color: "#52c41a",
- path: "/pc/powerCenter/communication-record",
- },
+ "RFM价值评分系统",
+ "多维度精准筛选",
+ "完整聊天记录查看",
+ "客户详情页面",
],
+ path: "/pc/powerCenter/customer-management",
},
{
- id: "ai",
- title: "AI智能功能",
- icon: ,
+ id: "ai-reception",
+ title: "AI接待设置",
+ description: "配置AI自动回复,智能推送策略,提升接待效率和客户体验",
+ icon: ,
color: "#722ed1",
- count: 2,
+ tag: "AI智能",
features: [
- {
- id: "ai-training",
- title: "AI模型训练",
- description: "训练专属AI模型,提升智能服务能力",
- icon: ,
- color: "#fa8c16",
- path: "/pc/powerCenter/ai-training",
- isNew: true,
- },
- {
- id: "auto-greeting",
- title: "自动问候",
- description: "设置智能问候规则,自动化客户接待流程",
- icon: ,
- color: "#722ed1",
- path: "/pc/powerCenter/auto-greeting",
- },
+ "自动欢迎语设置",
+ "AI智能推送策略",
+ "标签化精准推送",
+ "接待模式切换",
],
+ path: "/pc/powerCenter/ai-reception",
},
{
- id: "marketing",
- title: "营销管理",
- icon: ,
+ id: "content-library",
+ title: "AI内容库配置",
+ description: "管理AI内容库,配置调用权限,优化AI推送效果和内容质量",
+ icon: ,
color: "#52c41a",
- count: 1,
+ tag: "内容管理",
features: [
- {
- id: "content-management",
- title: "内容管理",
- description: "管理营销内容,素材库,提升内容创作效率",
- icon: ,
- color: "#722ed1",
- path: "/pc/powerCenter/content-management",
- },
+ "多库管理与分类",
+ "AI调用权限配置",
+ "内容检索规则设置",
+ "手动内容上传",
],
+ path: "/pc/powerCenter/content-library",
+ },
+];
+
+// KPI统计数据
+export const kpiData: KPIData[] = [
+ {
+ id: "total-customers",
+ value: "1,234",
+ label: "总客户数",
+ trend: {
+ icon: "↑",
+ text: "12% 本月",
+ },
+ },
+ {
+ id: "active-customers",
+ value: "856",
+ label: "活跃客户",
+ trend: {
+ icon: "↑",
+ text: "8% 本月",
+ },
+ },
+ {
+ id: "assigned-users",
+ value: "342",
+ label: "当前客服分配用户数",
+ trend: {
+ icon: "",
+ text: "当前登录客服",
+ },
},
];
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/index.module.scss b/Touchkebao/src/pages/pc/ckbox/powerCenter/index.module.scss
index 79089122..8209d4d6 100644
--- a/Touchkebao/src/pages/pc/ckbox/powerCenter/index.module.scss
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/index.module.scss
@@ -1,160 +1,188 @@
.powerCenter {
padding: 40px;
- background: linear-gradient(180deg, #f8fafc 0%, #ffffff 100%);
+ background: #ffffff;
min-height: 100vh;
- // 功能分类区域
- .categorySection {
- margin-bottom: 48px;
+ // 页面标题区域
+ .pageHeader {
+ text-align: center;
+ margin-bottom: 60px;
- .categoryHeader {
- display: flex;
- align-items: center;
- margin-bottom: 24px;
- padding: 0 8px;
-
- .categoryIcon {
- width: 48px;
- height: 48px;
- color: #ffffff;
- border-radius: 50%;
+ .titleSection {
+ .mainTitle {
display: flex;
align-items: center;
justify-content: center;
- margin-right: 16px;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ gap: 16px;
+ margin-bottom: 16px;
- .anticon {
- font-size: 24px;
- color: white;
+ .titleIcon {
+ font-size: 32px;
+ color: #722ed1;
+ }
+
+ h1 {
+ font-size: 48px;
+ font-weight: 700;
+ color: #1a1a1a;
+ margin: 0;
}
}
- .categoryInfo {
+ .subtitle {
+ font-size: 18px;
+ color: #666;
+ margin: 0;
+ font-weight: 400;
+ }
+ }
+ }
+
+ // 核心功能模块
+ .coreFeatures {
+ margin-bottom: 60px;
+
+ .featureCard {
+ background: white;
+ border-radius: 16px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+ transition: all 0.3s ease;
+ cursor: pointer;
+ overflow: hidden;
+ height: 400px;
+
+ &:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
+ }
+
+ .cardContent {
+ padding: 32px;
+ height: 100%;
display: flex;
- align-items: center;
- gap: 10px;
- .categoryTitle {
- font-size: 24px;
- font-weight: 600;
- color: #1a1a1a;
- margin: 0 0 4px 0;
+ flex-direction: column;
+
+ .cardHeader {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ margin-bottom: 24px;
+
+ .cardIcon {
+ width: 64px;
+ height: 64px;
+ border-radius: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(24, 144, 255, 0.1);
+ }
+
+ .cardTag {
+ color: white;
+ font-size: 12px;
+ font-weight: 500;
+ padding: 6px 12px;
+ border-radius: 16px;
+ height: 28px;
+ line-height: 16px;
+ display: flex;
+ align-items: center;
+ }
}
- .categoryCount {
- font-size: 12px;
- color: #666;
- background: #f0f0f0;
- border-radius: 12px;
- border: 1px solid #e0e0e0;
- height: 24px;
- line-height: 20px;
- padding: 0 10px;
+ .cardInfo {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+
+ .cardTitle {
+ font-size: 20px;
+ font-weight: 600;
+ color: #1a1a1a;
+ margin: 0 0 12px 0;
+ line-height: 1.3;
+ }
+
+ .cardDescription {
+ font-size: 14px;
+ color: #666;
+ line-height: 1.6;
+ margin: 0 0 20px 0;
+ }
+
+ .featureList {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ flex: 1;
+
+ li {
+ font-size: 13px;
+ color: #666;
+ line-height: 1.5;
+ margin-bottom: 8px;
+ position: relative;
+ padding-left: 16px;
+
+ &::before {
+ content: "•";
+ color: #1890ff;
+ font-weight: bold;
+ position: absolute;
+ left: 0;
+ }
+ }
+ }
}
}
}
+ }
- .featureCards {
- .featureCard {
- border-radius: 16px;
- border: none;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- transition: all 0.3s ease;
- cursor: pointer;
- position: relative;
- overflow: hidden;
- background: white;
- padding: 20px;
- box-sizing: border-box;
- margin-bottom: 16px;
+ // KPI统计区域
+ .kpiSection {
+ margin-bottom: 40px;
- &:hover {
- transform: translateY(-2px);
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
+ .kpiCard {
+ background: white;
+ border-radius: 12px;
+ padding: 24px;
+ text-align: center;
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.18);
+ transition: all 0.3s ease;
+
+ &:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+ }
+
+ .kpiValue {
+ font-size: 32px;
+ font-weight: 700;
+ color: #1a1a1a;
+ margin-bottom: 8px;
+ }
+
+ .kpiLabel {
+ font-size: 14px;
+ color: #666;
+ margin-bottom: 8px;
+ }
+
+ .kpiTrend {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+
+ .trendIcon {
+ color: #52c41a;
+ font-weight: bold;
}
- .cardContent {
- .cardHeader {
- position: relative;
- display: flex;
- justify-content: space-between;
- margin-bottom: 10px;
- .cardIcon {
- color: #ffffff;
- width: 48px;
- height: 48px;
- border-radius: 12px;
- display: flex;
- align-items: center;
- justify-content: center;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
- .anticon {
- font-size: 24px;
- color: white;
- }
- }
- .badge {
- background: #ff6b35;
- color: white;
- font-size: 11px;
- font-weight: 500;
- padding: 4px 8px;
- border-radius: 12px;
- z-index: 2;
- box-shadow: 0 1px 4px rgba(255, 107, 53, 0.3);
- height: 24px;
- // 新功能标签样式
- &[data-type="new"] {
- background: #52c41a;
- box-shadow: 0 1px 4px rgba(82, 196, 26, 0.3);
- }
- }
- }
-
- .cardInfo {
- flex: 1;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
-
- .cardTitle {
- font-size: 16px;
- font-weight: 600;
- color: #1a1a1a;
- margin: 0 0 8px 0;
- line-height: 1.3;
- }
-
- .cardDescription {
- font-size: 14px;
- color: #666;
- line-height: 1.5;
- margin: 0 0 12px 0;
- display: -webkit-box;
- -webkit-line-clamp: 2;
- line-clamp: 2;
- -webkit-box-orient: vertical;
- overflow: hidden;
- flex: 1;
- }
-
- .cardAction {
- display: flex;
- justify-content: space-between;
- color: #979797;
- font-size: 12px;
- font-weight: 500;
-
- .arrow {
- font-size: 14px;
- transition: transform 0.3s ease;
- }
-
- &:hover .arrow {
- transform: translateX(4px);
- }
- }
- }
+ .trendText {
+ font-size: 12px;
+ color: #52c41a;
}
}
}
@@ -180,63 +208,65 @@
.powerCenter {
padding: 32px 24px;
- .categorySection {
- .categoryHeader {
- .categoryIcon {
- width: 44px;
- height: 44px;
+ .pageHeader {
+ margin-bottom: 40px;
- .anticon {
- font-size: 22px;
+ .titleSection {
+ .mainTitle {
+ h1 {
+ font-size: 36px;
+ }
+
+ .titleIcon {
+ font-size: 28px;
}
}
- .categoryInfo {
- .categoryTitle {
- font-size: 22px;
+ .subtitle {
+ font-size: 16px;
+ }
+ }
+ }
+
+ .coreFeatures {
+ .featureCard {
+ height: 360px;
+
+ .cardContent {
+ padding: 24px;
+
+ .cardHeader {
+ .cardIcon {
+ width: 56px;
+ height: 56px;
+ }
+ }
+
+ .cardInfo {
+ .cardTitle {
+ font-size: 18px;
+ }
+
+ .cardDescription {
+ font-size: 13px;
+ }
+
+ .featureList {
+ li {
+ font-size: 12px;
+ }
+ }
}
}
}
+ }
- .featureCards {
- .featureCard {
- height: 180px;
- width: 260px;
- padding: 20px;
+ .kpiSection {
+ .kpiCard {
+ padding: 20px;
- .cardContent {
- .cardIcon {
- width: 44px;
- height: 44px;
- margin: 18px auto 14px;
-
- .anticon {
- font-size: 22px;
- }
- }
-
- .cardInfo {
- padding: 0 14px 14px;
-
- .cardTitle {
- font-size: 15px;
- margin-bottom: 6px;
- }
-
- .cardDescription {
- font-size: 11px;
- margin-bottom: 10px;
- }
-
- .cardAction {
- font-size: 11px;
-
- .arrow {
- font-size: 12px;
- }
- }
- }
- }
+ .kpiValue {
+ font-size: 28px;
}
}
}
@@ -247,76 +277,89 @@
.powerCenter {
padding: 24px 16px;
- .categorySection {
+ .pageHeader {
margin-bottom: 32px;
- .categoryHeader {
- .categoryIcon {
- width: 40px;
- height: 40px;
+ .titleSection {
+ .mainTitle {
+ flex-direction: column;
+ gap: 8px;
- .anticon {
- font-size: 20px;
+ h1 {
+ font-size: 28px;
+ }
+
+ .titleIcon {
+ font-size: 24px;
}
}
- .categoryInfo {
- .categoryTitle {
- font-size: 20px;
+ .subtitle {
+ font-size: 14px;
+ }
+ }
+ }
+
+ .coreFeatures {
+ .featureCard {
+ height: 320px;
+ margin-bottom: 16px;
+
+ .cardContent {
+ padding: 20px;
+
+ .cardHeader {
+ margin-bottom: 16px;
+
+ .cardIcon {
+ width: 48px;
+ height: 48px;
+ }
+
+ .cardTag {
+ font-size: 11px;
+ padding: 4px 8px;
+ }
}
- .categoryCount {
- font-size: 12px;
- padding: 3px 10px;
+ .cardInfo {
+ .cardTitle {
+ font-size: 16px;
+ margin-bottom: 8px;
+ }
+
+ .cardDescription {
+ font-size: 12px;
+ margin-bottom: 16px;
+ }
+
+ .featureList {
+ li {
+ font-size: 11px;
+ margin-bottom: 6px;
+ }
+ }
}
}
}
+ }
- .featureCards {
- .featureCard {
- height: 160px;
- width: 240px;
- padding: 16px;
+ .kpiSection {
+ .kpiCard {
+ padding: 16px;
+ margin-bottom: 12px;
- .cardContent {
- .badge {
- top: 10px;
- right: 10px;
- font-size: 10px;
- padding: 3px 6px;
- }
+ .kpiValue {
+ font-size: 24px;
+ }
- .cardIcon {
- width: 40px;
- height: 40px;
- margin: 16px auto 12px;
+ .kpiLabel {
+ font-size: 12px;
+ }
- .anticon {
- font-size: 20px;
- }
- }
-
- .cardInfo {
- padding: 0 12px 12px;
-
- .cardTitle {
- font-size: 14px;
- margin-bottom: 6px;
- }
-
- .cardDescription {
- font-size: 10px;
- margin-bottom: 8px;
- }
-
- .cardAction {
- font-size: 10px;
-
- .arrow {
- font-size: 11px;
- }
- }
- }
+ .kpiTrend {
+ .trendText {
+ font-size: 11px;
}
}
}
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/index.tsx
index d0f00002..d6d09e5d 100644
--- a/Touchkebao/src/pages/pc/ckbox/powerCenter/index.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/index.tsx
@@ -1,10 +1,21 @@
import React from "react";
import { useNavigate } from "react-router-dom";
import styles from "./index.module.scss";
-import { FeatureCard, featureCategories } from "./index.data.tsx";
+import { FeatureCard, featureCategories, kpiData } from "./index.data";
import { Col, Row } from "antd";
+import {
+ UserOutlined,
+ TeamOutlined,
+ UsergroupAddOutlined,
+} from "@ant-design/icons";
+
const PowerCenter: React.FC = () => {
const navigate = useNavigate();
+ const getKpiBg = (id: string) => {
+ if (id === "total-customers") return "#1890ff";
+ if (id === "active-customers") return "#52c41a";
+ return "#722ed1";
+ };
const handleCardClick = (card: FeatureCard) => {
if (card.path) {
@@ -14,79 +25,127 @@ const PowerCenter: React.FC = () => {
return (
- {/* 功能分类展示 */}
- {featureCategories.map(category => (
-
- {/* 分类标题 */}
-
-
- {category.icon}
-
-
-
{category.title}
-
- {category.count}个功能
-
-
+ {/* 页面标题区域 */}
+
+
+
+
+ AI智能营销·一站式客户管理·高效业务增长
+
+
+
- {/* 功能卡片 */}
-
-
- {category.features.map(card => (
-
+ {/* KPI统计区域(置顶,按图展示) */}
+
+
+ {kpiData.map(kpi => (
+
+
+
+
+
+ {kpi.label}
+
+
+ {kpi.value}
+
+ {kpi.trend && (
+
+
+ {kpi.trend.icon}
+
+
+ {kpi.trend.text}
+
+
+ )}
+
handleCardClick(card)}
+ aria-hidden
+ style={{
+ width: 56,
+ height: 56,
+ borderRadius: 12,
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ background: getKpiBg(kpi.id),
+ color: "#fff",
+ boxShadow: "0 6px 14px rgba(0,0,0,0.18)",
+ }}
>
-
-
-
- {card.icon}
-
- {/* 热门/新功能标签 */}
- {(card.isHot || card.isNew) && (
-
- {card.isHot ? "热门" : "新功能"}
-
- )}
+ {kpi.id === "total-customers" && (
+
+ )}
+ {kpi.id === "active-customers" && (
+
+ )}
+ {kpi.id !== "total-customers" &&
+ kpi.id !== "active-customers" && (
+
+ )}
+
+
+
+
+ ))}
+
+
- {/* 功能图标 */}
-
-
- {/* 功能信息 */}
-
-
{card.title}
-
- {card.description}
-
-
- 点击进入功能
- →
-
-
+ {/* 核心功能模块 */}
+
+
+ {featureCategories.map(card => (
+
+ handleCardClick(card)}
+ >
+
+
+
{card.icon}
+
+ {card.tag}
-
- ))}
-
-
-
- ))}
+
+
{card.title}
+
{card.description}
+
+
+ {card.features.map((feature, index) => (
+ - {feature}
+ ))}
+
+
+
+
+
+ ))}
+
+
{/* 页面底部 */}
触客宝 AI私域营销系统 - 让每一次沟通都更有价值
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ChatRecordSearch/index.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ChatRecordSearch/index.module.scss
new file mode 100644
index 00000000..09eaa69c
--- /dev/null
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ChatRecordSearch/index.module.scss
@@ -0,0 +1,27 @@
+.chatRecordSearch {
+ flex: 1;
+ display: flex;
+ justify-content: space-between;
+ .searchContentContainer {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-size: 12px;
+ .timeRange {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ .timeRangeTitle {
+ width: 80px;
+ }
+ }
+ .searchContent {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ .searchContentTitle {
+ width: 100px;
+ }
+ }
+ }
+}
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ChatRecordSearch/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ChatRecordSearch/index.tsx
new file mode 100644
index 00000000..9bb04d51
--- /dev/null
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ChatRecordSearch/index.tsx
@@ -0,0 +1,86 @@
+import React, { useState } from "react";
+import { Button, Input, DatePicker, message } from "antd";
+import dayjs from "dayjs";
+import { CloseOutlined } from "@ant-design/icons";
+import { useWeChatStore } from "@/store/module/weChat/weChat";
+import styles from "./index.module.scss";
+const { RangePicker } = DatePicker;
+
+const ChatRecordSearch: React.FC = () => {
+ const [searchContent, setSearchContent] = useState
("");
+ const [dateRange, setDateRange] = useState<[dayjs.Dayjs, dayjs.Dayjs] | null>(
+ null,
+ );
+ const [loading, setLoading] = useState(false);
+ const SearchMessage = useWeChatStore(state => state.SearchMessage);
+ const updateShowChatRecordModel = useWeChatStore(
+ state => state.updateShowChatRecordModel,
+ );
+ // 执行查找
+ const handleSearch = async () => {
+ if (!dateRange) {
+ message.warning("请选择时间范围");
+ return;
+ }
+
+ try {
+ setLoading(true);
+ const [From, To] = dateRange;
+ const searchData = {
+ From: From.unix() * 1000,
+ To: To.unix() * 1000,
+ keyword: searchContent.trim(),
+ };
+ await SearchMessage(searchData);
+ message.success("查找完成");
+ } catch (error) {
+ console.error("查找失败:", error);
+ message.error("查找失败,请重试");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleCancel = () => {
+ setSearchContent("");
+ setDateRange(null);
+ setLoading(false);
+ handleSearch();
+ updateShowChatRecordModel(false);
+ };
+
+ return (
+
+
+ {/* 时间范围选择 */}
+
+
+ {/* 查找内容输入 */}
+
+
查找内容
+
setSearchContent(e.target.value)}
+ disabled={loading}
+ />
+
+ 查找
+
+
+
+
+
+ );
+};
+
+export default ChatRecordSearch;
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/chatRecord/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/chatRecord/index.tsx
deleted file mode 100644
index 3068e42d..00000000
--- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/chatRecord/index.tsx
+++ /dev/null
@@ -1,150 +0,0 @@
-import React, { useState } from "react";
-import { Button, Modal, Input, DatePicker, message } from "antd";
-import { MessageOutlined } from "@ant-design/icons";
-import dayjs from "dayjs";
-import { useWeChatStore } from "@/store/module/weChat/weChat";
-
-const { RangePicker } = DatePicker;
-
-interface ChatRecordProps {
- className?: string;
- disabled?: boolean;
-}
-
-const ChatRecord: React.FC = ({
- className,
- disabled = false,
-}) => {
- const [visible, setVisible] = useState(false);
- const [searchContent, setSearchContent] = useState("");
- const [dateRange, setDateRange] = useState<[dayjs.Dayjs, dayjs.Dayjs] | null>(
- null,
- );
- const [loading, setLoading] = useState(false);
- const SearchMessage = useWeChatStore(state => state.SearchMessage);
-
- // 打开弹窗
- const openModal = () => {
- setVisible(true);
- };
-
- // 关闭弹窗并重置状态
- const closeModal = () => {
- setVisible(false);
- setSearchContent("");
- setDateRange(null);
- setLoading(false);
- };
-
- // 执行查找
- const handleSearch = async () => {
- if (!dateRange) {
- message.warning("请选择时间范围");
- return;
- }
-
- try {
- setLoading(true);
- const [From, To] = dateRange;
- const searchData = {
- From: From.unix() * 1000,
- To: To.unix() * 1000,
- keyword: searchContent.trim(),
- };
- await SearchMessage(searchData);
-
- message.success("查找完成");
- closeModal();
- } catch (error) {
- console.error("查找失败:", error);
- message.error("查找失败,请重试");
- } finally {
- setLoading(false);
- }
- };
-
- return (
- <>
-
-
- 聊天记录
-
-
-
-
- 取消
-
-
- 查找
-
- ,
- ]}
- >
-
- {/* 时间范围选择 */}
-
-
- {/* 查找内容输入 */}
-
-
- 查找内容
-
-
setSearchContent(e.target.value)}
- size="large"
- maxLength={100}
- showCount
- disabled={loading}
- />
-
-
-
- >
- );
-};
-
-export default ChatRecord;
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx
index cd6c9d29..16f6c179 100644
--- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx
@@ -6,6 +6,7 @@ import {
PictureOutlined,
ExportOutlined,
CloseOutlined,
+ MessageOutlined,
} from "@ant-design/icons";
import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
import { useWebSocketStore } from "@/store/module/websocket/websocket";
@@ -14,7 +15,6 @@ import { EmojiInfo } from "@/components/EmojiSeclection/wechatEmoji";
import SimpleFileUpload from "@/components/Upload/SimpleFileUpload";
import AudioRecorder from "@/components/Upload/AudioRecorder";
import ToContract from "./components/toContract";
-import ChatRecord from "./components/chatRecord";
import styles from "./MessageEnter.module.scss";
import { useWeChatStore } from "@/store/module/weChat/weChat";
const { Footer } = Layout;
@@ -35,6 +35,12 @@ const MessageEnter: React.FC
= ({ contract }) => {
const updateTransmitModal = useWeChatStore(
state => state.updateTransmitModal,
);
+ const showChatRecordModel = useWeChatStore(
+ state => state.showChatRecordModel,
+ );
+ const updateShowChatRecordModel = useWeChatStore(
+ state => state.updateShowChatRecordModel,
+ );
const quoteMessageContent = useWeChatStore(
state => state.quoteMessageContent,
@@ -154,6 +160,9 @@ const MessageEnter: React.FC = ({ contract }) => {
const handTurnRignt = () => {
updateTransmitModal(true);
};
+ const openChatRecordModel = () => {
+ updateShowChatRecordModel(!showChatRecordModel);
+ };
return (
<>
@@ -202,7 +211,17 @@ const MessageEnter: React.FC = ({ contract }) => {
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/MessageRecord.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/MessageRecord.module.scss
index d20bd680..c2cf7a46 100644
--- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/MessageRecord.module.scss
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/MessageRecord.module.scss
@@ -121,6 +121,11 @@
line-height: 1.4;
white-space: pre-wrap;
word-break: break-word;
+ &::after {
+ content: "";
+ display: block;
+ clear: both;
+ }
}
// 表情包消息
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage/LocationMessage.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage/LocationMessage.module.scss
new file mode 100644
index 00000000..61ef4774
--- /dev/null
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage/LocationMessage.module.scss
@@ -0,0 +1,293 @@
+// 通用消息文本样式
+.messageText {
+ line-height: 1.4;
+ white-space: pre-wrap;
+ word-break: break-word;
+ color: #666;
+ font-size: 14px;
+}
+
+// 位置消息基础样式
+.locationMessage {
+ max-width: 420px;
+ margin: 4px 0;
+}
+
+.locationCard {
+ background: #ffffff;
+ border: 1px solid #e1e8ed;
+ border-radius: 12px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+ cursor: pointer;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ overflow: hidden;
+
+ &:hover {
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
+ transform: translateY(-1px);
+ border-color: #1890ff;
+ }
+
+ &:active {
+ transform: translateY(0);
+ }
+}
+
+// 位置消息头部
+.locationHeader {
+ display: flex;
+ align-items: center;
+ padding: 12px 16px 8px;
+ background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
+ border-bottom: 1px solid #e1e8ed;
+}
+
+.locationIcon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
+ border-radius: 8px;
+ color: white;
+ margin-right: 12px;
+ box-shadow: 0 2px 4px rgba(24, 144, 255, 0.2);
+
+ svg {
+ width: 18px;
+ height: 18px;
+ }
+}
+
+.locationTitle {
+ font-size: 14px;
+ font-weight: 600;
+ color: #1a1a1a;
+ letter-spacing: -0.01em;
+}
+
+// 位置消息内容
+.locationContent {
+ padding: 12px 16px;
+}
+
+.poiName {
+ font-size: 16px;
+ font-weight: 600;
+ color: #1a1a1a;
+ line-height: 1.4;
+ margin-bottom: 8px;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.locationAddress {
+ font-size: 13px;
+ color: #666;
+ line-height: 1.5;
+ margin-bottom: 12px;
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.locationDetails {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ margin-bottom: 12px;
+}
+
+.poiCategory,
+.poiPhone {
+ display: flex;
+ align-items: center;
+ font-size: 12px;
+ color: #8c8c8c;
+ line-height: 1.4;
+
+ .categoryIcon,
+ .phoneIcon {
+ margin-right: 6px;
+ font-size: 11px;
+ }
+}
+
+.coordinates {
+ display: flex;
+ align-items: center;
+ font-size: 11px;
+ color: #999;
+ background: #f8f9fa;
+ padding: 6px 8px;
+ border-radius: 6px;
+ border: 1px solid #e9ecef;
+
+ .coordLabel {
+ margin-right: 6px;
+ font-weight: 500;
+ }
+
+ .coordValue {
+ font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
+ letter-spacing: 0.5px;
+ }
+}
+
+// 位置消息操作区域
+.locationAction {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 10px 16px;
+ background: linear-gradient(135deg, #f0f8ff 0%, #e6f7ff 100%);
+ border-top: 1px solid #e1e8ed;
+ color: #1890ff;
+ font-size: 13px;
+ font-weight: 500;
+ transition: all 0.2s ease;
+
+ .actionText {
+ margin-right: 6px;
+ }
+
+ svg {
+ width: 14px;
+ height: 14px;
+ transition: transform 0.2s ease;
+ }
+
+ &:hover {
+ background: linear-gradient(135deg, #e6f7ff 0%, #bae7ff 100%);
+
+ svg {
+ transform: translateX(2px);
+ }
+ }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+ .locationMessage {
+ max-width: 280px;
+ }
+
+ .locationCard {
+ border-radius: 10px;
+ }
+
+ .locationHeader {
+ padding: 10px 14px 6px;
+ }
+
+ .locationIcon {
+ width: 28px;
+ height: 28px;
+ margin-right: 10px;
+
+ svg {
+ width: 16px;
+ height: 16px;
+ }
+ }
+
+ .locationTitle {
+ font-size: 13px;
+ }
+
+ .locationContent {
+ padding: 10px 14px;
+ }
+
+ .poiName {
+ font-size: 15px;
+ margin-bottom: 6px;
+ }
+
+ .locationAddress {
+ font-size: 12px;
+ margin-bottom: 10px;
+ }
+
+ .locationDetails {
+ gap: 4px;
+ margin-bottom: 10px;
+ }
+
+ .poiCategory,
+ .poiPhone {
+ font-size: 11px;
+ }
+
+ .coordinates {
+ font-size: 10px;
+ padding: 4px 6px;
+ }
+
+ .locationAction {
+ padding: 8px 14px;
+ font-size: 12px;
+
+ svg {
+ width: 12px;
+ height: 12px;
+ }
+ }
+}
+
+// 深色模式支持(如果需要)
+@media (prefers-color-scheme: dark) {
+ .locationCard {
+ background: #1f1f1f;
+ border-color: #333;
+ color: #e6e6e6;
+
+ &:hover {
+ border-color: #40a9ff;
+ }
+ }
+
+ .locationHeader {
+ background: linear-gradient(135deg, #2a2a2a 0%, #1a1a1a 100%);
+ border-bottom-color: #333;
+ }
+
+ .locationTitle {
+ color: #e6e6e6;
+ }
+
+ .poiName {
+ color: #e6e6e6;
+ }
+
+ .locationAddress {
+ color: #999;
+ }
+
+ .poiCategory,
+ .poiPhone {
+ color: #666;
+ }
+
+ .coordinates {
+ background: #2a2a2a;
+ border-color: #333;
+ color: #999;
+ }
+
+ .locationAction {
+ background: linear-gradient(135deg, #1a2332 0%, #0d1419 100%);
+ border-top-color: #333;
+ color: #40a9ff;
+
+ &:hover {
+ background: linear-gradient(135deg, #0d1419 0%, #1a2332 100%);
+ }
+ }
+}
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage/index.tsx
new file mode 100644
index 00000000..c2d6d8a4
--- /dev/null
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage/index.tsx
@@ -0,0 +1,146 @@
+import React from "react";
+import styles from "./LocationMessage.module.scss";
+
+interface LocationMessageProps {
+ content: string;
+}
+
+interface LocationData {
+ x: string; // 经度
+ y: string; // 纬度
+ scale: string; // 缩放级别
+ label: string; // 地址标签
+ maptype: string; // 地图类型
+ poiname: string; // POI名称
+ poiid: string; // POI ID
+ buildingId: string; // 建筑ID
+ floorName: string; // 楼层名称
+ poiCategoryTips: string; // POI分类提示
+ poiBusinessHour: string; // 营业时间
+ poiPhone: string; // 电话
+ poiPriceTips: string; // 价格提示
+ isFromPoiList: string; // 是否来自POI列表
+ adcode: string; // 行政区划代码
+ cityname: string; // 城市名称
+ fromusername: string; // 发送者用户名
+}
+
+const LocationMessage: React.FC
= ({ content }) => {
+ // 统一的错误消息渲染函数
+ const renderErrorMessage = (fallbackText: string) => (
+ {fallbackText}
+ );
+
+ if (typeof content !== "string" || !content.trim()) {
+ return renderErrorMessage("[位置消息 - 无效内容]");
+ }
+
+ try {
+ // 解析位置消息内容
+ const parseLocationContent = (content: string): LocationData | null => {
+ try {
+ // 提取XML中的location标签内容
+ const locationMatch = content.match(/]*>/);
+ if (!locationMatch) {
+ return null;
+ }
+
+ const locationTag = locationMatch[0];
+
+ // 提取所有属性
+ const extractAttribute = (tag: string, attrName: string): string => {
+ const regex = new RegExp(`${attrName}="([^"]*)"`);
+ const match = tag.match(regex);
+ return match ? match[1] : "";
+ };
+
+ return {
+ x: extractAttribute(locationTag, "x"),
+ y: extractAttribute(locationTag, "y"),
+ scale: extractAttribute(locationTag, "scale"),
+ label: extractAttribute(locationTag, "label"),
+ maptype: extractAttribute(locationTag, "maptype"),
+ poiname: extractAttribute(locationTag, "poiname"),
+ poiid: extractAttribute(locationTag, "poiid"),
+ buildingId: extractAttribute(locationTag, "buildingId"),
+ floorName: extractAttribute(locationTag, "floorName"),
+ poiCategoryTips: extractAttribute(locationTag, "poiCategoryTips"),
+ poiBusinessHour: extractAttribute(locationTag, "poiBusinessHour"),
+ poiPhone: extractAttribute(locationTag, "poiPhone"),
+ poiPriceTips: extractAttribute(locationTag, "poiPriceTips"),
+ isFromPoiList: extractAttribute(locationTag, "isFromPoiList"),
+ adcode: extractAttribute(locationTag, "adcode"),
+ cityname: extractAttribute(locationTag, "cityname"),
+ fromusername: extractAttribute(locationTag, "fromusername"),
+ };
+ } catch (error) {
+ console.error("解析位置消息失败:", error);
+ return null;
+ }
+ };
+
+ const locationData = parseLocationContent(content);
+
+ if (!locationData) {
+ return renderErrorMessage("[位置消息 - 解析失败]");
+ }
+
+ // 生成地图链接
+ const generateMapUrl = (lat: string, lng: string, label: string) => {
+ // 使用腾讯地图链接
+ return `https://apis.map.qq.com/uri/v1/marker?marker=coord:${lat},${lng};title:${encodeURIComponent(label)}&referer=wechat`;
+ };
+
+ const mapUrl = generateMapUrl(
+ locationData.y,
+ locationData.x,
+ locationData.label,
+ );
+
+ // 处理POI信息
+ const poiName = locationData.poiname || locationData.label;
+ const poiCategory = locationData.poiCategoryTips
+ ? locationData.poiCategoryTips.split(":")[0]
+ : "";
+ const poiPhone = locationData.poiPhone || "";
+
+ return (
+
+
window.open(mapUrl, "_blank")}
+ >
+ {/* 位置详情 */}
+
+ {/* POI名称 */}
+ {poiName &&
{poiName}
}
+
+ {/* 详细地址 */}
+
{locationData.label}
+
+ {/* POI分类和电话 */}
+
+ {poiCategory && (
+
+ 🏷️
+ {poiCategory}
+
+ )}
+ {poiPhone && (
+
+ 📞
+ {poiPhone}
+
+ )}
+
+
+
+
+ );
+ } catch (error) {
+ console.error("位置消息渲染失败:", error);
+ return renderErrorMessage("[位置消息 - 渲染失败]");
+ }
+};
+
+export default LocationMessage;
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx
index d989d1ac..fee0dbd3 100644
--- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx
@@ -5,6 +5,7 @@ import AudioMessage from "./components/AudioMessage/AudioMessage";
import SmallProgramMessage from "./components/SmallProgramMessage";
import VideoMessage from "./components/VideoMessage";
import ClickMenu from "./components/ClickMeau";
+import LocationMessage from "./components/LocationMessage";
import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
import { formatWechatTime } from "@/utils/common";
import { getEmojiPath } from "@/components/EmojiSeclection/wechatEmoji";
@@ -270,6 +271,9 @@ const MessageRecord: React.FC = ({ contract }) => {
}
return renderErrorMessage("[表情包]");
+ case 48: // 定位消息
+ return ;
+
case 49: // 小程序/文章/其他:图文、文件
return ;
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/api.ts b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/api.ts
new file mode 100644
index 00000000..a1a12a52
--- /dev/null
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/api.ts
@@ -0,0 +1,96 @@
+import request from "@/api/request";
+// 快捷回复项接口
+export interface QuickWordsReply {
+ id: number;
+ groupId: number;
+ userId: number;
+ title: string;
+ msgType: number;
+ content: string;
+ createTime: string;
+ lastUpdateTime: string;
+ sortIndex: string;
+ updateTime: string | null;
+ isDel: number;
+ delTime: string | null;
+}
+
+// 快捷回复组接口
+export interface QuickWordsItem {
+ id: number;
+ groupName: string;
+ sortIndex: string;
+ parentId: number;
+ replyType: string;
+ replys: any | null;
+ companyId: number;
+ userId: number;
+ replies: QuickWordsReply[];
+ children: QuickWordsItem[];
+}
+
+//好友接待配置
+export function setFriendInjectConfig(params: any): Promise {
+ return request("/v1/kefu/reply/list", params, "GET");
+}
+
+export interface AddReplyRequest {
+ id?: string;
+ content?: string;
+ groupId?: string;
+ /**
+ * 1文本 3图片 43视频 49链接 等
+ */
+ msgType?: string[];
+ /**
+ * 默认50
+ */
+ sortIndex?: string;
+ title?: string;
+ [property: string]: any;
+}
+
+// 添加快捷回复
+export function addReply(params: AddReplyRequest): Promise {
+ return request("/v1/kefu/reply/addReply", params, "POST");
+}
+
+// 更新快捷回复
+export function updateReply(params: AddReplyRequest): Promise {
+ return request("/v1/kefu/reply/updateReply", params, "POST");
+}
+
+// 删除快捷回复
+export function deleteReply(params: { id: string }): Promise {
+ return request("/v1/kefu/reply/deleteReply", params, "DELETE");
+}
+
+export interface AddGroupRequest {
+ id?: string;
+ groupName?: string;
+ parentId?: string;
+ /**
+ * 0 公共 1私有 2部门
+ */
+ replyType?: string[];
+ /**
+ * 默认50
+ */
+ sortIndex?: string;
+ [property: string]: any;
+}
+
+// 添加快捷回复组
+export function addGroup(params: AddGroupRequest): Promise {
+ return request("/v1/kefu/reply/addGroup", params, "POST");
+}
+
+// 更新快捷回复组
+export function updateGroup(params: AddGroupRequest): Promise {
+ return request("/v1/kefu/reply/updateGroup", params, "POST");
+}
+
+// 删除快捷回复组
+export function deleteGroup(params: { id: string }): Promise {
+ return request("/v1/kefu/reply/deleteGroup", params, "DELETE");
+}
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/components/GroupModal.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/components/GroupModal.tsx
new file mode 100644
index 00000000..420d942b
--- /dev/null
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/components/GroupModal.tsx
@@ -0,0 +1,67 @@
+import React from "react";
+import { Modal, Form, Input, Space, Button } from "antd";
+import { AddGroupRequest } from "../api";
+
+export interface GroupModalProps {
+ open: boolean;
+ mode: "add" | "edit";
+ initialValues?: Partial;
+ onSubmit: (values: AddGroupRequest) => void;
+ onCancel: () => void;
+}
+
+const GroupModal: React.FC = ({
+ open,
+ mode,
+ initialValues,
+ onSubmit,
+ onCancel,
+}) => {
+ const [form] = Form.useForm();
+
+ return (
+ {
+ onCancel();
+ form.resetFields();
+ }}
+ footer={null}
+ destroyOnClose
+ >
+
+
+
+
+
+
+
+ 确定
+
+ {
+ onCancel();
+ form.resetFields();
+ }}
+ >
+ 取消
+
+
+
+
+
+ );
+};
+
+export default GroupModal;
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/components/QuickReplyModal.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/components/QuickReplyModal.tsx
new file mode 100644
index 00000000..478f1097
--- /dev/null
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/components/QuickReplyModal.tsx
@@ -0,0 +1,247 @@
+import React, { useMemo } from "react";
+import { Modal, Form, Input, Select, Space, Button } from "antd";
+import {
+ PictureOutlined,
+ VideoCameraOutlined,
+ LinkOutlined,
+} from "@ant-design/icons";
+import SimpleFileUpload from "@/components/Upload/SimpleFileUpload";
+// 简化版不再使用样式与解析组件
+import { AddReplyRequest } from "../api";
+
+export interface QuickReplyModalProps {
+ open: boolean;
+ mode: "add" | "edit";
+ initialValues?: Partial;
+ onSubmit: (values: AddReplyRequest) => void;
+ onCancel: () => void;
+ groupOptions?: { label: string; value: string }[];
+ defaultGroupId?: string;
+}
+
+const QuickReplyModal: React.FC = ({
+ open,
+ mode,
+ initialValues,
+ onSubmit,
+ onCancel,
+ groupOptions,
+ defaultGroupId,
+}) => {
+ const [form] = Form.useForm();
+
+ const mergedInitialValues = useMemo(() => {
+ return {
+ groupId: defaultGroupId,
+ msgType: initialValues?.msgType || ["1"],
+ ...initialValues,
+ } as Partial;
+ }, [initialValues, defaultGroupId]);
+
+ // 监听类型变化
+ const msgTypeWatch = Form.useWatch("msgType", form);
+ const selectedMsgType = useMemo(() => {
+ const value = msgTypeWatch;
+ const raw = Array.isArray(value) ? value[0] : value;
+ return Number(raw || "1");
+ }, [msgTypeWatch]);
+
+ // 根据文件格式判断消息类型
+ const getMsgTypeByFileFormat = (filePath: string): number => {
+ const extension = filePath.toLowerCase().split(".").pop() || "";
+ const imageFormats = [
+ "jpg",
+ "jpeg",
+ "png",
+ "gif",
+ "bmp",
+ "webp",
+ "svg",
+ "ico",
+ ];
+ if (imageFormats.includes(extension)) return 3;
+ const videoFormats = [
+ "mp4",
+ "avi",
+ "mov",
+ "wmv",
+ "flv",
+ "mkv",
+ "webm",
+ "3gp",
+ "rmvb",
+ ];
+ if (videoFormats.includes(extension)) return 43;
+ return 49;
+ };
+
+ const FileType = {
+ TEXT: 1,
+ IMAGE: 2,
+ VIDEO: 3,
+ AUDIO: 4,
+ FILE: 5,
+ } as const;
+
+ const handleFileUploaded = (
+ filePath: string | { url: string; durationMs: number },
+ fileType: number,
+ ) => {
+ let msgType = 1;
+ if (([FileType.TEXT] as number[]).includes(fileType)) {
+ msgType = getMsgTypeByFileFormat(filePath as string);
+ } else if (([FileType.IMAGE] as number[]).includes(fileType)) {
+ msgType = 3;
+ } else if (([FileType.VIDEO] as number[]).includes(fileType)) {
+ msgType = 43;
+ } else if (([FileType.AUDIO] as number[]).includes(fileType)) {
+ msgType = 34;
+ } else if (([FileType.FILE] as number[]).includes(fileType)) {
+ msgType = 49;
+ }
+
+ form.setFieldsValue({
+ msgType: [String(msgType)],
+ content: ([FileType.AUDIO] as number[]).includes(fileType)
+ ? JSON.stringify(filePath)
+ : (filePath as string),
+ });
+ };
+
+ const handleKeyPress = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter" && !e.ctrlKey && !e.shiftKey) {
+ e.preventDefault();
+ form.submit();
+ }
+ };
+
+ // 简化后不再有预览解析
+
+ return (
+ {
+ onCancel();
+ form.resetFields();
+ }}
+ footer={null}
+ destroyOnClose
+ >
+
+
+
+
+
+ {selectedMsgType === 1 && (
+ form.setFieldsValue({ content: e.target.value })}
+ onKeyDown={handleKeyPress}
+ />
+ )}
+ {selectedMsgType === 3 && (
+
+ handleFileUploaded(filePath, FileType.IMAGE)
+ }
+ maxSize={1}
+ type={1}
+ slot={}>上传图片}
+ />
+ )}
+ {selectedMsgType === 43 && (
+
+ handleFileUploaded(filePath, FileType.VIDEO)
+ }
+ maxSize={1}
+ type={4}
+ slot={}>上传视频}
+ />
+ )}
+ {selectedMsgType === 49 && (
+ }
+ value={form.getFieldValue("content")}
+ onChange={e => form.setFieldsValue({ content: e.target.value })}
+ />
+ )}
+
+
+
+
+
+ 确定
+
+ {
+ onCancel();
+ form.resetFields();
+ }}
+ >
+ 取消
+
+
+
+
+
+ );
+};
+
+export default QuickReplyModal;
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/index.tsx
index 915dc873..561200e5 100644
--- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/index.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/index.tsx
@@ -1,136 +1,583 @@
-import React, { useMemo, useState } from "react";
-import { Card, Input, Button, Space, List, Tag } from "antd";
+import React, { useMemo, useState, useEffect, useCallback } from "react";
+import {
+ Input,
+ Button,
+ Space,
+ Tabs,
+ Tree,
+ Modal,
+ Form,
+ message,
+ Tooltip,
+ Spin,
+ Dropdown,
+} from "antd";
+import {
+ PlusOutlined,
+ ReloadOutlined,
+ EditOutlined,
+ DeleteOutlined,
+ FileTextOutlined,
+ PictureOutlined,
+ PlayCircleOutlined,
+} from "@ant-design/icons";
+import {
+ QuickWordsItem,
+ QuickWordsReply,
+ setFriendInjectConfig,
+ addReply,
+ updateReply,
+ deleteReply,
+ updateGroup,
+ deleteGroup,
+ AddReplyRequest,
+ AddGroupRequest,
+ addGroup,
+} from "./api";
+import Layout from "@/components/Layout/LayoutFiexd";
+import QuickReplyModal from "./components/QuickReplyModal";
+import GroupModal from "./components/GroupModal";
+import { useWeChatStore } from "@/store/module/weChat/weChat";
+import { useWebSocketStore } from "@/store/module/websocket/websocket";
-export interface QuickWordItem {
- id: string | number;
- text?: string; // 兼容旧结构
- title?: string;
- content?: string;
- tag?: string; // 分类/标签
- usageCount?: number;
+// 消息类型枚举
+export enum MessageType {
+ TEXT = 1,
+ IMAGE = 3,
+ VIDEO = 43,
+ LINK = 49,
+}
+
+// 快捷语类型枚举
+export enum QuickWordsType {
+ PERSONAL = 1, // 个人
+ PUBLIC = 0, // 公共
+ DEPARTMENT = 2, // 部门
}
export interface QuickWordsProps {
- title?: string;
- words: QuickWordItem[];
- onInsert?: (text: string) => void;
- onAdd?: (text: string) => void;
- onRemove?: (id: string | number) => void;
+ onInsert?: (reply: QuickWordsReply) => void;
}
-const QuickWords: React.FC = ({
- title = "快捷语录",
- words,
- onInsert,
-
- onRemove,
-}) => {
- const [keyword, setKeyword] = useState("");
- const sorted = useMemo(
- () =>
- [...(words || [])].sort((a, b) =>
- String(a.id).localeCompare(String(b.id)),
- ),
- [words],
+const QuickWords: React.FC = ({ onInsert }) => {
+ const [activeTab, setActiveTab] = useState(
+ QuickWordsType.PUBLIC,
);
+ const [keyword, setKeyword] = useState("");
+ const [loading, setLoading] = useState(false);
+ const [quickWordsData, setQuickWordsData] = useState([]);
+ const [expandedKeys, setExpandedKeys] = useState([]);
+ const [selectedKeys, setSelectedKeys] = useState([]);
+
+ // 模态框状态
+ const [addModalVisible, setAddModalVisible] = useState(false);
+ const [editModalVisible, setEditModalVisible] = useState(false);
+ const [groupModalVisible, setGroupModalVisible] = useState(false);
+ const [editingItem, setEditingItem] = useState(null);
+ const [editingGroup, setEditingGroup] = useState(null);
+ const [isAddingGroup, setIsAddingGroup] = useState(false);
+ const [form] = Form.useForm();
+ const [groupForm] = Form.useForm();
+ const updateQuoteMessageContent = useWeChatStore(
+ state => state.updateQuoteMessageContent,
+ );
+ const currentContract = useWeChatStore(state => state.currentContract);
+ const { sendCommand } = useWebSocketStore.getState();
+
+ const sendQuickReplyNow = (reply: QuickWordsReply) => {
+ if (!currentContract) return;
+ const params = {
+ wechatAccountId: currentContract.wechatAccountId,
+ wechatChatroomId: currentContract?.chatroomId ? currentContract.id : 0,
+ wechatFriendId: currentContract?.chatroomId ? 0 : currentContract.id,
+ msgSubType: 0,
+ msgType: reply.msgType,
+ content: reply.content,
+ } as any;
+ sendCommand("CmdSendMessage", params);
+ };
+
+ const previewAndConfirmSend = (reply: QuickWordsReply) => {
+ let previewNode: React.ReactNode = null;
+ if (reply.msgType === MessageType.IMAGE) {
+ previewNode = (
+
+

+
+ );
+ } else if (reply.msgType === MessageType.VIDEO) {
+ try {
+ const json = JSON.parse(reply.content || "{}");
+ const cover = json.previewImage || json.thumbPath || "";
+ previewNode = (
+
+ {cover ? (
+
})
+ ) : (
+
视频消息
+ )}
+
+ );
+ } catch {
+ previewNode = 视频消息
;
+ }
+ } else if (reply.msgType === MessageType.LINK) {
+ previewNode = (
+
+
{reply.title}
+
{reply.content}
+
+ );
+ }
+
+ Modal.confirm({
+ title: "确认发送该快捷语?",
+ content: previewNode,
+ okText: "发送",
+ cancelText: "取消",
+ onOk: () => {
+ sendQuickReplyNow(reply);
+ message.success("已发送");
+ },
+ });
+ };
+
+ // 获取快捷语数据
+ const fetchQuickWords = useCallback(async () => {
+ setLoading(true);
+ try {
+ const data = await setFriendInjectConfig({ replyType: activeTab });
+ setQuickWordsData(data || []);
+ } catch (error) {
+ message.error("获取快捷语数据失败");
+ } finally {
+ setLoading(false);
+ }
+ }, [activeTab]);
+
+ // 初始化数据
+ useEffect(() => {
+ fetchQuickWords();
+ }, [fetchQuickWords]);
+
+ // 获取消息类型图标
+ const getMessageTypeIcon = (msgType: number) => {
+ switch (msgType) {
+ case MessageType.TEXT:
+ return ;
+ case MessageType.IMAGE:
+ return ;
+ case MessageType.VIDEO:
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ // 将数据转换为Tree组件需要的格式
+ const convertToTreeData = (data: QuickWordsItem[]): any[] => {
+ return data.map(item => ({
+ key: `group-${item.id}`,
+ title: (
+
+
{item.groupName}
+
+
+ }
+ onClick={e => {
+ e.stopPropagation();
+ handleEditGroup(item);
+ }}
+ />
+
+
+ }
+ onClick={e => {
+ e.stopPropagation();
+ handleDeleteGroup(item.id);
+ }}
+ />
+
+
+
+ ),
+ children: [
+ ...item.replies.map(reply => ({
+ key: `reply-${reply.id}`,
+ title: (
+ {
+ e.stopPropagation();
+ // 将快捷语内容写入输入框(仅文本或可直接粘贴的内容)
+ try {
+ if ([MessageType.TEXT].includes(reply.msgType)) {
+ updateQuoteMessageContent(reply.content || "");
+ } else if ([MessageType.LINK].includes(reply.msgType)) {
+ previewAndConfirmSend(reply);
+ } else {
+ // 图片/视频等类型:弹出预览确认后直接发送
+ previewAndConfirmSend(reply);
+ }
+ } catch (_) {}
+ }}
+ >
+
+ {getMessageTypeIcon(reply.msgType)}
+ {reply.title}
+
+
+
+ }
+ onClick={e => {
+ e.stopPropagation();
+ handleEditReply(reply);
+ }}
+ />
+
+
+ }
+ onClick={e => {
+ e.stopPropagation();
+ handleDeleteReply(reply.id);
+ }}
+ />
+
+
+
+ ),
+ isLeaf: true,
+ })),
+ ...convertToTreeData(item.children || []),
+ ],
+ }));
+ };
+
+ // 处理添加快捷回复
+ const handleAddReply = async (values: AddReplyRequest) => {
+ try {
+ const fallbackGroupId =
+ selectedKeys[0]?.toString().replace("group-", "") ||
+ groupOptions[0]?.value ||
+ "";
+ await addReply({
+ ...values,
+ groupId: values.groupId || fallbackGroupId,
+ replyType: [activeTab.toString()],
+ });
+ message.success("添加快捷回复成功");
+ setAddModalVisible(false);
+ form.resetFields();
+ fetchQuickWords();
+ } catch (error) {
+ message.error("添加快捷回复失败");
+ }
+ };
+
+ // 处理编辑快捷回复
+ const handleEditReply = (reply: QuickWordsReply) => {
+ setEditingItem(reply);
+ setEditModalVisible(true);
+ };
+
+ // 处理更新快捷回复
+ const handleUpdateReply = async (values: AddReplyRequest) => {
+ if (!editingItem) return;
+
+ try {
+ await updateReply({
+ ...values,
+ id: editingItem.id.toString(),
+ });
+ message.success("更新快捷回复成功");
+ setEditModalVisible(false);
+ setEditingItem(null);
+ fetchQuickWords();
+ } catch (error) {
+ message.error("更新快捷回复失败");
+ }
+ };
+
+ // 处理删除快捷回复
+ const handleDeleteReply = async (id: number) => {
+ Modal.confirm({
+ title: "确认删除",
+ content: "确定要删除这个快捷回复吗?",
+ onOk: async () => {
+ try {
+ await deleteReply({ id: id.toString() });
+ message.success("删除成功");
+ fetchQuickWords();
+ } catch (error) {
+ message.error("删除失败");
+ }
+ },
+ });
+ };
+
+ // 处理编辑分组
+ const handleEditGroup = (group: QuickWordsItem) => {
+ setIsAddingGroup(false);
+ setEditingGroup(group);
+ setGroupModalVisible(true);
+ };
+
+ // 打开新增分组
+ const handleOpenAddGroup = () => {
+ setIsAddingGroup(true);
+ setEditingGroup(null);
+ groupForm.resetFields();
+ setGroupModalVisible(true);
+ };
+
+ // 处理更新分组
+ const handleUpdateGroup = async (values: AddGroupRequest) => {
+ if (!editingGroup) return;
+
+ try {
+ await updateGroup({
+ ...values,
+ id: editingGroup.id.toString(),
+ });
+ message.success("更新分组成功");
+ setGroupModalVisible(false);
+ setEditingGroup(null);
+ fetchQuickWords();
+ } catch (error) {
+ message.error("更新分组失败");
+ }
+ };
+
+ // 处理新增分组
+ const handleAddGroup = async (values: AddGroupRequest) => {
+ try {
+ await addGroup({
+ ...values,
+ parentId: selectedKeys[0]?.toString().startsWith("group-")
+ ? selectedKeys[0]?.toString().replace("group-", "")
+ : "0",
+ replyType: [activeTab.toString()],
+ });
+ message.success("新增分组成功");
+ setGroupModalVisible(false);
+ setIsAddingGroup(false);
+ fetchQuickWords();
+ } catch (error) {
+ message.error("新增分组失败");
+ }
+ };
+
+ // 处理删除分组
+ const handleDeleteGroup = async (id: number) => {
+ Modal.confirm({
+ title: "确认删除",
+ content: "确定要删除这个分组吗?删除后该分组下的所有快捷回复也会被删除。",
+ onOk: async () => {
+ try {
+ await deleteGroup({ id: id.toString() });
+ message.success("删除成功");
+ fetchQuickWords();
+ } catch (error) {
+ message.error("删除失败");
+ }
+ },
+ });
+ };
+
+ // 过滤数据
+ const filteredData = useMemo(() => {
+ if (!keyword.trim()) return quickWordsData;
+
+ const filterData = (data: QuickWordsItem[]): QuickWordsItem[] => {
+ return data
+ .map(item => ({
+ ...item,
+ replies: item.replies.filter(
+ reply =>
+ reply.title.toLowerCase().includes(keyword.toLowerCase()) ||
+ reply.content.toLowerCase().includes(keyword.toLowerCase()),
+ ),
+ children: filterData(item.children || []),
+ }))
+ .filter(
+ item =>
+ item.replies.length > 0 ||
+ item.children.length > 0 ||
+ item.groupName.toLowerCase().includes(keyword.toLowerCase()),
+ );
+ };
+
+ return filterData(quickWordsData);
+ }, [quickWordsData, keyword]);
+
+ const treeData = convertToTreeData(filteredData);
+
+ // 供新增/编辑快捷语使用的分组下拉数据
+ const groupOptions = useMemo(() => {
+ const flat: { label: string; value: string }[] = [];
+ const walk = (items: QuickWordsItem[]) => {
+ items.forEach(it => {
+ flat.push({ label: it.groupName, value: it.id.toString() });
+ if (it.children && it.children.length) walk(it.children);
+ });
+ };
+ walk(quickWordsData);
+ return flat;
+ }, [quickWordsData]);
return (
-
-
- setKeyword(e.target.value)}
- onSearch={v => setKeyword(v)}
- />
+
+ setActiveTab(Number(key) as QuickWordsType)}
+ items={[
+ {
+ key: QuickWordsType.PERSONAL.toString(),
+ label: "个人快捷语",
+ },
- {
- const text = `${item.title || ""}${item.content || ""}${item.text || ""}`;
- return text.toLowerCase().includes(keyword.trim().toLowerCase());
- })}
- renderItem={item => {
- const displayTitle = item.title || item.text || "未命名";
- const displayContent = item.content || item.text || "";
- return (
-
-
-
-
- {item.tag && {item.tag}}
-
- {displayTitle}
-
-
-
- {displayContent}
-
- {typeof item.usageCount === "number" && (
-
- 使用 {item.usageCount} 次
-
- )}
-
-
- {onRemove && (
- onRemove(item.id)}
- >
- 删除
-
- )}
- onInsert?.(displayContent || displayTitle)}
- >
- 使用
-
-
-
-
- );
- }}
- />
+ {
+ key: QuickWordsType.DEPARTMENT.toString(),
+ label: "公司快捷语",
+ },
+ ]}
+ />
+
+ setKeyword(e.target.value)}
+ style={{ flex: 1 }}
+ />
+ {
+ if (key === "add-group") return handleOpenAddGroup();
+ if (key === "add-reply") return setAddModalVisible(true);
+ if (key === "import-reply")
+ return message.info("导入快捷语功能开发中");
+ },
+ }}
+ placement="bottomRight"
+ trigger={["click"]}
+ >
+
+ } />
+
+
+
+ } onClick={fetchQuickWords} />
+
+
+
+ }
+ >
+
+
+
+
-
+
+
setAddModalVisible(false)}
+ />
+
+ {
+ setEditModalVisible(false);
+ setEditingItem(null);
+ }}
+ />
+
+ {
+ setGroupModalVisible(false);
+ setEditingGroup(null);
+ setIsAddingGroup(false);
+ }}
+ />
+
);
};
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/index.tsx
index 9cf35e2d..9be82d10 100644
--- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/index.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/index.tsx
@@ -16,6 +16,7 @@ import MessageEnter from "./components/MessageEnter";
import MessageRecord from "./components/MessageRecord";
import FollowupReminderModal from "./components/FollowupReminderModal";
import TodoListModal from "./components/TodoListModal";
+import ChatRecordSearch from "./components/ChatRecordSearch";
import { setFriendInjectConfig } from "@/pages/pc/ckbox/weChat/api";
import { useWeChatStore } from "@/store/module/weChat/weChat";
const { Header, Content } = Layout;
@@ -37,6 +38,9 @@ const ChatWindow: React.FC = ({ contract }) => {
const aiQuoteMessageContent = useWeChatStore(
state => state.aiQuoteMessageContent,
);
+ const showChatRecordModel = useWeChatStore(
+ state => state.showChatRecordModel,
+ );
const [showProfile, setShowProfile] = useState(true);
const [followupModalVisible, setFollowupModalVisible] = useState(false);
const [todoModalVisible, setTodoModalVisible] = useState(false);
@@ -136,12 +140,18 @@ const ChatWindow: React.FC = ({ contract }) => {
- } onClick={handleFollowupClick}>
- 跟进提醒
-
- } onClick={handleTodoClick}>
- 待办事项
-
+ {showChatRecordModel ? (
+
+ ) : (
+ <>
+ } onClick={handleFollowupClick}>
+ 跟进提醒
+
+ } onClick={handleTodoClick}>
+ 待办事项
+
+ >
+ )}
{/* 聊天内容 */}
diff --git a/Touchkebao/src/store/module/weChat/weChat.data.ts b/Touchkebao/src/store/module/weChat/weChat.data.ts
index 7a012775..29a1f2d6 100644
--- a/Touchkebao/src/store/module/weChat/weChat.data.ts
+++ b/Touchkebao/src/store/module/weChat/weChat.data.ts
@@ -10,6 +10,8 @@ import {
* 包含聊天消息、联系人管理、朋友圈等功能的状态和方法
*/
export interface WeChatState {
+ showChatRecordModel: boolean;
+ updateShowChatRecordModel: (show: boolean) => void;
aiQuoteMessageContent: number;
updateAiQuoteMessageContent: (message: number) => void;
quoteMessageContent: string;
diff --git a/Touchkebao/src/store/module/weChat/weChat.ts b/Touchkebao/src/store/module/weChat/weChat.ts
index dadfd728..5290b5eb 100644
--- a/Touchkebao/src/store/module/weChat/weChat.ts
+++ b/Touchkebao/src/store/module/weChat/weChat.ts
@@ -33,6 +33,10 @@ import {
export const useWeChatStore = create()(
persist(
(set, get) => ({
+ showChatRecordModel: false,
+ updateShowChatRecordModel: (show: boolean) => {
+ set({ showChatRecordModel: show });
+ },
//当前用户的ai接管状态
aiQuoteMessageContent: 0,
updateAiQuoteMessageContent: (message: number) => {
@@ -159,7 +163,8 @@ export const useWeChatStore = create()(
} else {
params.wechatChatroomId = contract.id;
}
-
+ //重置动作
+ set({ showChatRecordModel: false });
clearUnreadCount1(params);
clearUnreadCount2([contract.id]);
getFriendInjectConfig({