diff --git a/Touchkebao/src/pages/mobile/scenarios/list/api.ts b/Touchkebao/src/pages/mobile/scenarios/list/api.ts deleted file mode 100644 index ff0fbbcd..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/list/api.ts +++ /dev/null @@ -1,26 +0,0 @@ -import request from "@/api/request"; - -// 获取场景列表 -export function getScenarios(params: any) { - return request("/v1/plan/scenes", params, "GET"); -} - -// 获取场景详情 -export function getScenarioDetail(id: string) { - return request(`/v1/scenarios/${id}`, {}, "GET"); -} - -// 创建场景 -export function createScenario(data: any) { - return request("/v1/scenarios", data, "POST"); -} - -// 更新场景 -export function updateScenario(id: string, data: any) { - return request(`/v1/scenarios/${id}`, data, "PUT"); -} - -// 删除场景 -export function deleteScenario(id: string) { - return request(`/v1/scenarios/${id}`, {}, "DELETE"); -} diff --git a/Touchkebao/src/pages/mobile/scenarios/list/index.module.scss b/Touchkebao/src/pages/mobile/scenarios/list/index.module.scss deleted file mode 100644 index d200fddb..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/list/index.module.scss +++ /dev/null @@ -1,329 +0,0 @@ -// 导航栏样式 -.nav-title { - font-size: 18px; - font-weight: 600; - color: var(--primary-color); -} - -.nav-text { - color: var(--primary-color); -} - -.nav-right { - margin-left: 4px; - font-size: 12px; -} - -.new-plan-btn { - border-radius: 20px; - padding: 4px 12px; - height: 32px; - font-size: 12px; - background: var(--primary-gradient); - border: none; - box-shadow: 0 2px 8px var(--primary-shadow); - - &:active { - transform: translateY(1px); - box-shadow: 0 1px 4px var(--primary-shadow); - } -} - -// 页面容器 -.scene-page { - background: #f5f6fa; - min-height: 100vh; - padding: 0 0 60px 0; -} -.error { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 50px 30px; -} -// 错误提示 -.error-notice { - margin-bottom: 12px; - padding: 8px 12px; - background: #fff2e8; - border: 1px solid #ffd591; - border-radius: 8px; - box-shadow: 0 1px 4px rgba(255, 213, 145, 0.2); -} - -.error-notice-text { - font-size: 12px; - color: #d46b08; - font-weight: 500; -} - -// 加载状态 -.loading-container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 200px; - gap: 12px; -} - -.loading-text { - font-size: 14px; - color: #666; - font-weight: 500; -} - -// 错误状态 -.error-container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 200px; - gap: 16px; -} - -.error-text { - font-size: 14px; - color: #ff4d4f; - text-align: center; - font-weight: 500; -} - -.retry-button { - min-width: 100px; - border-radius: 20px; - background: var(--primary-gradient); - border: none; - box-shadow: 0 2px 8px var(--primary-shadow); -} - -// 页面头部 -.scene-header { - margin-bottom: 16px; - text-align: center; -} - -.header-title { - display: flex; - align-items: center; - justify-content: center; - gap: 6px; - font-size: 18px; - font-weight: 700; - color: var(--primary-color); - margin-bottom: 6px; -} - -.header-icon { - font-size: 20px; - color: var(--primary-color); -} - -.header-subtitle { - font-size: 12px; - color: #666; - line-height: 1.4; -} - -// 场景列表 -.scenarios-list { - display: flex; - flex-direction: column; - gap: 10px; -} - -// 场景卡片 -.scenario-item { - cursor: pointer; - transition: all 0.2s ease; - - &:hover { - transform: translateY(-1px); - } - - &:active { - transform: translateY(0); - } -} - -.scenarios-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 16px; - padding: 16px; -} - -.scenario-card { - background: #fff; - border-radius: 16px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); - transition: - box-shadow 0.2s, - transform 0.2s; - cursor: pointer; - overflow: hidden; - &:hover { - box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12); - transform: translateY(-2px) scale(1.02); - } -} - -.card-inner { - display: flex; - flex-direction: column; - align-items: center; - padding: 18px 10px 14px 10px; -} - -.card-img-wrap { - margin-bottom: 8px; -} -.card-img-bg { - width: 48px; - height: 48px; - background: #f0f2f5; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; -} -.card-img { - width: 32px; - height: 32px; - object-fit: contain; -} - -.card-title { - font-size: 16px; - font-weight: 600; - color: #1677ff; - text-align: center; - margin-bottom: 2px; -} - -.card-desc { - font-size: 12px; - color: #888; - text-align: center; - overflow: hidden; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; -} - -.card-stats { - display: flex; - align-items: center; - justify-content: center; - gap: 10px; - margin-top: 4px; -} -.card-count { - font-size: 13px; - color: #666; -} -.card-growth { - font-size: 12px; - color: #52c41a; - display: flex; - align-items: center; -} - -// 空状态 -.empty-state { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 40px 20px; - text-align: center; -} - -.empty-icon { - font-size: 36px; - margin-bottom: 12px; - opacity: 0.6; -} - -.empty-text { - font-size: 14px; - color: #666; - margin-bottom: 16px; - font-weight: 500; -} - -.empty-action { - border-radius: 20px; - background: var(--primary-gradient); - border: none; - box-shadow: 0 2px 8px var(--primary-shadow); - padding: 6px 16px; - font-weight: 500; - font-size: 12px; -} - -// 响应式设计 -@media (max-width: 480px) { - .scenario-card { - padding: 14px 16px; - min-height: 70px; - } - - .scenario-icon { - width: 46px; - height: 46px; - } - - .scenario-image { - width: 28px; - height: 28px; - } - - .scenario-name { - font-size: 15px; - } - - .stat-text { - font-size: 12px; - } - - .scenario-growth { - font-size: 15px; - } - - .growth-icon { - font-size: 13px; - } -} - -@media (max-width: 500px) { - .scenarios-grid { - gap: 10px; - padding: 10px; - } - .card-inner { - padding: 12px 4px 10px 4px; - } - .card-img-bg { - width: 60px; - height: 60px; - } - .card-img { - width: 40px; - height: 40px; - } - .card-title { - font-size: 15px; - } - .card-desc { - font-size: 11px; - } - .card-count { - font-size: 12px; - } - .card-growth { - font-size: 11px; - } -} diff --git a/Touchkebao/src/pages/mobile/scenarios/list/index.tsx b/Touchkebao/src/pages/mobile/scenarios/list/index.tsx deleted file mode 100644 index 0b50536c..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/list/index.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; -import { Button, Toast } from "antd-mobile"; -import { PlusOutlined, RiseOutlined } from "@ant-design/icons"; -import MeauMobile from "@/components/MeauMobile/MeauMoible"; -import NavCommon from "@/components/NavCommon"; -import Layout from "@/components/Layout/Layout"; -import { getScenarios } from "./api"; -import style from "./index.module.scss"; - -interface Scenario { - id: string; - name: string; - image: string; - description?: string; - count: number; - growth: string; - status: number; -} - -const Scene: React.FC = () => { - const navigate = useNavigate(); - const [scenarios, setScenarios] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(""); - - useEffect(() => { - const fetchScenarios = async () => { - setLoading(true); - setError(""); - try { - const response = await getScenarios({ page: 1, limit: 20 }); - const transformedScenarios: Scenario[] = response.map((item: any) => ({ - id: item.id.toString(), - name: item.name, - image: - item.image || - "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-api.png", - description: "", - count: item.count, - growth: item.growth, - status: item.status, - })); - setScenarios(transformedScenarios); - } catch (error) { - setError("获取场景数据失败,请稍后重试"); - Toast.show({ - content: "获取场景数据失败,请稍后重试", - position: "top", - }); - } finally { - setLoading(false); - } - }; - fetchScenarios(); - }, []); - - const handleScenarioClick = (scenarioId: string, scenarioName: string) => { - navigate( - `/scenarios/list/${scenarioId}/${encodeURIComponent(scenarioName)}`, - ); - }; - - const handleNewPlan = () => { - navigate("/scenarios/new"); - }; - - if (error && scenarios.length === 0) { - return ( - } - title="场景获客" - right={ - - } - /> - } - footer={} - > -
-
{error}
- -
-
- ); - } - - return ( - 场景获客} - title={""} - right={ - - } - /> - } - footer={} - > -
-
- {scenarios.map(scenario => ( -
handleScenarioClick(scenario.id, scenario.name)} - > -
-
-
- {scenario.name} { - e.currentTarget.src = - "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-api.png"; - }} - /> -
-
-
{scenario.name}
- -
- - 今日: {scenario.count} - - - - {scenario.growth} - -
-
-
- ))} -
-
-
- ); -}; - -export default Scene; diff --git a/Touchkebao/src/pages/mobile/scenarios/plan/list/api.ts b/Touchkebao/src/pages/mobile/scenarios/plan/list/api.ts deleted file mode 100644 index 26377d6c..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/plan/list/api.ts +++ /dev/null @@ -1,41 +0,0 @@ -import request from "@/api/request"; -import { PlanDetail, PlanListResponse, ApiResponse } from "./data"; - -// ==================== 计划相关接口 ==================== -// 获取计划列表 -export function getPlanList(params: { - sceneId: string; - page: number; - pageSize: number; -}): Promise { - return request(`/v1/plan/list`, params, "GET"); -} - -// 获取计划详情 -export function getPlanDetail(planId: string): Promise { - return request(`/v1/plan/detail`, { planId }, "GET"); -} - -// 复制计划 -export function copyPlan(planId: string): Promise> { - return request(`/v1/plan/copy`, { planId }, "GET"); -} - -// 删除计划 -export function deletePlan(planId: string): Promise> { - return request(`/v1/plan/delete`, { planId }, "DELETE"); -} - -// 获取小程序二维码 -export function getWxMinAppCode(planId: string): Promise> { - return request(`/v1/plan/getWxMinAppCode`, { taskId: planId }, "GET"); -} -//获客列表 -export function getUserList(planId: string, type: number) { - return request(`/v1/plan/getUserList`, { planId, type }, "GET"); -} - -//获客列表 -export function getFriendRequestTaskStats(taskId: string) { - return request(`/v1/dashboard/friendRequestTaskStats`, { taskId }, "GET"); -} diff --git a/Touchkebao/src/pages/mobile/scenarios/plan/list/components/AccountListModal.tsx b/Touchkebao/src/pages/mobile/scenarios/plan/list/components/AccountListModal.tsx deleted file mode 100644 index 7d16ad61..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/plan/list/components/AccountListModal.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { Popup, Avatar, SpinLoading } from "antd-mobile"; -import { Button, message } from "antd"; -import { CloseOutlined } from "@ant-design/icons"; -import style from "./Popups.module.scss"; -import { getUserList } from "../api"; - -interface AccountItem { - id: string | number; - nickname?: string; - wechatId?: string; - avatar?: string; - status?: string; - userinfo: { - alias: string; - nickname: string; - avatar: string; - wechatId: string; - }; - phone?: string; -} - -interface AccountListModalProps { - visible: boolean; - onClose: () => void; - ruleId?: number; - ruleName?: string; -} - -const AccountListModal: React.FC = ({ - visible, - onClose, - ruleId, - ruleName, -}) => { - const [accounts, setAccounts] = useState([]); - const [loading, setLoading] = useState(false); - - // 获取账号数据 - const fetchAccounts = async () => { - if (!ruleId) return; - - setLoading(true); - try { - const detailRes = await getUserList(ruleId.toString(), 1); - const accountData = detailRes?.list || []; - setAccounts(accountData); - } catch (error) { - console.error("获取账号详情失败:", error); - message.error("获取账号详情失败"); - } finally { - setLoading(false); - } - }; - - // 当弹窗打开且有ruleId时,获取数据 - useEffect(() => { - if (visible && ruleId) { - fetchAccounts(); - } - }, [visible, ruleId]); - - const title = ruleName ? `${ruleName} - 已添加账号列表` : "已添加账号列表"; - const getStatusColor = (status?: string | number) => { - // 确保status是数字类型 - const statusNum = Number(status); - - switch (statusNum) { - case 0: - return "#faad14"; // 待添加 - 黄色警告色 - case 1: - return "#1890ff"; // 添加中 - 蓝色进行中 - case 2: - return "#ff4d4f"; // 添加失败 - 红色错误色 - case 3: - return "#ff4d4f"; // 添加失败 - 红色错误色 - case 4: - return "#52c41a"; // 已添加 - 绿色成功色 - default: - return "#d9d9d9"; // 未知状态 - 灰色 - } - }; - - const getStatusText = (status?: number) => { - switch (status) { - case 0: - return "待添加"; - case 1: - return "添加中"; - case 2: - return "请求已发送待通过"; - case 3: - return "添加失败"; - case 4: - return "已添加"; - default: - return "未知状态"; - } - }; - - return ( - -
- {/* 头部 */} -
-

{title}

-
- - {/* 账号列表 */} -
- {loading ? ( -
- -
- 正在加载账号列表... -
-
- ) : accounts.length > 0 ? ( - accounts.map((account, index) => ( -
-
- -
-
-
- {account.userinfo.nickname || - account.userinfo.alias || - `账号${account.id}`} -
-
- {account.userinfo.wechatId || "未绑定微信号"} -
-
-
- - - {getStatusText(Number(account.status))} - -
-
- )) - ) : ( -
-
暂无账号数据
-
- )} -
- - {/* 底部统计 */} -
-
- 共 {accounts.length} 个账号 -
-
-
-
- ); -}; - -export default AccountListModal; diff --git a/Touchkebao/src/pages/mobile/scenarios/plan/list/components/DeviceListModal.tsx b/Touchkebao/src/pages/mobile/scenarios/plan/list/components/DeviceListModal.tsx deleted file mode 100644 index e220e3fc..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/plan/list/components/DeviceListModal.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { Popup, Avatar, SpinLoading } from "antd-mobile"; -import { Button, message } from "antd"; -import { CloseOutlined } from "@ant-design/icons"; -import style from "./Popups.module.scss"; -import { getPlanDetail } from "../api"; - -interface DeviceItem { - id: string | number; - memo?: string; - imei?: string; - wechatId?: string; - status?: "online" | "offline"; - avatar?: string; - totalFriend?: number; -} - -interface DeviceListModalProps { - visible: boolean; - onClose: () => void; - ruleId?: number; - ruleName?: string; -} - -const DeviceListModal: React.FC = ({ - visible, - onClose, - ruleId, - ruleName, -}) => { - const [devices, setDevices] = useState([]); - const [loading, setLoading] = useState(false); - - // 获取设备数据 - const fetchDevices = async () => { - if (!ruleId) return; - - setLoading(true); - try { - const detailRes = await getPlanDetail(ruleId.toString()); - const deviceData = detailRes?.deviceGroupsOptions || []; - setDevices(deviceData); - } catch (error) { - console.error("获取设备详情失败:", error); - message.error("获取设备详情失败"); - } finally { - setLoading(false); - } - }; - - // 当弹窗打开且有ruleId时,获取数据 - useEffect(() => { - if (visible && ruleId) { - fetchDevices(); - } - }, [visible, ruleId]); - - const title = ruleName ? `${ruleName} - 分发设备列表` : "分发设备列表"; - const getStatusColor = (status?: string) => { - return status === "online" ? "#52c41a" : "#ff4d4f"; - }; - - const getStatusText = (status?: string) => { - return status === "online" ? "在线" : "离线"; - }; - - return ( - -
- {/* 头部 */} -
-

{title}

-
- - {/* 设备列表 */} -
- {loading ? ( -
- -
正在加载设备列表...
-
- ) : devices.length > 0 ? ( - devices.map((device, index) => ( -
- {/* 顶部行:IMEI */} -
- - IMEI: {device.imei?.toUpperCase() || "-"} - -
- - {/* 主要内容区域:头像和详细信息 */} -
- {/* 头像 */} -
- {device.avatar ? ( - 头像 - ) : ( - - {(device.memo || device.wechatId || "设")[0]} - - )} -
- - {/* 设备信息 */} -
-
-

- {device.memo || "未命名设备"} -

- - {getStatusText(device.status)} - -
- -
-
- 微信号: - - {device.wechatId || "未绑定"} - -
-
- 好友数: - - {device.totalFriend ?? "-"} - -
-
-
-
-
- )) - ) : ( -
-
暂无设备数据
-
- )} -
- - {/* 底部统计 */} -
-
- 共 {devices.length} 个设备 -
-
-
-
- ); -}; - -export default DeviceListModal; diff --git a/Touchkebao/src/pages/mobile/scenarios/plan/list/components/OreadyAdd.tsx b/Touchkebao/src/pages/mobile/scenarios/plan/list/components/OreadyAdd.tsx deleted file mode 100644 index fa6e1853..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/plan/list/components/OreadyAdd.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { Popup, Avatar, SpinLoading } from "antd-mobile"; -import { Button, message } from "antd"; -import { CloseOutlined } from "@ant-design/icons"; -import style from "./Popups.module.scss"; -import { getUserList } from "../api"; - -interface AccountItem { - id: string | number; - nickname?: string; - wechatId?: string; - avatar?: string; - status?: string; - userinfo: { - alias: string; - nickname: string; - avatar: string; - wechatId: string; - }; - phone?: string; -} - -interface AccountListModalProps { - visible: boolean; - onClose: () => void; - ruleId?: number; - ruleName?: string; -} - -const AccountListModal: React.FC = ({ - visible, - onClose, - ruleId, - ruleName, -}) => { - const [accounts, setAccounts] = useState([]); - const [loading, setLoading] = useState(false); - - // 获取账号数据 - const fetchAccounts = async () => { - if (!ruleId) return; - - setLoading(true); - try { - const detailRes = await getUserList(ruleId.toString(), 2); - const accountData = detailRes?.list || []; - setAccounts(accountData); - } catch (error) { - console.error("获取账号详情失败:", error); - message.error("获取账号详情失败"); - } finally { - setLoading(false); - } - }; - - // 当弹窗打开且有ruleId时,获取数据 - useEffect(() => { - if (visible && ruleId) { - fetchAccounts(); - } - }, [visible, ruleId]); - - const title = ruleName ? `${ruleName} - 已添加账号列表` : "已添加账号列表"; - const getStatusColor = (status?: string) => { - switch (status) { - case "normal": - return "#52c41a"; - case "limited": - return "#faad14"; - case "blocked": - return "#ff4d4f"; - default: - return "#d9d9d9"; - } - }; - - const getStatusText = (status?: string) => { - switch (status) { - case "normal": - return "正常"; - case "limited": - return "受限"; - case "blocked": - return "封禁"; - default: - return "未知"; - } - }; - - return ( - -
- {/* 头部 */} -
-

{title}

-
- - {/* 账号列表 */} -
- {loading ? ( -
- -
- 正在加载账号列表... -
-
- ) : accounts.length > 0 ? ( - accounts.map((account, index) => ( -
-
- -
-
-
- {account.userinfo.nickname || - account.userinfo.alias || - `账号${account.id}`} -
-
- {account.userinfo.wechatId || "未绑定微信号"} -
-
-
- - - {getStatusText(account.status)} - -
-
- )) - ) : ( -
-
暂无账号数据
-
- )} -
- - {/* 底部统计 */} -
-
- 共 {accounts.length} 个账号 -
-
-
-
- ); -}; - -export default AccountListModal; diff --git a/Touchkebao/src/pages/mobile/scenarios/plan/list/components/PoolListModal.tsx b/Touchkebao/src/pages/mobile/scenarios/plan/list/components/PoolListModal.tsx deleted file mode 100644 index a084dc3c..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/plan/list/components/PoolListModal.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { Popup, SpinLoading } from "antd-mobile"; -import { Button, message } from "antd"; -import { CloseOutlined } from "@ant-design/icons"; -import style from "./Popups.module.scss"; -import { getFriendRequestTaskStats } from "../api"; -import LineChart2 from "@/components/LineChart2"; -interface StatisticsData { - totalAll: number; - totalError: number; - totalPass: number; - totalPassRate: number; - totalSuccess: number; - totalSuccessRate: number; -} - -interface PoolListModalProps { - visible: boolean; - onClose: () => void; - ruleId?: number; - ruleName?: string; -} - -const PoolListModal: React.FC = ({ - visible, - onClose, - ruleId, - ruleName, -}) => { - const [statistics, setStatistics] = useState({ - totalAll: 0, - totalError: 0, - totalPass: 0, - totalPassRate: 0, - totalSuccess: 0, - totalSuccessRate: 0, - }); - - const [xData, setXData] = useState([]); - const [yData, setYData] = useState([]); - const [loading, setLoading] = useState(false); - - // 当弹窗打开且有ruleId时,获取数据 - useEffect(() => { - if (visible && ruleId) { - setLoading(true); - getFriendRequestTaskStats(ruleId.toString()) - .then(res => { - console.log(res); - setXData(res.dateArray); - setYData([ - res.allNumArray, - res.errorNumArray, - res.passNumArray, - res.passRateArray, - res.successNumArray, - res.successRateArray, - ]); - setStatistics(res.totalStats); - setLoading(false); - }) - .finally(() => { - setLoading(false); - }); - } - }, [visible, ruleId]); - - const title = ruleName ? `${ruleName} - 累计统计数据` : "累计统计数据"; - return ( - -
- {/* 头部 */} -
-

{title}

-
- - {/* 统计数据表格 */} -
- {loading ? ( -
- -
- 正在加载统计数据... -
-
- ) : ( -
-
-
总计
-
- {statistics.totalAll} -
-
-
-
扫码
-
- {statistics.totalError} -
-
-
-
成功
-
- {statistics.totalSuccess} -
-
-
-
失败
-
- {statistics.totalError} -
-
-
-
通过
-
- {statistics.totalPass} -
-
-
-
成功率
-
- {statistics.totalSuccessRate}% -
-
-
-
通过率
-
- {statistics.totalPassRate}% -
-
-
- )} -
- - {/* 趋势图占位 */} -
-
趋势图
-
- -
-
-
-
- ); -}; - -export default PoolListModal; diff --git a/Touchkebao/src/pages/mobile/scenarios/plan/list/components/Popups.module.scss b/Touchkebao/src/pages/mobile/scenarios/plan/list/components/Popups.module.scss deleted file mode 100644 index cbfce7c1..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/plan/list/components/Popups.module.scss +++ /dev/null @@ -1,744 +0,0 @@ -.listToolbar { - display: flex; - align-items: center; - padding: 12px 0; - border-bottom: 1px solid #f0f0f0; - background: #fff; - font-size: 16px; - color: #222; -} - -.ruleList { - padding: 0 16px; -} - -.ruleCard { - background: #fff; - border-radius: 12px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); - margin-bottom: 20px; - padding: 16px; - border: 1px solid #ececec; - transition: - box-shadow 0.2s, - border-color 0.2s; - position: relative; -} -.ruleCard:hover { - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); - border-color: #b3e5fc; -} - -.ruleHeader { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 8px; -} -.ruleName { - font-size: 17px; - font-weight: 600; - color: #222; -} - -.ruleStatus { - display: flex; - align-items: center; - gap: 8px; -} - -.ruleSwitch { - margin-left: 4px; -} - -.ruleMenu { - margin-left: 8px; - cursor: pointer; - color: #888; - font-size: 18px; -} - -.ruleMeta { - display: flex; - justify-content: space-between; - margin-bottom: 10px; - font-size: 15px; - color: #444; - font-weight: 500; -} -.ruleMetaItem { - flex: 1; - text-align: center; - transition: background-color 0.2s ease; -} -.ruleMetaItem:not(:last-child) { - border-right: 1px solid #f0f0f0; -} -.ruleMetaItem:hover { - background-color: #f8f9fa; - border-radius: 6px; -} - -.ruleDivider { - border-top: 1px solid #f0f0f0; - margin: 12px 0 10px 0; -} - -.ruleStats { - display: flex; - justify-content: space-between; - font-size: 16px; - color: #222; - font-weight: 600; - margin-bottom: 8px; -} -.ruleStatsItem { - flex: 1; - text-align: center; -} -.ruleStatsItem:not(:last-child) { - border-right: 1px solid #f0f0f0; -} - -.ruleFooter { - display: flex; - justify-content: space-between; - font-size: 12px; - color: #888; - margin-top: 6px; - align-items: center; -} - -.ruleFooterIcon { - margin-right: 4px; - vertical-align: middle; - font-size: 15px; - position: relative; - top: -2px; -} - -.empty { - text-align: center; - color: #bbb; - padding: 40px 0; -} - -.pagination { - display: flex; - justify-content: center; - padding: 16px 0; - background: #fff; -} - -// 账号列表弹窗样式 -.accountModal { - height: 100%; - display: flex; - flex-direction: column; -} - -.accountModalHeader { - display: flex; - align-items: center; - justify-content: space-between; - padding: 16px 20px; - border-bottom: 1px solid #f0f0f0; - background: #fff; -} - -.accountModalTitle { - margin: 0; - font-size: 18px; - font-weight: 600; - color: #222; -} - -.accountModalClose { - border: none; - background: none; - color: #888; - font-size: 16px; -} - -.accountList { - flex: 1; - overflow-y: auto; - padding: 0 20px; -} - -.accountItem { - display: flex; - align-items: center; - padding: 12px 0; - border-bottom: 1px solid #f5f5f5; -} - -.accountItem:last-child { - border-bottom: none; -} - -.accountAvatar { - margin-right: 12px; - flex-shrink: 0; -} - -.accountInfo { - flex: 1; - min-width: 0; -} - -.accountName { - font-size: 16px; - font-weight: 500; - color: #222; - margin-bottom: 4px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.accountWechatId { - font-size: 14px; - color: #888; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.accountStatus { - display: flex; - align-items: center; - gap: 6px; - flex-shrink: 0; -} - -.statusDot { - width: 8px; - height: 8px; - border-radius: 50%; -} - -.statusText { - font-size: 13px; - color: #666; -} - -.accountEmpty { - display: flex; - align-items: center; - justify-content: center; - height: 200px; - color: #888; -} - -.accountEmptyText { - font-size: 16px; -} - -.accountLoading { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 200px; - gap: 16px; - padding: 20px; -} - -.accountLoadingText { - font-size: 15px; - color: #666; - font-weight: 500; -} - -.accountModalFooter { - padding: 16px 20px; - border-top: 1px solid #f0f0f0; - background: #fff; -} - -.accountStats { - text-align: center; - font-size: 14px; - color: #666; -} - -// 设备列表弹窗样式 -.deviceModal { - height: 100%; - display: flex; - flex-direction: column; -} - -.deviceModalHeader { - display: flex; - align-items: center; - justify-content: space-between; - padding: 16px 20px; - border-bottom: 1px solid #f0f0f0; - background: #fff; -} - -.deviceModalTitle { - margin: 0; - font-size: 18px; - font-weight: 600; - color: #222; -} - -.deviceModalClose { - border: none; - background: none; - color: #888; - font-size: 16px; -} - -.deviceList { - flex: 1; - overflow-y: auto; - padding: 0 20px; -} - -.deviceItem { - background: #fff; - border-radius: 12px; - padding: 12px; - margin-bottom: 12px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); - border: 1px solid #ececec; - transition: all 0.2s ease; -} - -.deviceItem:hover { - transform: translateY(-1px); - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); -} - -.deviceHeaderRow { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 8px; -} - -.deviceImeiText { - font-size: 13px; - color: #888; - font-weight: 500; -} - -.deviceMainContent { - display: flex; - align-items: center; -} - -.deviceAvatar { - width: 48px; - height: 48px; - border-radius: 12px; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - display: flex; - align-items: center; - justify-content: center; - margin-right: 12px; - flex-shrink: 0; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - overflow: hidden; -} - -.deviceAvatar img { - width: 100%; - height: 100%; - object-fit: cover; - border-radius: 12px; -} - -.deviceAvatarText { - color: #fff; - font-size: 18px; - font-weight: 600; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); -} - -.deviceInfo { - flex: 1; - min-width: 0; -} - -.deviceInfoHeader { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 6px; -} - -.deviceName { - margin: 0; - font-size: 16px; - font-weight: 600; - color: #222; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - flex: 1; - margin-right: 8px; -} - -.deviceStatusBadge { - padding: 2px 8px; - border-radius: 10px; - font-size: 12px; - font-weight: 500; - flex-shrink: 0; -} - -.deviceStatusOnline { - background: rgba(82, 196, 26, 0.1); - color: #52c41a; -} - -.deviceStatusOffline { - background: rgba(255, 77, 79, 0.1); - color: #ff4d4f; -} - -.deviceInfoList { - display: flex; - flex-direction: column; - gap: 4px; -} - -.deviceInfoItem { - display: flex; - align-items: center; - font-size: 13px; -} - -.deviceInfoLabel { - color: #888; - margin-right: 6px; - min-width: 50px; -} - -.deviceInfoValue { - color: #444; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.deviceFriendCount { - color: #1890ff; - font-weight: 500; -} - -.deviceEmpty { - display: flex; - align-items: center; - justify-content: center; - height: 200px; - color: #888; -} - -.deviceEmptyText { - font-size: 16px; -} - -.deviceLoading { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 200px; - gap: 16px; - padding: 20px; -} - -.deviceLoadingText { - font-size: 15px; - color: #666; - font-weight: 500; -} - -.deviceModalFooter { - padding: 16px 20px; - border-top: 1px solid #f0f0f0; - background: #fff; -} - -.deviceStats { - text-align: center; - font-size: 14px; - color: #666; -} - -// 流量池列表弹窗样式 -.poolModal { - height: 100%; - display: flex; - flex-direction: column; -} - -.poolModalHeader { - display: flex; - align-items: center; - justify-content: space-between; - padding: 16px 20px; - border-bottom: 1px solid #f0f0f0; - background: #fff; -} - -.poolModalTitle { - margin: 0; - font-size: 18px; - font-weight: 600; - color: #222; -} - -.poolModalClose { - border: none; - background: none; - color: #888; - font-size: 16px; -} - -.poolList { - flex: 1; - overflow-y: auto; - padding: 0 20px; -} - -.poolItem { - background: #fff; - border-radius: 12px; - padding: 12px; - margin-bottom: 12px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); - border: 1px solid #ececec; - transition: all 0.2s ease; -} - -.poolItem:hover { - transform: translateY(-1px); - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); -} - -.poolMainContent { - display: flex; - align-items: flex-start; -} - -.poolIcon { - width: 48px; - height: 48px; - border-radius: 12px; - background: linear-gradient(135deg, #1890ff 0%, #722ed1 100%); - display: flex; - align-items: center; - justify-content: center; - margin-right: 12px; - flex-shrink: 0; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); -} - -.poolIconText { - color: #fff; - font-size: 18px; - font-weight: 600; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); -} - -.poolInfo { - flex: 1; - min-width: 0; -} - -.poolInfoHeader { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 6px; -} - -.poolName { - margin: 0; - font-size: 16px; - font-weight: 600; - color: #222; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - flex: 1; - margin-right: 8px; -} - -.poolUserCount { - padding: 2px 8px; - border-radius: 10px; - font-size: 12px; - font-weight: 500; - background: rgba(24, 144, 255, 0.1); - color: #1890ff; - flex-shrink: 0; -} - -.poolInfoList { - display: flex; - flex-direction: column; - gap: 4px; - margin-bottom: 8px; -} - -.poolInfoItem { - display: flex; - align-items: center; - font-size: 13px; -} - -.poolInfoLabel { - color: #888; - margin-right: 6px; - min-width: 60px; -} - -.poolInfoValue { - color: #444; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.poolTags { - display: flex; - flex-wrap: wrap; - gap: 6px; -} - -.poolTag { - padding: 2px 8px; - border-radius: 10px; - font-size: 11px; - background: rgba(0, 0, 0, 0.05); - color: #666; - border: 1px solid rgba(0, 0, 0, 0.1); -} - -.poolLoading { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 200px; - gap: 16px; - padding: 20px; -} - -.poolLoadingText { - font-size: 15px; - color: #666; - font-weight: 500; -} - -.poolEmpty { - display: flex; - align-items: center; - justify-content: center; - height: 200px; - color: #888; -} - -.poolEmptyText { - font-size: 16px; -} - -.poolModalFooter { - padding: 16px 20px; - border-top: 1px solid #f0f0f0; - background: #fff; -} - -.poolStats { - text-align: center; - font-size: 14px; - color: #666; -} - -// 统计数据弹窗样式 -.statisticsContent { - flex: 1; - overflow-y: auto; - padding: 0 20px; -} - -.statisticsLoading { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 200px; - gap: 16px; - padding: 20px; -} - -.statisticsLoadingText { - font-size: 15px; - color: #666; - font-weight: 500; -} - -.statisticsTable { - padding: 16px 0; -} - -.statisticsRow { - display: flex; - align-items: center; - justify-content: space-between; - padding: 12px 0; - border-bottom: 1px solid #f5f5f5; -} - -.statisticsRow:last-child { - border-bottom: none; -} - -.statisticsLabel { - font-size: 15px; - color: #666; - font-weight: 500; -} - -.statisticsValue { - font-size: 16px; - color: #222; - font-weight: 600; -} - -.trendChart { - padding: 20px; - border-top: 1px solid #f0f0f0; - background: #fff; -} - -.chartTitle { - font-size: 16px; - font-weight: 600; - color: #222; - margin-bottom: 16px; -} - -.chartPlaceholder { - height: 200px; - background: #f8f9fa; - border-radius: 12px; - display: flex; - align-items: center; - justify-content: center; - border: 2px dashed #d9d9d9; -} - -.chartNote { - font-size: 14px; - color: #888; - text-align: center; -} diff --git a/Touchkebao/src/pages/mobile/scenarios/plan/list/data.ts b/Touchkebao/src/pages/mobile/scenarios/plan/list/data.ts deleted file mode 100644 index 77e1df19..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/plan/list/data.ts +++ /dev/null @@ -1,60 +0,0 @@ -export interface Task { - id: string; - name: string; - status: number; - created_at: string; - updated_at: string; - enabled: boolean; - total_customers?: number; - today_customers?: number; - lastUpdated?: string; - stats?: { - devices?: number; - acquired?: number; - added?: number; - }; - reqConf?: { - device?: string[]; - selectedDevices?: string[]; - }; - acquiredCount?: number; - addedCount?: number; - passRate?: number; - passCount?: number; -} - -export interface ApiSettings { - apiKey: string; - webhookUrl: string; - taskId: string; -} - -// API响应相关类型 -export interface TextUrl { - apiKey: string; - originalString?: string; - sign?: string; - fullUrl: string; -} - -export interface PlanDetail { - id: number; - name: string; - scenario: number; - enabled: boolean; - status: number; - apiKey: string; - textUrl: TextUrl; - [key: string]: any; -} - -export interface ApiResponse { - code: number; - msg?: string; - data: T; -} - -export interface PlanListResponse { - list: Task[]; - total: number; -} diff --git a/Touchkebao/src/pages/mobile/scenarios/plan/list/index.module.scss b/Touchkebao/src/pages/mobile/scenarios/plan/list/index.module.scss deleted file mode 100644 index d5fc2004..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/plan/list/index.module.scss +++ /dev/null @@ -1,444 +0,0 @@ -.scenario-list-page { - padding: 0 16px; -} - -.loading { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 60vh; - gap: 16px; -} - -.loading-text { - color: #666; - font-size: 14px; -} - -.search-bar { - display: flex; - gap: 12px; - align-items: center; - padding: 16px; -} - -.search-input-wrapper { - position: relative; - flex: 1; - - .ant-input { - border-radius: 8px; - height: 40px; - } -} - -.plan-list { - display: flex; - flex-direction: column; - gap: 12px; -} - -.pagination-container { - display: flex; - justify-content: center; - padding: 14px 0; - background: white; - border-radius: 12px; - margin-top: 16px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - - :global(.ant-pagination) { - .ant-pagination-item { - border-radius: 6px; - border: 1px solid #d9d9d9; - - &:hover { - border-color: var(--primary-color); - } - - &.ant-pagination-item-active { - background: var(--primary-color); - border-color: var(--primary-color); - - a { - color: white; - } - } - } - - .ant-pagination-prev, - .ant-pagination-next { - border-radius: 6px; - border: 1px solid #d9d9d9; - - &:hover { - border-color: var(--primary-color); - color: var(--primary-color); - } - } - } -} - -.plan-item { - background: white; - border-radius: 12px; - padding: 16px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - transition: all 0.2s ease; - - &:hover { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - } -} - -.plan-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 16px; -} - -.plan-name { - font-size: 16px; - font-weight: 600; - color: #333; - flex: 1; - margin-right: 12px; -} - -.plan-header-right { - display: flex; - align-items: center; - gap: 8px; -} - -.more-btn { - padding: 4px; - min-width: auto; - height: 28px; - width: 28px; - border-radius: 4px; - - &:hover { - background-color: #f5f5f5; - } -} - -.stats-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 12px; - margin-bottom: 16px; -} - -.stat-item { - background: #f8f9fa; - border-radius: 8px; - padding: 12px; - text-align: center; - border: 1px solid #e9ecef; - cursor: pointer; - transition: all 0.2s ease; - position: relative; - - &:hover { - background: #e6f7ff; - border-color: #91d5ff; - transform: translateY(-1px); - box-shadow: 0 2px 8px rgba(24, 144, 255, 0.15); - } - - &:active { - transform: translateY(0); - box-shadow: 0 1px 4px rgba(24, 144, 255, 0.1); - } - - &:hover::after { - opacity: 1; - } -} - -.stat-label { - font-size: 12px; - color: #666; - margin-bottom: 4px; - font-weight: 500; -} - -.stat-value { - font-size: 18px; - font-weight: 600; - color: #333; - line-height: 1.2; -} - -.plan-footer { - border-top: 1px solid #f0f0f0; - padding-top: 12px; - display: flex; - justify-content: space-between; -} - -.last-execution { - display: flex; - align-items: center; - gap: 6px; - font-size: 12px; - color: #999; - - svg { - font-size: 14px; - color: #999; - } -} - -.empty-state { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 60px 20px; - text-align: center; -} - -.empty-text { - color: #999; - font-size: 14px; - margin-bottom: 20px; -} - -.create-first-btn { - height: 40px; - padding: 0 24px; - border-radius: 20px; -} - -// 加载更多按钮样式 -.load-more-container { - display: flex; - justify-content: center; - padding: 20px 0; -} - -.load-more-btn { - height: 44px; - padding: 0 32px; - border-radius: 22px; - font-size: 16px; - font-weight: 500; - display: flex; - align-items: center; - gap: 8px; - transition: all 0.2s ease; - - &:hover { - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - } - - &:active { - transform: translateY(0); - } -} - -// 没有更多数据提示样式 -.no-more-data { - display: flex; - justify-content: center; - align-items: center; - padding: 20px 0; - color: #999; - font-size: 14px; - - span { - position: relative; - padding: 0 20px; - - &::before, - &::after { - content: ""; - position: absolute; - top: 50%; - width: 40px; - height: 1px; - background-color: #e0e0e0; - } - - &::before { - left: -50px; - } - - &::after { - right: -50px; - } - } -} - -.action-menu-dialog { - background: white; - border-radius: 16px 16px 0 0; - padding: 20px; - max-height: 60vh; - display: flex; - flex-direction: column; -} - -.action-menu-item { - display: flex; - align-items: center; - gap: 12px; - padding: 16px; - border-radius: 8px; - cursor: pointer; - transition: background-color 0.2s ease; - - &:hover { - background-color: #f5f5f5; - } - - &.danger { - color: #ff4d4f; - - &:hover { - background-color: #fff2f0; - } - } -} - -.action-icon { - font-size: 16px; - width: 20px; - text-align: center; -} - -.action-text { - font-size: 16px; - font-weight: 500; -} - -.dialog-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 20px; - padding-bottom: 16px; - border-bottom: 1px solid #f0f0f0; - - h3 { - margin: 0; - font-size: 18px; - font-weight: 600; - color: #333; - } -} - -.dialog-content { - flex: 1; - text-align: center; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; -} - -.qr-dialog { - background: white; - border-radius: 16px; - padding: 20px; - width: 100%; -} - -.qr-loading { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 40px 20px; - gap: 16px; - color: #666; - font-size: 14px; -} - -.qr-image { - width: 100%; - max-width: 200px; - height: auto; - border-radius: 8px; -} - -.qr-error { - text-align: center; - color: #ff4d4f; - font-size: 14px; - padding: 40px 20px; -} - -.qr-link-section { - margin-top: 20px; - width: 100%; - padding: 0 10px; -} - -.link-label { - font-size: 14px; - font-weight: 500; - color: #333; - margin-bottom: 8px; - text-align: left; -} - -.link-input-wrapper { - display: flex; - gap: 8px; - align-items: center; - width: 100%; - - @media (max-width: 480px) { - flex-direction: column; - gap: 12px; - } -} - -.link-input { - flex: 1; - - .ant-input { - border-radius: 8px; - font-size: 12px; - color: #666; - background-color: #f8f9fa; - border: 1px solid #e9ecef; - - &:focus { - border-color: #1890ff; - box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); - } - } - - @media (max-width: 480px) { - width: 100%; - } -} - -.copy-button { - height: 32px; - padding: 0 12px; - border-radius: 8px; - font-size: 12px; - display: flex; - align-items: center; - gap: 4px; - white-space: nowrap; - flex-shrink: 0; - - .anticon { - font-size: 12px; - } - - @media (max-width: 480px) { - width: 100%; - justify-content: center; - } -} diff --git a/Touchkebao/src/pages/mobile/scenarios/plan/list/index.tsx b/Touchkebao/src/pages/mobile/scenarios/plan/list/index.tsx deleted file mode 100644 index 5e3573c1..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/plan/list/index.tsx +++ /dev/null @@ -1,671 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { useParams, useNavigate } from "react-router-dom"; -import { - Button, - Toast, - SpinLoading, - Dialog, - Popup, - Card, - Tag, -} from "antd-mobile"; -import { Input, Pagination } from "antd"; -import { - PlusOutlined, - CopyOutlined, - DeleteOutlined, - SettingOutlined, - SearchOutlined, - ReloadOutlined, - QrcodeOutlined, - EditOutlined, - MoreOutlined, - ClockCircleOutlined, -} from "@ant-design/icons"; -import NavCommon from "@/components/NavCommon"; -import Layout from "@/components/Layout/Layout"; -import { - getPlanList, - getPlanDetail, - copyPlan, - deletePlan, - getWxMinAppCode, -} from "./api"; -import style from "./index.module.scss"; -import { Task, ApiSettings, PlanDetail } from "./data"; -import PlanApi from "./planApi"; -import { buildApiUrl } from "@/utils/apiUrl"; -import DeviceListModal from "./components/DeviceListModal"; -import AccountListModal from "./components/AccountListModal"; -import OreadyAdd from "./components/OreadyAdd"; -import PoolListModal from "./components/PoolListModal"; - -const ScenarioList: React.FC = () => { - const { scenarioId, scenarioName } = useParams<{ - scenarioId: string; - scenarioName: string; - }>(); - const navigate = useNavigate(); - const [tasks, setTasks] = useState([]); - const [loading, setLoading] = useState(true); - const [showApiDialog, setShowApiDialog] = useState(false); - const [currentApiSettings, setCurrentApiSettings] = useState({ - apiKey: "", - webhookUrl: "", - taskId: "", - }); - const [searchTerm, setSearchTerm] = useState(""); - const [loadingTasks, setLoadingTasks] = useState(false); - const [showQrDialog, setShowQrDialog] = useState(false); - const [qrLoading, setQrLoading] = useState(false); - const [qrImg, setQrImg] = useState(""); - const [currentTaskId, setCurrentTaskId] = useState(""); - const [showActionMenu, setShowActionMenu] = useState(null); - - // 设备列表弹窗状态 - const [showDeviceList, setShowDeviceList] = useState(false); - const [currentTask, setCurrentTask] = useState(null); - - // 账号列表弹窗状态 - const [showAccountList, setShowAccountList] = useState(false); - - // 已添加弹窗状态 - const [showOreadyAdd, setShowOreadyAdd] = useState(false); - - // 通过率弹窗状态 - const [showPoolList, setShowPoolList] = useState(false); - - // 分页相关状态 - const [currentPage, setCurrentPage] = useState(1); - const [hasMore, setHasMore] = useState(true); - const [loadingMore, setLoadingMore] = useState(false); - const [total, setTotal] = useState(0); - const pageSize = 20; - - // 获取计划列表数据 - const fetchPlanList = async (page: number, isLoadMore: boolean = false) => { - if (!scenarioId) return; - - if (isLoadMore) { - setLoadingMore(true); - } else { - setLoadingTasks(true); - } - - try { - const response = await getPlanList({ - sceneId: scenarioId, - page: page, - pageSize: pageSize, - }); - - if (response && response.list) { - if (isLoadMore) { - // 加载更多时,追加数据 - setTasks(prev => [...prev, ...response.list]); - } else { - // 首次加载或刷新时,替换数据 - setTasks(response.list); - } - - // 更新分页信息 - setTotal(response.total || 0); - setHasMore(response.list.length === pageSize); - setCurrentPage(page); - } - } catch (error) { - console.error("获取计划列表失败:", error); - if (!isLoadMore) { - setTasks([]); - } - Toast.show({ - content: "获取数据失败", - position: "top", - }); - } finally { - if (isLoadMore) { - setLoadingMore(false); - } else { - setLoadingTasks(false); - } - } - }; - - useEffect(() => { - const fetchScenarioData = async () => { - if (!scenarioId) return; - setLoading(true); - - try { - await fetchPlanList(1, false); - } catch (error) { - console.error("获取场景数据失败:", error); - setTasks([]); - } finally { - setLoading(false); - } - }; - - fetchScenarioData(); - }, [scenarioId]); - - // 分页改变处理 - const handlePageChange = async (page: number) => { - setCurrentPage(page); - await fetchPlanList(page, false); - }; - - const handleCopyPlan = async (taskId: string) => { - const taskToCopy = tasks.find(task => task.id === taskId); - if (!taskToCopy) return; - - try { - await copyPlan(taskId); - Toast.show({ - content: `已成功复制"${taskToCopy.name}"`, - position: "top", - }); - // 刷新列表 - handleRefresh(); - } catch (error) { - Toast.show({ - content: "复制失败,请重试", - position: "top", - }); - } - }; - - const handleDeletePlan = async (taskId: string) => { - const taskToDelete = tasks.find(task => task.id === taskId); - if (!taskToDelete) return; - - const result = await Dialog.confirm({ - content: `确定要删除"${taskToDelete.name}"吗?`, - confirmText: "删除", - cancelText: "取消", - }); - - if (result) { - try { - await deletePlan(taskId); - Toast.show({ - content: "计划已删除", - position: "top", - }); - // 刷新列表 - handleRefresh(); - } catch (error) { - Toast.show({ - content: "删除失败,请重试", - position: "top", - }); - } - } - }; - - const handleOpenApiSettings = async (taskId: string) => { - try { - const response: PlanDetail = await getPlanDetail(taskId); - if (response) { - // 处理webhook URL,使用工具函数构建完整地址 - const webhookUrl = buildApiUrl( - response.textUrl?.fullUrl || `webhook/${taskId}`, - ); - - setCurrentApiSettings({ - apiKey: response.apiKey || "demo-api-key-123456", - webhookUrl: webhookUrl, - taskId: taskId, - }); - setShowApiDialog(true); - } - } catch (error) { - Toast.show({ - content: "获取计划接口失败", - position: "top", - }); - } - }; - - const handleCreateNewPlan = () => { - navigate(`/scenarios/new/${scenarioId}`); - }; - - const handleShowQrCode = async (taskId: string) => { - setQrLoading(true); - setShowQrDialog(true); - setQrImg(""); - setCurrentTaskId(taskId); // 设置当前任务ID - - try { - const response = await getWxMinAppCode(taskId); - setQrImg(response); - } catch (error) { - Toast.show({ - content: "获取二维码失败", - position: "top", - }); - } finally { - setQrLoading(false); - } - }; - - // 处理设备列表弹窗 - const handleShowDeviceList = (task: Task) => { - setCurrentTask(task); - setShowDeviceList(true); - }; - - // 处理账号列表弹窗 - const handleShowAccountList = (task: Task) => { - setCurrentTask(task); - setShowAccountList(true); - }; - - // 处理已添加弹窗 - const handleShowOreadyAdd = (task: Task) => { - setCurrentTask(task); - setShowOreadyAdd(true); - }; - - // 处理通过率弹窗 - const handleShowPoolList = (task: Task) => { - setCurrentTask(task); - setShowPoolList(true); - }; - - const getStatusColor = (status: number) => { - switch (status) { - case 1: - return "success"; - case 0: - return "default"; - case -1: - return "danger"; - default: - return "default"; - } - }; - - const getStatusText = (status: number) => { - switch (status) { - case 1: - return "进行中"; - case 0: - return "已暂停"; - case -1: - return "已停止"; - default: - return "未知"; - } - }; - - const handleRefresh = async () => { - // 重置分页状态 - setCurrentPage(1); - setHasMore(true); - await fetchPlanList(1, false); - }; - - const filteredTasks = tasks.filter(task => - task.name.toLowerCase().includes(searchTerm.toLowerCase()), - ); - - // 生成操作菜单 - const getActionMenu = (task: Task) => [ - { - key: "edit", - text: "编辑计划", - icon: , - onClick: () => { - setShowActionMenu(null); - navigate(`/scenarios/edit/${task.id}`); - }, - }, - { - key: "copy", - text: "复制计划", - icon: , - onClick: () => { - setShowActionMenu(null); - handleCopyPlan(task.id); - }, - }, - { - key: "settings", - text: "计划接口", - icon: , - onClick: () => { - setShowActionMenu(null); - handleOpenApiSettings(task.id); - }, - }, - - { - key: "delete", - text: "删除计划", - icon: , - onClick: () => { - setShowActionMenu(null); - handleDeletePlan(task.id); - }, - danger: true, - }, - ]; - - const deviceCount = (task: Task) => { - return Array.isArray(task.reqConf?.device) - ? task.reqConf!.device.length - : Array.isArray(task.reqConf?.selectedDevices) - ? task.reqConf!.selectedDevices.length - : 0; - }; - - return ( - - navigate("/scenarios")} - title={scenarioName || ""} - right={ - - } - /> - - {/* 搜索栏 */} -
-
- setSearchTerm(e.target.value)} - prefix={} - allowClear - size="large" - /> -
- -
- - } - loading={loading} - footer={ -
- -
- } - > -
- {/* 计划列表 */} -
- {filteredTasks.length === 0 ? ( -
-
- {searchTerm ? "没有找到匹配的计划" : "暂无计划"} -
- -
- ) : ( - <> - {filteredTasks.map(task => ( - - {/* 头部:标题、状态和操作菜单 */} -
-
{task.name}
-
- - {getStatusText(task.status)} - - -
-
- - {/* 统计数据网格 */} -
-
{ - e.stopPropagation(); // 阻止事件冒泡,避免触发卡片点击 - handleShowDeviceList(task); - }} - > -
设备数
-
- {deviceCount(task)} -
-
-
{ - e.stopPropagation(); // 阻止事件冒泡,避免触发卡片点击 - handleShowAccountList(task); - }} - > -
已获客
-
- {task?.acquiredCount || 0} -
-
-
{ - e.stopPropagation(); // 阻止事件冒泡,避免触发卡片点击 - handleShowOreadyAdd(task); - }} - > -
已添加
-
- {task.passCount || 0} -
-
-
{ - e.stopPropagation(); // 阻止事件冒泡,避免触发卡片点击 - handleShowPoolList(task); - }} - > -
通过率
-
- {task.passRate}% -
-
-
- - {/* 底部:上次执行时间 */} -
-
- - 上次执行: {task.lastUpdated || "--"} -
-
- { - setShowActionMenu(null); - handleShowQrCode(task.id); - }} - /> -
-
-
- ))} - - )} -
- - {/* 计划接口弹窗 */} - setShowApiDialog(false)} - apiKey={currentApiSettings.apiKey} - webhookUrl={currentApiSettings.webhookUrl} - taskId={currentApiSettings.taskId} - /> - - {/* 操作菜单弹窗 */} - setShowActionMenu(null)} - position="bottom" - bodyStyle={{ height: "auto", maxHeight: "60vh" }} - > -
-
-

操作菜单

- -
-
- {showActionMenu && - getActionMenu(tasks.find(t => t.id === showActionMenu)!).map( - item => ( -
- {item.icon} - {item.text} -
- ), - )} -
-
-
- - {/* 二维码弹窗 */} - setShowQrDialog(false)} - position="bottom" - > -
-
-

小程序二维码

- -
-
- {qrLoading ? ( -
- -
生成二维码中...
-
- ) : qrImg ? ( - <> - 小程序二维码 - {/* 链接复制区域 */} -
-
小程序链接
-
- - -
-
- - ) : ( -
二维码生成失败
- )} -
-
-
- - {/* 设备列表弹窗 */} - setShowDeviceList(false)} - ruleId={currentTask?.id ? parseInt(currentTask.id) : undefined} - ruleName={currentTask?.name} - /> - - {/* 账号列表弹窗 */} - setShowAccountList(false)} - ruleId={currentTask?.id ? parseInt(currentTask.id) : undefined} - ruleName={currentTask?.name} - /> - - {/* 已添加弹窗 */} - setShowOreadyAdd(false)} - ruleId={currentTask?.id ? parseInt(currentTask.id) : undefined} - ruleName={currentTask?.name} - /> - - {/* 通过率弹窗 */} - setShowPoolList(false)} - ruleId={currentTask?.id ? parseInt(currentTask.id) : undefined} - ruleName={currentTask?.name} - /> -
-
- ); -}; - -export default ScenarioList; diff --git a/Touchkebao/src/pages/mobile/scenarios/plan/list/planApi.module.scss b/Touchkebao/src/pages/mobile/scenarios/plan/list/planApi.module.scss deleted file mode 100644 index 48fe1565..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/plan/list/planApi.module.scss +++ /dev/null @@ -1,601 +0,0 @@ -// 移动端样式 -.plan-api-dialog { - background: white; - border-radius: 16px 16px 0 0; - height: 100%; - display: flex; - flex-direction: column; - overflow: hidden; -} - -.dialog-header { - display: flex; - justify-content: space-between; - align-items: flex-start; - padding: 20px; - border-bottom: 1px solid #f0f0f0; - background: #fafafa; - - .header-left { - display: flex; - align-items: flex-start; - gap: 12px; - flex: 1; - } - - .header-icon { - font-size: 24px; - color: #1890ff; - margin-top: 4px; - } - - .header-content { - flex: 1; - - h3 { - margin: 0 0 8px 0; - font-size: 18px; - font-weight: 600; - color: #333; - } - - p { - margin: 0; - font-size: 14px; - color: #666; - line-height: 1.5; - } - } - - .close-btn { - width: 32px; - height: 32px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-size: 18px; - color: #999; - background: transparent; - border: none; - cursor: pointer; - - &:hover { - background: #f5f5f5; - } - } -} - -.nav-tabs { - display: flex; - background: white; - border-bottom: 1px solid #f0f0f0; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - - .nav-tab { - flex: 1; - min-width: 80px; - padding: 12px 8px; - border: none; - background: transparent; - color: #666; - font-size: 14px; - cursor: pointer; - display: flex; - flex-direction: column; - align-items: center; - gap: 4px; - transition: all 0.2s ease; - white-space: nowrap; - - svg { - font-size: 16px; - } - - &:hover { - color: #1890ff; - } - - &.active { - color: #1890ff; - border-bottom: 2px solid #1890ff; - } - } -} - -.dialog-content { - flex: 1; - overflow-y: auto; - padding: 20px; -} - -.dialog-footer { - display: flex; - justify-content: space-between; - align-items: center; - padding: 16px 20px; - border-top: 1px solid #f0f0f0; - background: #fafafa; - - .security-note { - display: flex; - align-items: center; - gap: 6px; - font-size: 12px; - color: #666; - - svg { - color: #52c41a; - } - } - - .complete-btn { - height: 36px; - padding: 0 24px; - border-radius: 18px; - } -} - -// 配置内容样式 -.config-content { - .config-section { - margin-bottom: 24px; - - &:last-child { - margin-bottom: 0; - } - } - - .section-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 12px; - - .section-title { - display: flex; - align-items: center; - gap: 8px; - font-size: 16px; - font-weight: 600; - color: #333; - - .section-icon { - color: #1890ff; - } - } - } - - .input-group { - display: flex; - gap: 8px; - margin-bottom: 12px; - - .api-input { - flex: 1; - border-radius: 8px; - } - - .copy-btn { - height: 40px; - padding: 0 16px; - border-radius: 8px; - display: flex; - align-items: center; - gap: 4px; - } - } - - .security-tip { - padding: 12px; - background: #fff7e6; - border: 1px solid #ffd591; - border-radius: 8px; - font-size: 12px; - color: #d46b08; - line-height: 1.5; - } - - .params-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 12px; - margin-top: 16px; - } - - .param-section { - background: #f8f9fa; - border-radius: 8px; - padding: 12px; - border: 1px solid #e9ecef; - - h4 { - margin: 0 0 8px 0; - font-size: 14px; - font-weight: 600; - color: #333; - } - - .param-list { - font-size: 12px; - color: #666; - line-height: 1.6; - - div { - margin-bottom: 4px; - } - - code { - background: #e9ecef; - padding: 2px 4px; - border-radius: 4px; - font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace; - font-size: 11px; - } - } - } -} - -// 测试内容样式 -.test-content { - .test-section { - h3 { - margin: 0 0 16px 0; - font-size: 16px; - font-weight: 600; - color: #333; - } - - .test-input { - margin-bottom: 16px; - border-radius: 8px; - } - - .test-buttons { - display: flex; - gap: 12px; - - .test-btn { - flex: 1; - height: 40px; - border-radius: 8px; - display: flex; - align-items: center; - justify-content: center; - gap: 6px; - } - } - } -} - -// 文档内容样式 -.docs-content { - .docs-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 16px; - } - - .doc-card { - text-align: center; - padding: 24px 16px; - border-radius: 12px; - border: 1px solid #f0f0f0; - transition: all 0.2s ease; - - &:hover { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); - } - - .doc-icon { - width: 48px; - height: 48px; - border-radius: 50%; - background: #f0f8ff; - display: flex; - align-items: center; - justify-content: center; - margin: 0 auto 16px; - font-size: 24px; - color: #1890ff; - } - - h4 { - margin: 0 0 8px 0; - font-size: 16px; - font-weight: 600; - color: #333; - } - - p { - margin: 0; - font-size: 14px; - color: #666; - line-height: 1.5; - } - } -} - -// 代码内容样式 -.code-content { - .language-tabs { - display: flex; - gap: 8px; - margin-bottom: 16px; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - - .lang-tab { - padding: 8px 16px; - border: 1px solid #d9d9d9; - background: white; - border-radius: 6px; - font-size: 14px; - color: #666; - cursor: pointer; - transition: all 0.2s ease; - white-space: nowrap; - - &:hover { - border-color: #1890ff; - color: #1890ff; - } - - &.active { - background: #1890ff; - border-color: #1890ff; - color: white; - } - } - } - - .code-block { - position: relative; - background: #f6f8fa; - border-radius: 8px; - border: 1px solid #e1e4e8; - overflow: hidden; - - .code { - margin: 0; - padding: 16px; - font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace; - font-size: 13px; - line-height: 1.5; - color: #24292e; - overflow-x: auto; - white-space: pre-wrap; - word-break: break-all; - } - - .copy-code-btn { - position: absolute; - top: 8px; - right: 8px; - height: 32px; - padding: 0 12px; - border-radius: 6px; - display: flex; - align-items: center; - gap: 4px; - font-size: 12px; - } - } -} - -// PC端样式覆盖 -.plan-api-modal { - .plan-api-dialog { - border-radius: 12px; - height: auto; - max-height: 80vh; - } - - .nav-tabs { - .nav-tab { - min-width: 100px; - padding: 16px 12px; - font-size: 15px; - - svg { - font-size: 18px; - } - } - } - - .dialog-content { - padding: 24px; - } - - .params-grid { - grid-template-columns: 1fr 1fr; - } - - .docs-grid { - grid-template-columns: 1fr 1fr; - } - - .test-buttons { - flex-direction: row; - } -} - -// 响应式设计 -@media (max-width: 768px) { - .plan-api-dialog { - .header-content { - h3 { - font-size: 16px; - } - - p { - font-size: 13px; - } - } - } - - .nav-tabs { - .nav-tab { - font-size: 13px; - padding: 10px 6px; - - svg { - font-size: 14px; - } - } - } - - .dialog-content { - padding: 16px; - } - - .config-content { - .params-grid { - grid-template-columns: 1fr; - gap: 8px; - } - } - - .docs-content { - .docs-grid { - grid-template-columns: 1fr; - gap: 12px; - } - } - - .test-content { - .test-buttons { - flex-direction: column; - gap: 8px; - } - } - - .code-content { - .language-tabs { - .lang-tab { - padding: 6px 12px; - font-size: 13px; - } - } - - .code-block { - .code { - font-size: 12px; - padding: 12px; - } - } - } -} - -// 暗色主题支持 -@media (prefers-color-scheme: dark) { - .plan-api-dialog { - background: #1f1f1f; - color: #fff; - - .dialog-header { - background: #262626; - border-bottom-color: #434343; - - .header-content { - h3 { - color: #fff; - } - - p { - color: #a6a6a6; - } - } - } - - .nav-tabs { - background: #262626; - border-bottom-color: #434343; - - .nav-tab { - color: #a6a6a6; - - &:hover { - color: #1890ff; - } - - &.active { - color: #1890ff; - border-bottom-color: #1890ff; - } - } - } - - .dialog-footer { - background: #262626; - border-top-color: #434343; - - .security-note { - color: #a6a6a6; - } - } - } - - .config-content { - .section-title { - color: #fff; - } - - .security-tip { - background: #2a1f00; - border-color: #d48806; - color: #ffc53d; - } - - .param-section { - background: #262626; - border-color: #434343; - - h4 { - color: #fff; - } - - .param-list { - color: #a6a6a6; - - code { - background: #434343; - } - } - } - } - - .test-content { - h3 { - color: #fff; - } - } - - .docs-content { - .doc-card { - background: #262626; - border-color: #434343; - - h4 { - color: #fff; - } - - p { - color: #a6a6a6; - } - } - } - - .code-content { - .code-block { - background: #0d1117; - border-color: #30363d; - - .code { - color: #c9d1d9; - } - } - } -} diff --git a/Touchkebao/src/pages/mobile/scenarios/plan/list/planApi.tsx b/Touchkebao/src/pages/mobile/scenarios/plan/list/planApi.tsx deleted file mode 100644 index ee67b59c..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/plan/list/planApi.tsx +++ /dev/null @@ -1,437 +0,0 @@ -import React, { useState, useMemo } from "react"; -import { Popup, Button, Toast, SpinLoading } from "antd-mobile"; -import { Modal, Input, Tabs, Card, Tag, Space } from "antd"; -import { - CopyOutlined, - CodeOutlined, - BookOutlined, - ThunderboltOutlined, - SettingOutlined, - LinkOutlined, - SafetyOutlined, - CheckCircleOutlined, -} from "@ant-design/icons"; -import style from "./planApi.module.scss"; -import { buildApiUrl } from "@/utils/apiUrl"; - -/** - * 计划接口配置弹窗组件 - * - * 使用示例: - * ```tsx - * const [showApiDialog, setShowApiDialog] = useState(false); - * const [apiSettings, setApiSettings] = useState({ - * apiKey: "your-api-key", - * webhookUrl: "https://api.example.com/webhook", - * taskId: "task-123" - * }); - * - * setShowApiDialog(false)} - * apiKey={apiSettings.apiKey} - * webhookUrl={apiSettings.webhookUrl} - * taskId={apiSettings.taskId} - * /> - * ``` - * - * 特性: - * - 移动端使用 Popup,PC端使用 Modal - * - 支持四个标签页:接口配置、快速测试、开发文档、代码示例 - * - 支持多种编程语言的代码示例 - * - 响应式设计,自适应不同屏幕尺寸 - * - 支持暗色主题 - * - 自动拼接API地址前缀 - */ - -interface PlanApiProps { - visible: boolean; - onClose: () => void; - apiKey: string; - webhookUrl: string; - taskId: string; -} - -interface ApiSettings { - apiKey: string; - webhookUrl: string; - taskId: string; -} - -const PlanApi: React.FC = ({ - visible, - onClose, - apiKey, - webhookUrl, - taskId, -}) => { - const [activeTab, setActiveTab] = useState("config"); - const [activeLanguage, setActiveLanguage] = useState("javascript"); - - // 处理webhook URL,确保包含完整的API地址 - const fullWebhookUrl = useMemo(() => { - return buildApiUrl(webhookUrl); - }, [webhookUrl]); - - // 生成测试URL - const testUrl = useMemo(() => { - if (!fullWebhookUrl) return ""; - return `${fullWebhookUrl}?name=测试客户&phone=13800138000&source=API测试`; - }, [fullWebhookUrl]); - - // 检测是否为移动端 - const isMobile = window.innerWidth <= 768; - - const handleCopy = (text: string, type: string) => { - navigator.clipboard.writeText(text); - Toast.show({ - content: `${type}已复制到剪贴板`, - position: "top", - }); - }; - - const handleTestInBrowser = () => { - window.open(testUrl, "_blank"); - }; - - const renderConfigTab = () => ( -
- {/* API密钥配置 */} -
-
-
- - API密钥 -
- 安全认证 -
-
- - -
-
- 安全提示: - 请妥善保管API密钥,不要在客户端代码中暴露。建议在服务器端使用该密钥。 -
-
- - {/* 接口地址配置 */} -
-
-
- - 接口地址 -
- POST请求 -
-
- - -
- - {/* 参数说明 */} -
-
-

必要参数

-
-
- name - 客户姓名 -
-
- phone - 手机号码 -
-
-
-
-

可选参数

-
-
- source - 来源标识 -
-
- remark - 备注信息 -
-
- tags - 客户标签 -
-
-
-
-
-
- ); - - const renderQuickTestTab = () => ( -
-
-

快速测试URL

-
- -
-
- - -
-
-
- ); - - const renderDocsTab = () => ( -
-
- -
- -
-

完整API文档

-

详细的接口说明和参数文档

-
- -
- -
-

集成指南

-

第三方平台集成教程

-
-
-
- ); - - const renderCodeTab = () => { - const codeExamples = { - javascript: `fetch('${fullWebhookUrl}', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer ${apiKey}' - }, - body: JSON.stringify({ - name: '张三', - phone: '13800138000', - source: '官网表单', - }) -})`, - python: `import requests - -url = '${fullWebhookUrl}' -headers = { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer ${apiKey}' -} -data = { - 'name': '张三', - 'phone': '13800138000', - 'source': '官网表单' -} - -response = requests.post(url, json=data, headers=headers)`, - php: ` '张三', - 'phone' => '13800138000', - 'source' => '官网表单' -); - -$options = array( - 'http' => array( - 'header' => "Content-type: application/json\\r\\nAuthorization: Bearer ${apiKey}\\r\\n", - 'method' => 'POST', - 'content' => json_encode($data) - ) -); - -$context = stream_context_create($options); -$result = file_get_contents($url, false, $context);`, - java: `import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.net.URI; - -HttpClient client = HttpClient.newHttpClient(); -String json = "{\\"name\\":\\"张三\\",\\"phone\\":\\"13800138000\\",\\"source\\":\\"官网表单\\"}"; - -HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("${fullWebhookUrl}")) - .header("Content-Type", "application/json") - .header("Authorization", "Bearer ${apiKey}") - .POST(HttpRequest.BodyPublishers.ofString(json)) - .build(); - -HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());`, - }; - - return ( -
-
- {Object.keys(codeExamples).map(lang => ( - - ))} -
-
-
-            
-              {codeExamples[activeLanguage as keyof typeof codeExamples]}
-            
-          
- -
-
- ); - }; - - const renderContent = () => ( -
- {/* 头部 */} -
-
- -
-

计划接口配置

-

- 通过API接口直接导入客资到该获客计划,支持多种编程语言和第三方平台集成 -

-
-
- -
- - {/* 导航标签 */} -
- - - - -
- - {/* 内容区域 */} -
- {activeTab === "config" && renderConfigTab()} - {activeTab === "test" && renderQuickTestTab()} - {activeTab === "docs" && renderDocsTab()} - {activeTab === "code" && renderCodeTab()} -
- - {/* 底部 */} -
-
- - 所有数据传输均采用HTTPS加密 -
- -
-
- ); - - // 移动端使用Popup - if (isMobile) { - return ( - - {renderContent()} - - ); - } - - // PC端使用Modal - return ( - - {renderContent()} - - ); -}; - -export default PlanApi; diff --git a/Touchkebao/src/pages/mobile/scenarios/plan/new/index.api.ts b/Touchkebao/src/pages/mobile/scenarios/plan/new/index.api.ts deleted file mode 100644 index ac1df3d8..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/plan/new/index.api.ts +++ /dev/null @@ -1,20 +0,0 @@ -import request from "@/api/request"; -// 获取场景类型列表 -export function getScenarioTypes() { - return request("/v1/plan/scenes", undefined, "GET"); -} - -// 创建计划 -export function createPlan(data: any) { - return request("/v1/plan/create", data, "POST"); -} - -// 更新计划 -export function updatePlan(data: any) { - return request("/v1/plan/update", data, "PUT"); -} - -// 获取计划详情 -export function getPlanDetail(planId: string) { - return request(`/v1/plan/detail?planId=${planId}`, undefined, "GET"); -} diff --git a/Touchkebao/src/pages/mobile/scenarios/plan/new/index.data.ts b/Touchkebao/src/pages/mobile/scenarios/plan/new/index.data.ts deleted file mode 100644 index 3cd21563..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/plan/new/index.data.ts +++ /dev/null @@ -1,59 +0,0 @@ -// 步骤定义 - 只保留三个步骤 -import { DeviceSelectionItem } from "@/components/DeviceSelection/data"; -import { GroupSelectionItem } from "@/components/GroupSelection/data"; -export const steps = [ - { id: 1, title: "步骤一", subtitle: "基础设置" }, - { id: 2, title: "步骤二", subtitle: "好友申请设置" }, - { id: 3, title: "步骤三", subtitle: "消息设置" }, -]; - -// 类型定义 -export interface FormData { - name: string; - scenario: number; - status: number; - sceneId: string | number; - remarkType: string; - greeting: string; - addInterval: number; - startTime: string; - endTime: string; - enabled: boolean; - remarkFormat: string; - addFriendInterval: number; - posters: any[]; // 后续可替换为具体Poster类型 - device: string[]; - customTags: string[]; - customTagsOptions: string[]; - deviceGroups: string[]; - deviceGroupsOptions: DeviceSelectionItem[]; - wechatGroups: string[]; - wechatGroupsOptions: GroupSelectionItem[]; - messagePlans: any[]; - [key: string]: any; -} -export const defFormData: FormData = { - name: "", - scenario: 1, - status: 0, - sceneId: "", - remarkType: "phone", - greeting: "你好,请通过", - addInterval: 1, - startTime: "09:00", - endTime: "18:00", - enabled: true, - remarkFormat: "", - addFriendInterval: 1, - posters: [], - device: [], - customTags: [], - customTagsOptions: [], - messagePlans: [], - deviceGroups: [], - deviceGroupsOptions: [], - wechatGroups: [], - wechatGroupsOptions: [], - contentGroups: [], - contentGroupsOptions: [], -}; diff --git a/Touchkebao/src/pages/mobile/scenarios/plan/new/index.tsx b/Touchkebao/src/pages/mobile/scenarios/plan/new/index.tsx deleted file mode 100644 index 949574a1..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/plan/new/index.tsx +++ /dev/null @@ -1,218 +0,0 @@ -import { useState, useEffect } from "react"; -import { useNavigate, useParams } from "react-router-dom"; -import { message, Button, Space } from "antd"; -import NavCommon from "@/components/NavCommon"; -import BasicSettings from "./steps/BasicSettings"; -import FriendRequestSettings from "./steps/FriendRequestSettings"; -import MessageSettings from "./steps/MessageSettings"; -import Layout from "@/components/Layout/Layout"; -import StepIndicator from "@/components/StepIndicator"; - -import { - getScenarioTypes, - createPlan, - getPlanDetail, - updatePlan, -} from "./index.api"; -import { FormData, defFormData, steps } from "./index.data"; - -export default function NewPlan() { - const router = useNavigate(); - const [currentStep, setCurrentStep] = useState(1); - const [formData, setFormData] = useState(defFormData); - - const [sceneList, setSceneList] = useState([]); - const [sceneLoading, setSceneLoading] = useState(true); - const { scenarioId, planId } = useParams<{ - scenarioId: string; - planId: string; - }>(); - const [isEdit, setIsEdit] = useState(false); - useEffect(() => { - loadData(); - }, []); - - const loadData = async () => { - setSceneLoading(true); - //获取场景类型 - getScenarioTypes() - .then(data => { - setSceneList(data || []); - }) - .catch(err => { - message.error(err.message || "获取场景类型失败"); - }) - .finally(() => setSceneLoading(false)); - if (planId) { - setIsEdit(true); - //获取计划详情 - - const detail = await getPlanDetail(planId); - setFormData(prev => ({ - ...prev, - name: detail.name ?? "", - scenario: Number(detail.scenario) || 1, - scenarioTags: detail.scenarioTags ?? [], - customTags: detail.customTags ?? [], - customTagsOptions: detail.customTags ?? [], - posters: detail.posters ?? [], - device: detail.device ?? [], - remarkType: detail.remarkType ?? "phone", - greeting: detail.greeting ?? "", - addInterval: detail.addInterval ?? 1, - startTime: detail.startTime ?? "09:00", - endTime: detail.endTime ?? "18:00", - enabled: detail.enabled ?? true, - sceneId: Number(detail.scenario) || 1, - remarkFormat: detail.remarkFormat ?? "", - addFriendInterval: detail.addFriendInterval ?? 1, - tips: detail.tips ?? "", - deviceGroups: detail.deviceGroups ?? [], - deviceGroupsOptions: detail.deviceGroupsOptions ?? [], - wechatGroups: detail.wechatGroups ?? [], - wechatGroupsOptions: detail.wechatGroupsOptions ?? [], - contentGroups: detail.contentGroups ?? [], - contentGroupsOptions: detail.contentGroupsOptions ?? [], - status: detail.status ?? 0, - messagePlans: detail.messagePlans ?? [], - })); - } else { - if (scenarioId) { - setFormData(prev => ({ - ...prev, - ...{ scenario: Number(scenarioId) || 1 }, - })); - } - } - }; - - // 更新表单数据 - const onChange = (data: any) => { - setFormData(prev => ({ ...prev, ...data })); - }; - - // 处理保存 - const handleSave2 = async () => { - if (isEdit && planId) { - // 编辑:拼接后端需要的完整参数 - const editData = { - ...formData, - ...{ sceneId: Number(formData.scenario) }, - id: Number(planId), - planId: Number(planId), - }; - console.log("editData", editData); - } else { - // 新建 - formData.sceneId = Number(formData.scenario); - console.log("formData", formData); - } - }; - // 处理保存 - const handleSave = async () => { - try { - if (isEdit && planId) { - // 编辑:拼接后端需要的完整参数 - const editData = { - ...formData, - ...{ sceneId: Number(formData.scenario) }, - id: Number(planId), - planId: Number(planId), - // 兼容后端需要的字段 - // 你可以根据实际需要补充其它字段 - }; - await updatePlan(editData); - } else { - // 新建 - formData.sceneId = Number(formData.scenario); - await createPlan(formData); - } - message.success(isEdit ? "计划已更新" : "获客计划已创建"); - const sceneItem = sceneList.find(v => formData.scenario === v.id); - router(`/scenarios/list/${formData.scenario}/${sceneItem.name}`); - } catch (error) { - message.error( - error instanceof Error - ? error.message - : typeof error === "string" - ? error - : isEdit - ? "更新计划失败,请重试" - : "创建计划失败,请重试", - ); - } - }; - - // 下一步 - const handleNext = () => { - if (currentStep === steps.length) { - handleSave(); - } else { - setCurrentStep(prev => prev + 1); - } - }; - - // 上一步 - const handlePrev = () => { - setCurrentStep(prev => Math.max(prev - 1, 1)); - }; - - // 渲染当前步骤内容 - const renderStepContent = () => { - switch (currentStep) { - case 1: - return ( - - ); - case 2: - return ( - - ); - case 3: - return ; - default: - return null; - } - }; - - // 渲染底部按钮 - const renderFooterButtons = () => { - return ( -
- {currentStep > 1 && ( - - )} - -
- ); - }; - - return ( - - - - - } - footer={renderFooterButtons()} - > - {renderStepContent()} - - ); -} diff --git a/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/BasicSettings.tsx b/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/BasicSettings.tsx deleted file mode 100644 index 7590f6fa..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/BasicSettings.tsx +++ /dev/null @@ -1,564 +0,0 @@ -import React, { useState, useEffect, useRef } from "react"; -import { Input, Button, Tag, Switch, Modal, Spin } from "antd"; -import { - PlusOutlined, - EyeOutlined, - CloseOutlined, - DownloadOutlined, -} from "@ant-design/icons"; -import { uploadFile } from "@/api/common"; -import styles from "./base.module.scss"; -import { posterTemplates } from "./base.data"; -import GroupSelection from "@/components/GroupSelection"; -import FileUpload from "@/components/Upload/FileUpload"; -import { GroupSelectionItem } from "@/components/GroupSelection/data"; - -interface BasicSettingsProps { - isEdit: boolean; - formData: any; - onChange: (data: any) => void; - sceneList: any[]; - sceneLoading: boolean; -} - -interface Material { - id: string; - name: string; - type: string; - url: string; -} - -const generatePosterMaterials = (): Material[] => { - return posterTemplates.map(template => ({ - id: template.id, - name: template.name, - type: "poster", - url: template.url, - })); -}; - -const BasicSettings: React.FC = ({ - formData, - onChange, - sceneList, - sceneLoading, -}) => { - const [isPreviewOpen, setIsPreviewOpen] = useState(false); - const [materials] = useState(generatePosterMaterials()); - const [selectedMaterials, setSelectedMaterials] = useState( - formData.posters?.length > 0 ? formData.posters : [], - ); - - // 自定义标签相关状态 - const [customTagInput, setCustomTagInput] = useState(""); - const [customTagsOptions, setCustomTagsOptions] = useState( - formData.customTagsOptions || [], - ); - const [tips, setTips] = useState(formData.tips || ""); - const [selectedScenarioTags, setSelectedScenarioTags] = useState( - formData.scenarioTags || [], - ); - const [selectedCustomTags, setSelectedCustomTags] = useState( - formData.customTags || [], - ); - // 电话获客相关状态 - const [phoneSettings, setPhoneSettings] = useState({ - autoAdd: formData.phoneSettings?.autoAdd ?? true, - speechToText: formData.phoneSettings?.speechToText ?? true, - questionExtraction: formData.phoneSettings?.questionExtraction ?? true, - }); - - // 新增:自定义海报相关状态 - const [customPosters, setCustomPosters] = useState([]); - const [previewUrl, setPreviewUrl] = useState(null); - - // 新增:用于文件选择的ref - const uploadInputRef = useRef(null); - - // 初始化时,如果没有选择场景,默认选择海报获客 - useEffect(() => { - if (!formData.scenario) { - onChange({ ...formData, scenario: "haibao" }); - } - }, [formData, onChange]); - - // 监听 formData 变化,同步自定义标签和获客标签状态 - useEffect(() => { - setCustomTagsOptions(formData.customTagsOptions || []); - setSelectedCustomTags(formData.customTags || []); - }, [formData.customTagsOptions, formData.customTags]); - - // 监听获客标签变化 - useEffect(() => { - setSelectedScenarioTags(formData.scenarioTags || []); - }, [formData.scenarioTags]); - - useEffect(() => { - setTips(formData.tips || ""); - }, [formData.tips]); - - // 选中场景 - const handleScenarioSelect = (sceneId: number) => { - onChange({ ...formData, scenario: sceneId }); - }; - - // 选中/取消标签 - const handleScenarioTagToggle = (tag: string) => { - const newTags = selectedScenarioTags.includes(tag) - ? selectedScenarioTags.filter((t: string) => t !== tag) - : [...selectedScenarioTags, tag]; - setSelectedScenarioTags(newTags); - onChange({ ...formData, scenarioTags: newTags }); - }; - - const handleCustomTagToggle = (tag: string) => { - const newTags = selectedCustomTags.includes(tag) - ? selectedCustomTags.filter((t: string) => t !== tag) - : [...selectedCustomTags, tag]; - setSelectedCustomTags(newTags); - onChange({ ...formData, customTags: newTags }); - }; - // 添加自定义标签 - const handleAddCustomTag = () => { - if (!customTagInput.trim()) return; - const newTag = customTagInput.trim(); - // 已存在则忽略 - if (customTagsOptions.includes(newTag)) { - // 若未选中则顺便选中 - const maybeSelected = selectedCustomTags.includes(newTag) - ? selectedCustomTags - : [...selectedCustomTags, newTag]; - setSelectedCustomTags(maybeSelected); - onChange({ ...formData, customTags: maybeSelected }); - setCustomTagInput(""); - return; - } - - const updatedOptions = [...customTagsOptions, newTag]; - const updatedSelected = [...selectedCustomTags, newTag]; - setCustomTagsOptions(updatedOptions); - setSelectedCustomTags(updatedSelected); - setCustomTagInput(""); - onChange({ - ...formData, - customTagsOptions: updatedOptions, - customTags: updatedSelected, - }); - }; - - // 删除自定义标签 - const handleRemoveCustomTag = (tagName: string) => { - const updatedOptions = customTagsOptions.filter( - (tag: string) => tag !== tagName, - ); - setCustomTagsOptions(updatedOptions); - - // 同时从选中的自定义标签中移除 - const updatedSelectedCustom = selectedCustomTags.filter( - (t: string) => t !== tagName, - ); - setSelectedCustomTags(updatedSelectedCustom); - - onChange({ - ...formData, - customTagsOptions: updatedOptions, - customTags: updatedSelectedCustom, - }); - }; - - // 新增:删除自定义海报 - const handleRemoveCustomPoster = (id: string) => { - setCustomPosters(prev => prev.filter(p => p.id !== id)); - // 如果选中则取消选中 - if (selectedMaterials.some(m => m.id === id)) { - setSelectedMaterials([]); - onChange({ ...formData, posters: [] }); - } - }; - - // 修改:选中/取消选中海报 - const handleMaterialSelect = (material: Material) => { - const isSelected = selectedMaterials.some(m => m.id === material.id); - if (isSelected) { - setSelectedMaterials([]); - onChange({ ...formData, posters: [] }); - } else { - setSelectedMaterials([material]); - onChange({ ...formData, posters: [material] }); - } - }; - - // 新增:全屏预览 - const handlePreviewImage = (url: string) => { - setPreviewUrl(url); - setIsPreviewOpen(true); - }; - - // 下载模板 - const handleDownloadTemplate = () => { - const template = - "电话号码,微信号,来源,订单金额,下单日期\n13800138000,wxid_123,抖音,99.00,2024-03-03"; - const blob = new Blob([template], { type: "text/csv" }); - const url = window.URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = "订单导入模板.csv"; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - window.URL.revokeObjectURL(url); - }; - - // 当前选中的场景对象 - const currentScene = sceneList.find(s => s.id === formData.scenario); - //打开订单 - const openOrder = - formData.scenario !== 2 ? { display: "none" } : { display: "block" }; - - const openPoster = - formData.scenario !== 1 ? { display: "none" } : { display: "block" }; - - const handleWechatGroupSelect = (groups: GroupSelectionItem[]) => { - onChange({ - ...formData, - wechatGroups: groups.map(v => v.id), - wechatGroupsOptions: groups, - }); - }; - return ( -
- {/* 场景选择区块 */} -
- {sceneLoading ? ( -
- -
- ) : ( -
- {sceneList.map(scene => { - const selected = formData.scenario === scene.id; - return ( - - ); - })} -
- )} -
- {/* 计划名称输入区 */} -
计划名称
-
- - onChange({ ...formData, name: String(e.target.value) }) - } - placeholder="请输入计划名称" - /> -
-
获客标签(可多选)
- {/* 标签选择区块 */} - {formData.scenario && ( -
- {(currentScene?.scenarioTags || []).map((tag: string) => ( - handleScenarioTagToggle(tag)} - className={styles["basic-tag-item"]} - > - {tag} - - ))} - {/* 自定义标签 */} - {customTagsOptions.map((tag: string) => ( - handleCustomTagToggle(tag)} - closable - onClose={() => handleRemoveCustomTag(tag)} - className={styles["basic-tag-item"]} - > - {tag} - - ))} -
- )} - {/* 自定义标签输入区 */} -
- setCustomTagInput(e.target.value)} - onPressEnter={handleAddCustomTag} - placeholder="添加自定义标签" - /> - -
- {/* 输入获客成功提示 - 只有海报场景才显示 */} - {formData.scenario === 1 && ( - <> -
请输入获客成功提示
-
- { - setTips(e.target.value); - onChange({ ...formData, tips: e.target.value }); - }} - placeholder="请输入获客成功提示" - /> -
- - )} - {/* 选素材 */} -
-
选择海报
-
- {[...materials, ...customPosters].map(material => { - const isSelected = selectedMaterials.some( - m => m.id === material.id, - ); - const isCustom = material.id.startsWith("custom-"); - return ( -
handleMaterialSelect(material)} - > - {/* 预览按钮:自定义海报在左上,内置海报在右上 */} - { - e.stopPropagation(); - handlePreviewImage(material.url); - }} - > - - - {/* 删除自定义海报按钮 */} - {isCustom && ( - - )} - {material.name} -
- {material.name} -
-
- ); - })} - {/* 添加海报卡片 */} -
uploadInputRef.current?.click()} - > - - - - 添加海报 - { - const file = e.target.files?.[0]; - if (file) { - // 直接上传 - try { - const url = await uploadFile(file); - const newPoster = { - id: `custom-${Date.now()}`, - name: "自定义海报", - type: "poster", - url: url, - }; - setCustomPosters(prev => [...prev, newPoster]); - } catch (err) { - // 可加toast提示 - } - e.target.value = ""; - } - }} - /> -
-
- {/* 全屏图片预览 */} - { - setIsPreviewOpen(false); - setPreviewUrl(null); - }} - footer={null} - width={800} - > - {previewUrl && ( - Preview - )} - -
- {/* 群选择 - 只有微信群场景才显示 */} - {formData.scenario === 7 && ( -
-
选择群聊
- -
- )} - - {/* 订单导入区块 - 使用FileUpload组件 */} -
-
订单表格上传
-
- -
-
- onChange({ ...formData, orderFileUrl: url })} - acceptTypes={["excel"]} - maxCount={1} - maxSize={10} - showPreview={false} - /> -
-
- 支持 Excel 格式,上传后将文件保存到服务器 -
-
- {/* 电话获客设置区块,仅在选择电话获客场景时显示 */} - {formData.scenario === 5 && ( -
-
- 电话获客设置 -
-
-
- 自动加好友 - setPhoneSettings(s => ({ ...s, autoAdd: v }))} - /> -
-
- 语音转文字 - - setPhoneSettings(s => ({ ...s, speechToText: v })) - } - /> -
-
- 问题提取 - - setPhoneSettings(s => ({ ...s, questionExtraction: v })) - } - /> -
-
-
- )} - -
- 是否启用 - onChange({ ...formData, status: value ? 1 : 0 })} - /> -
-
- ); -}; - -export default BasicSettings; diff --git a/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/FriendRequestSettings.tsx b/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/FriendRequestSettings.tsx deleted file mode 100644 index db8f3648..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/FriendRequestSettings.tsx +++ /dev/null @@ -1,232 +0,0 @@ -"use client"; - -import React, { useState, useEffect } from "react"; -import { Input, Button, Modal, Alert, Select } from "antd"; -import { MessageOutlined } from "@ant-design/icons"; -import DeviceSelection from "@/components/DeviceSelection"; -import styles from "./friend.module.scss"; -import { DeviceSelectionItem } from "@/components/DeviceSelection/data"; - -interface FriendRequestSettingsProps { - formData: any; - onChange: (data: any) => void; -} - -// 招呼语模板 -const greetingTemplates = [ - "你好,请通过", - "你好,了解XX,请通过", - "你好,我是XX产品的客服请通过", - "你好,感谢关注我们的产品", - "你好,很高兴为您服务", -]; - -// 备注类型选项 -const remarkTypes = [ - { value: "phone", label: "手机号" }, - { value: "nickname", label: "昵称" }, - { value: "source", label: "来源" }, -]; - -const FriendRequestSettings: React.FC = ({ - formData, - onChange, -}) => { - const [isTemplateDialogOpen, setIsTemplateDialogOpen] = useState(false); - const [hasWarnings, setHasWarnings] = useState(false); - - const [showRemarkTip, setShowRemarkTip] = useState(false); - - // 获取场景标题 - const getScenarioTitle = () => { - switch (formData.scenario) { - case "douyin": - return "抖音直播"; - case "xiaohongshu": - return "小红书"; - case "weixinqun": - return "微信群"; - case "gongzhonghao": - return "公众号"; - default: - return formData.name || "获客计划"; - } - }; - - // 使用useEffect设置默认值 - useEffect(() => { - if (!formData.greeting) { - onChange({ - ...formData, - greeting: "你好,请通过", - remarkType: "phone", // 默认选择手机号 - remarkFormat: `手机号+${getScenarioTitle()}`, // 默认备注格式 - addFriendInterval: 1, - }); - } - }, [formData, formData.greeting, onChange]); - - // 检查是否有未完成的必填项 - useEffect(() => { - const hasIncompleteFields = !formData.greeting?.trim(); - setHasWarnings(hasIncompleteFields); - }, [formData]); - - const handleTemplateSelect = (template: string) => { - onChange({ ...formData, greeting: template }); - setIsTemplateDialogOpen(false); - }; - const handleDevicesChange = (deviceGroupsOptions: DeviceSelectionItem[]) => { - onChange({ - ...formData, - deviceGroups: deviceGroupsOptions.map(d => d.id), - deviceGroupsOptions: deviceGroupsOptions, - }); - }; - - return ( -
- {/* 选择设备区块 */} -
选择设备
-
- -
- - {/* 好友备注区块 */} -
好友备注
-
-
- - setShowRemarkTip(true)} - onMouseLeave={() => setShowRemarkTip(false)} - > - ? - - {showRemarkTip && ( -
-
设置添加好友时的备注格式
-
- 备注格式预览: -
-
- {formData.remarkType === "phone" && - `138****1234+${getScenarioTitle()}`} - {formData.remarkType === "nickname" && - `小红书用户2851+${getScenarioTitle()}`} - {formData.remarkType === "source" && - `抖音直播+${getScenarioTitle()}`} -
-
- )} -
-
- - {/* 招呼语区块 */} -
招呼语
-
- onChange({ ...formData, greeting: e.target.value })} - placeholder="请输入招呼语" - suffix={ - - } - /> -
- - {/* 添加间隔区块 */} -
添加间隔
-
- - onChange({ - ...formData, - addFriendInterval: Number(e.target.value), - }) - } - style={{ width: 100 }} - /> - 分钟 -
- - {/* 允许加人时间段区块 */} -
允许加人的时间段
-
- - onChange({ ...formData, addFriendTimeStart: e.target.value }) - } - style={{ width: 120 }} - /> - - - onChange({ ...formData, addFriendTimeEnd: e.target.value }) - } - style={{ width: 120 }} - /> -
- - {hasWarnings && ( - - )} - - {/* 招呼语模板弹窗 */} - setIsTemplateDialogOpen(false)} - footer={null} - > -
- {greetingTemplates.map((template, index) => ( - - ))} -
-
-
- ); -}; - -export default FriendRequestSettings; diff --git a/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/MessageCard.tsx b/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/MessageCard.tsx deleted file mode 100644 index 7202fc91..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/MessageCard.tsx +++ /dev/null @@ -1,336 +0,0 @@ -import React from "react"; -import { Input, Button } from "antd"; -import { CloseOutlined, ClockCircleOutlined } from "@ant-design/icons"; -import styles from "./messages.module.scss"; -// 导入Upload组件 -import ImageUpload from "@/components/Upload/ImageUpload/ImageUpload"; -import VideoUpload from "@/components/Upload/VideoUpload"; -import FileUpload from "@/components/Upload/FileUpload"; -import MainImgUpload from "@/components/Upload/MainImgUpload"; -// 导入GroupSelection组件 -import GroupSelection from "@/components/GroupSelection"; -import { GroupSelectionItem } from "@/components/GroupSelection/data"; -import { MessageContentItem, messageTypes } from "./base.data"; - -interface MessageCardProps { - message: MessageContentItem; - dayIndex: number; - messageIndex: number; - planDay: number; - onUpdateMessage: ( - dayIndex: number, - messageIndex: number, - updates: Partial, - ) => void; - onRemoveMessage: (dayIndex: number, messageIndex: number) => void; - onToggleIntervalUnit: (dayIndex: number, messageIndex: number) => void; -} - -const MessageCard: React.FC = ({ - message, - dayIndex, - messageIndex, - planDay, - onUpdateMessage, - onRemoveMessage, - onToggleIntervalUnit, -}) => { - return ( -
-
- {/* 时间/间隔设置 */} -
-
- {planDay === 0 ? ( - <> - 间隔 - - onUpdateMessage(dayIndex, messageIndex, { - sendInterval: Number(e.target.value), - }) - } - style={{ width: 60 }} - /> - - - ) : ( - <> - 发送时间 - - onUpdateMessage(dayIndex, messageIndex, { - scheduledTime: { - ...(message.scheduledTime || { - hour: 9, - minute: 0, - second: 0, - }), - hour: Number(e.target.value), - }, - }) - } - style={{ width: 40 }} - /> - : - - onUpdateMessage(dayIndex, messageIndex, { - scheduledTime: { - ...(message.scheduledTime || { - hour: 9, - minute: 0, - second: 0, - }), - minute: Number(e.target.value), - }, - }) - } - style={{ width: 40 }} - /> - : - - onUpdateMessage(dayIndex, messageIndex, { - scheduledTime: { - ...(message.scheduledTime || { - hour: 9, - minute: 0, - second: 0, - }), - second: Number(e.target.value), - }, - }) - } - style={{ width: 40 }} - /> - - )} -
- -
- {/* 类型切换按钮 */} -
- {messageTypes.map(type => ( - - ))} -
-
-
- {/* 文本消息 */} - {message.type === "text" && ( - - onUpdateMessage(dayIndex, messageIndex, { - content: e.target.value, - }) - } - placeholder="请输入消息内容" - autoSize={{ minRows: 3, maxRows: 6 }} - /> - )} - {/* 小程序消息 */} - {message.type === "miniprogram" && ( - <> - - onUpdateMessage(dayIndex, messageIndex, { - title: e.target.value, - }) - } - placeholder="请输入小程序标题" - style={{ marginBottom: 8 }} - /> - - onUpdateMessage(dayIndex, messageIndex, { - description: e.target.value, - }) - } - placeholder="请输入小程序描述" - style={{ marginBottom: 8 }} - /> - - onUpdateMessage(dayIndex, messageIndex, { - address: e.target.value, - }) - } - placeholder="请输入小程序路径" - style={{ marginBottom: 8 }} - /> -
- - onUpdateMessage(dayIndex, messageIndex, { - content: url, - }) - } - maxSize={5} - showPreview={true} - /> -
- - )} - {/* 链接消息 */} - {message.type === "link" && ( - <> - - onUpdateMessage(dayIndex, messageIndex, { - title: e.target.value, - }) - } - placeholder="请输入链接标题" - style={{ marginBottom: 8 }} - /> - - onUpdateMessage(dayIndex, messageIndex, { - description: e.target.value, - }) - } - placeholder="请输入链接描述" - style={{ marginBottom: 8 }} - /> - - onUpdateMessage(dayIndex, messageIndex, { - linkUrl: e.target.value, - }) - } - placeholder="请输入链接地址" - style={{ marginBottom: 8 }} - /> -
- - onUpdateMessage(dayIndex, messageIndex, { - coverImage: url, - }) - } - maxSize={1} - showPreview={true} - /> -
- - )} - {/* 群邀请消息 */} - {message.type === "group" && ( -
- { - onUpdateMessage(dayIndex, messageIndex, { - groupIds: groups.map(v => v.id), - groupOptions: groups, - }); - }} - placeholder="选择邀请入的群" - showSelectedList={true} - selectedListMaxHeight={200} - /> -
- )} - {/* 图片消息 */} - {message.type === "image" && ( -
- - onUpdateMessage(dayIndex, messageIndex, { - content: urls[0] || "", - }) - } - count={1} - accept="image/*" - /> -
- )} - {/* 视频消息 */} - {message.type === "video" && ( -
- { - const videoUrl = Array.isArray(url) ? url[0] || "" : url; - onUpdateMessage(dayIndex, messageIndex, { - content: videoUrl, - }); - }} - maxSize={50} - maxCount={1} - showPreview={true} - /> -
- )} - {/* 文件消息 */} - {message.type === "file" && ( -
- { - const fileUrl = Array.isArray(url) ? url[0] || "" : url; - onUpdateMessage(dayIndex, messageIndex, { - content: fileUrl, - }); - }} - maxSize={10} - maxCount={1} - showPreview={true} - acceptTypes={["excel", "word", "ppt"]} - /> -
- )} -
-
- ); -}; - -export default MessageCard; diff --git a/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/MessageSettings.tsx b/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/MessageSettings.tsx deleted file mode 100644 index 23116882..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/MessageSettings.tsx +++ /dev/null @@ -1,242 +0,0 @@ -import React, { useState } from "react"; -import { Button, Tabs, Modal, message } from "antd"; -import { PlusOutlined, CloseOutlined } from "@ant-design/icons"; -import styles from "./messages.module.scss"; -import { - MessageContentItem, - MessageContentGroup, - MessageSettingsProps, -} from "./base.data"; -import MessageCard from "./MessageCard"; - -const MessageSettings: React.FC = ({ - formData, - onChange, -}) => { - const [isAddDayPlanOpen, setIsAddDayPlanOpen] = useState(false); - - // 获取当前的消息计划,如果没有则使用默认值 - const getCurrentMessagePlans = (): MessageContentGroup[] => { - if (formData.messagePlans && formData.messagePlans.length > 0) { - return formData.messagePlans; - } - return [ - { - day: 0, - messages: [ - { - id: "1", - type: "text", - content: "", - sendInterval: 5, - intervalUnit: "seconds", - }, - ], - }, - ]; - }; - - // 添加新消息 - const handleAddMessage = (dayIndex: number, type = "text") => { - const currentPlans = getCurrentMessagePlans(); - const updatedPlans = [...currentPlans]; - const newMessage: MessageContentItem = { - id: Date.now().toString(), - type: type as MessageContentItem["type"], - content: "", - }; - - if (currentPlans[dayIndex].day === 0) { - newMessage.sendInterval = 5; - newMessage.intervalUnit = "seconds"; - } else { - newMessage.scheduledTime = { - hour: 9, - minute: 0, - second: 0, - }; - } - - updatedPlans[dayIndex].messages.push(newMessage); - onChange({ ...formData, messagePlans: updatedPlans }); - }; - - // 更新消息内容 - const handleUpdateMessage = ( - dayIndex: number, - messageIndex: number, - updates: Partial, - ) => { - const currentPlans = getCurrentMessagePlans(); - const updatedPlans = [...currentPlans]; - updatedPlans[dayIndex].messages[messageIndex] = { - ...updatedPlans[dayIndex].messages[messageIndex], - ...updates, - }; - onChange({ ...formData, messagePlans: updatedPlans }); - }; - - // 删除消息 - const handleRemoveMessage = (dayIndex: number, messageIndex: number) => { - const currentPlans = getCurrentMessagePlans(); - const updatedPlans = [...currentPlans]; - updatedPlans[dayIndex].messages.splice(messageIndex, 1); - onChange({ ...formData, messagePlans: updatedPlans }); - }; - - // 切换时间单位 - const toggleIntervalUnit = (dayIndex: number, messageIndex: number) => { - const currentPlans = getCurrentMessagePlans(); - const message = currentPlans[dayIndex].messages[messageIndex]; - const newUnit = message.intervalUnit === "minutes" ? "seconds" : "minutes"; - handleUpdateMessage(dayIndex, messageIndex, { intervalUnit: newUnit }); - }; - - // 添加新的天数计划 - const handleAddDayPlan = () => { - const currentPlans = getCurrentMessagePlans(); - const newDay = currentPlans.length; - const updatedPlans = [ - ...currentPlans, - { - day: newDay, - messages: [ - { - id: Date.now().toString(), - type: "text", - content: "", - scheduledTime: { - hour: 9, - minute: 0, - second: 0, - }, - }, - ], - }, - ]; - onChange({ ...formData, messagePlans: updatedPlans }); - setIsAddDayPlanOpen(false); - message.success(`已添加第${newDay}天的消息计划`); - }; - - // 删除天数计划 - const handleRemoveDayPlan = (dayIndex: number) => { - if (dayIndex === 0) { - message.warning("不能删除即时消息"); - return; - } - - const currentPlans = getCurrentMessagePlans(); - Modal.confirm({ - title: "确认删除", - content: `确定要删除第${currentPlans[dayIndex].day}天的消息计划吗?`, - onOk: () => { - const updatedPlans = currentPlans.filter( - (_, index) => index !== dayIndex, - ); - // 重新计算天数 - const recalculatedPlans = updatedPlans.map((plan, index) => ({ - ...plan, - day: index, - })); - onChange({ ...formData, messagePlans: recalculatedPlans }); - message.success(`已删除第${currentPlans[dayIndex].day}天的消息计划`); - }, - }); - }; - - const items = getCurrentMessagePlans().map( - (plan: MessageContentGroup, dayIndex: number) => ({ - key: plan.day.toString(), - label: ( -
- {plan.day === 0 ? "即时消息" : `第${plan.day}天`} - {dayIndex > 0 && ( -
- ), - children: ( -
- {plan.messages.map((message, messageIndex) => ( - - ))} - -
- ), - }), - ); - - return ( -
-
-

消息设置

- -
- - - {/* 添加天数计划弹窗 */} - setIsAddDayPlanOpen(false)} - onOk={() => { - handleAddDayPlan(); - setIsAddDayPlanOpen(false); - }} - > -

选择要添加的消息计划类型

- -
-
- ); -}; - -export default MessageSettings; diff --git a/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/base.data.ts b/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/base.data.ts deleted file mode 100644 index dd5e5a6f..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/base.data.ts +++ /dev/null @@ -1,84 +0,0 @@ -export const posterTemplates = [ - { - id: "poster-1", - name: "点击领取", - url: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/%E7%82%B9%E5%87%BB%E9%A2%86%E5%8F%961-tipd1HI7da6qooY5NkhxQnXBnT5LGU.gif", - }, - { - id: "poster-2", - name: "点击合作", - url: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/%E7%82%B9%E5%87%BB%E5%90%88%E4%BD%9C-LPlMdgxtvhqCSr4IM1bZFEFDBF3ztI.gif", - }, - { - id: "poster-3", - name: "点击咨询", - url: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/%E7%82%B9%E5%87%BB%E5%92%A8%E8%AF%A2-FTiyAMAPop2g9LvjLOLDz0VwPg3KVu.gif", - }, - { - id: "poster-4", - name: "点击签到", - url: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/%E7%82%B9%E5%87%BB%E7%AD%BE%E5%88%B0-94TZIkjLldb4P2jTVlI6MkSDg0NbXi.gif", - }, - { - id: "poster-5", - name: "点击了解", - url: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/%E7%82%B9%E5%87%BB%E4%BA%86%E8%A7%A3-6GCl7mQVdO4WIiykJyweSubLsTwj71.gif", - }, - { - id: "poster-6", - name: "点击报名", - url: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/%E7%82%B9%E5%87%BB%E6%8A%A5%E5%90%8D-Mj0nnva0BiASeDAIhNNaRRAbjPgjEj.gif", - }, -]; -// ======================================== -import { - MessageOutlined, - PictureOutlined, - VideoCameraOutlined, - FileOutlined, - AppstoreOutlined, - LinkOutlined, - TeamOutlined, -} from "@ant-design/icons"; - -export interface MessageContentItem { - id: string; - type: "text" | "image" | "video" | "file" | "miniprogram" | "link" | "group"; - content: string; - sendInterval?: number; - intervalUnit?: "seconds" | "minutes"; - scheduledTime?: { - hour: number; - minute: number; - second: number; - }; - title?: string; - description?: string; - address?: string; - groupIds?: string[]; // 改为数组以支持GroupSelection组件 - groupOptions?: any[]; // 添加群选项数组 - linkUrl?: string; - coverImage?: string; - [key: string]: any; -} - -export interface MessageContentGroup { - day: number; - messages: MessageContentItem[]; -} - -export interface MessageSettingsProps { - formData: any; - onChange: (data: any) => void; -} - -// 消息类型配置 -export const messageTypes = [ - { id: "text", icon: MessageOutlined, label: "文本" }, - { id: "image", icon: PictureOutlined, label: "图片" }, - { id: "video", icon: VideoCameraOutlined, label: "视频" }, - { id: "file", icon: FileOutlined, label: "文件" }, - { id: "miniprogram", icon: AppstoreOutlined, label: "小程序" }, - { id: "link", icon: LinkOutlined, label: "链接" }, - { id: "group", icon: TeamOutlined, label: "邀请入群" }, -]; diff --git a/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/base.module.scss b/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/base.module.scss deleted file mode 100644 index aac76ca4..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/base.module.scss +++ /dev/null @@ -1,163 +0,0 @@ -.basic-container { - padding: 12px; -} -.basic-scene-select { - background: #fff; - border-radius: 12px; - padding: 24px; - margin-bottom: 24px; -} -.basic-scene-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 20px; -} -.basic-scene-btn { - height: 40px; - border: none; - border-radius: 10px; - font-weight: 500; - font-size: 16px; - outline: none; - cursor: pointer; - background: rgba(#1677ff, 0.1); - color: #1677ff; - transition: all 0.2s; -} -.basic-scene-btn.selected { - background: #1677ff; - color: #fff; - box-shadow: 0 2px 8px rgba(22, 119, 255, 0.08); -} -.basic-label { - margin-bottom: 12px; - font-weight: 500; -} -.basic-input-block { - border: 1px solid #eee; - margin-bottom: 16px; -} -.basic-tag-list { - display: flex; - flex-wrap: wrap; - gap: 5px; - padding-bottom: 16px; -} -.basic-tag-item { - margin-bottom: 6px; -} -.basic-custom-tag-input { - display: flex; - gap: 8px; - margin-bottom: 16px; -} -.basic-success-tip { - display: flex; -} -.basic-materials { - margin: 16px 0; -} -.basic-materials-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 12px; -} - -.basic-material-preview { - position: absolute; - top: 8px; - padding-left: 2px; - right: 8px; - background: rgba(0, 0, 0, 0.5); - border-radius: 50%; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - z-index: 2; - cursor: pointer; -} -.basic-material-card { - border: 2px solid #eee; - border-radius: 8px; - padding: 6px; - cursor: pointer; - background: #fff; - text-align: center; - position: relative; - min-height: 192px; - transition: border 0.2s; -} -.basic-material-card.selected { - border: 2px solid #1890ff; - background: #e6f7ff; -} -.basic-material-img { - width: 100px; - height: 180px; - object-fit: cover; - border-radius: 4px; - margin-bottom: 0; - display: block; -} -.basic-material-name { - position: absolute; - left: 0; - bottom: 0; - width: 100%; - background: rgba(0, 0, 0, 0.5); - color: #fff; - font-size: 14px; - padding: 4px 0; - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; - text-align: center; - z-index: 3; -} -.basic-add-material { - border: 2px dashed #bbb; - border-radius: 8px; - padding: 6px; - cursor: pointer; - background: #fafbfc; - text-align: center; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 190px; -} -.basic-order-upload { - margin: 16px 0; -} -.basic-order-upload-label { - font-weight: 500; - margin-bottom: 8px; -} -.basic-order-upload-actions { - display: flex; - gap: 12px; - margin-bottom: 4px; -} -.basic-order-upload-tip { - color: #888; - font-size: 13px; - margin-bottom: 8px; -} -.basic-phone-settings { - margin: 16px 0; - background: #f7f8fa; - border-radius: 10px; - padding: 20px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03); - margin-bottom: 12px; -} -.basic-wechat-group { - margin: 16px 0; -} -.basic-footer-switch { - display: flex; - align-items: center; - justify-content: space-between; - margin: 16px 0; -} diff --git a/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/friend.module.scss b/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/friend.module.scss deleted file mode 100644 index 12cd8977..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/friend.module.scss +++ /dev/null @@ -1,97 +0,0 @@ -.friend-container { - padding: 12px; -} -.friend-label { - margin-bottom: 12px; - font-weight: 500; -} -.friend-block { - margin-bottom: 16px; -} -.friend-remark-tip { - position: absolute; - right: 0; - top: 100%; - z-index: 10; - background: #fff; - border: 1px solid #e8e8e8; - border-radius: 8px; - padding: 12px; - width: 240px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - margin-top: 8px; - font-size: 13px; - line-height: 1.5; - - &::before { - content: ""; - position: absolute; - top: -6px; - right: 20px; - width: 0; - height: 0; - border-left: 6px solid transparent; - border-right: 6px solid transparent; - border-bottom: 6px solid #fff; - } - - &::after { - content: ""; - position: absolute; - top: -7px; - right: 20px; - width: 0; - height: 0; - border-left: 6px solid transparent; - border-right: 6px solid transparent; - border-bottom: 6px solid #e8e8e8; - } -} -.friend-remark-q { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - cursor: pointer; - color: #888; - background: #fff; - width: 18px; - height: 18px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-size: 12px; - font-weight: bold; - z-index: 2; - border: 1px solid #d9d9d9; - transition: all 0.2s ease; - - &:hover { - color: #1677ff; - border-color: #1677ff; - background: #f0f8ff; - } -} -.friend-remark-container { - position: relative; -} -.friend-interval-row { - display: flex; - align-items: center; - gap: 8px; -} -.friend-time-row { - display: flex; - align-items: center; - gap: 8px; -} -.friend-footer { - display: flex; - justify-content: space-between; - margin-top: 32px; -} -.friend-modal-btn { - width: 100%; - margin-bottom: 8px; -} diff --git a/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/messages.module.scss b/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/messages.module.scss deleted file mode 100644 index a6d047bd..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/messages.module.scss +++ /dev/null @@ -1,110 +0,0 @@ -.messages-container { - padding: 16px; -} -.messages-header { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 16px; -} -.messages-title { - font-size: 18px; - font-weight: 600; -} -.messages-tab { - margin-bottom: 16px; -} -.messages-day-panel { - border-radius: 10px; - margin-bottom: 16px; -} -.messages-message-card { - background: #fff; - border-radius: 12px; - box-shadow: - 0 4px 16px rgba(22, 119, 255, 0.06), - 0 1.5px 4px rgba(0, 0, 0, 0.04); - padding: 20px 12px 16px 12px; - margin-bottom: 20px; - border: 1.5px solid #f0f3fa; - transition: - box-shadow 0.2s, - border 0.2s, - transform 0.2s; - position: relative; -} -.messages-message-card:hover { - box-shadow: - 0 8px 24px rgba(22, 119, 255, 0.12), - 0 2px 8px rgba(0, 0, 0, 0.08); - border: 1.5px solid #1677ff; - transform: translateY(-2px) scale(1.01); -} -.messages-message-header { -} -.messages-message-header-content { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 8px; -} -.messages-message-type-btns { - display: flex; - gap: 5px; - margin-bottom: 8px; -} -.messages-message-type-btn { - width: 20px; -} -.messages-message-content { - margin-bottom: 10px; - font-size: 15px; - color: #222; - line-height: 1.7; -} -.messages-message-actions { - display: flex; - align-items: center; - gap: 12px; - margin-top: 8px; -} -.messages-message-remove-btn { - color: #ff4d4f; - background: none; - border: none; - cursor: pointer; - font-size: 16px; - padding: 0 8px; - transition: color 0.2s; -} -.messages-message-remove-btn:hover { - color: #d9363e; -} -.messages-add-message-btn { - width: 100%; - margin-top: 8px; -} -.messages-footer { - display: flex; - justify-content: space-between; - margin-top: 32px; -} -.messages-modal-btn { - width: 100%; - margin-bottom: 8px; -} -.messages-group-select-item { - padding: 16px; - border-radius: 8px; - cursor: pointer; - background: #fff; - margin-bottom: 8px; - border: 1px solid #eee; - transition: - border 0.2s, - background 0.2s; -} -.messages-group-select-item.selected { - background: #e6f7ff; - border: 1.5px solid #1677ff; -} diff --git a/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/step.api.ts b/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/step.api.ts deleted file mode 100644 index 8c68b53f..00000000 --- a/Touchkebao/src/pages/mobile/scenarios/plan/new/steps/step.api.ts +++ /dev/null @@ -1,384 +0,0 @@ -import request from "@/api/request"; - -// ==================== 场景相关接口 ==================== - -// 获取场景列表 -export function getScenarios(params: any) { - return request("/v1/plan/scenes", params, "GET"); -} - -// 获取场景详情 -export function getScenarioDetail(id: string) { - return request(`/v1/scenarios/${id}`, {}, "GET"); -} - -// 创建场景 -export function createScenario(data: any) { - return request("/v1/scenarios", data, "POST"); -} - -// 更新场景 -export function updateScenario(id: string, data: any) { - return request(`/v1/scenarios/${id}`, data, "PUT"); -} - -// 删除场景 -export function deleteScenario(id: string) { - return request(`/v1/scenarios/${id}`, {}, "DELETE"); -} - -// ==================== 计划相关接口 ==================== - -// 获取计划列表 -export function getPlanList( - scenarioId: string, - page: number = 1, - limit: number = 20, -) { - return request(`/api/scenarios/${scenarioId}/plans`, { page, limit }, "GET"); -} -// 复制计划 -export function copyPlan(planId: string) { - return request(`/api/scenarios/plans/${planId}/copy`, undefined, "POST"); -} - -// 删除计划 -export function deletePlan(planId: string) { - return request(`/api/scenarios/plans/${planId}`, undefined, "DELETE"); -} - -// 获取小程序二维码 -export function getWxMinAppCode(planId: string) { - return request(`/api/scenarios/plans/${planId}/qrcode`, undefined, "GET"); -} - -// ==================== 设备相关接口 ==================== - -// 获取设备列表 -export function getDevices() { - return request("/api/devices", undefined, "GET"); -} - -// 获取设备详情 -export function getDeviceDetail(deviceId: string) { - return request(`/api/devices/${deviceId}`, undefined, "GET"); -} - -// 创建设备 -export function createDevice(data: any) { - return request("/api/devices", data, "POST"); -} - -// 更新设备 -export function updateDevice(deviceId: string, data: any) { - return request(`/api/devices/${deviceId}`, data, "PUT"); -} - -// 删除设备 -export function deleteDevice(deviceId: string) { - return request(`/api/devices/${deviceId}`, undefined, "DELETE"); -} - -// ==================== 微信号相关接口 ==================== - -// 获取微信号列表 -export function getWechatAccounts() { - return request("/api/wechat-accounts", undefined, "GET"); -} - -// 获取微信号详情 -export function getWechatAccountDetail(accountId: string) { - return request(`/api/wechat-accounts/${accountId}`, undefined, "GET"); -} - -// 创建微信号 -export function createWechatAccount(data: any) { - return request("/api/wechat-accounts", data, "POST"); -} - -// 更新微信号 -export function updateWechatAccount(accountId: string, data: any) { - return request(`/api/wechat-accounts/${accountId}`, data, "PUT"); -} - -// 删除微信号 -export function deleteWechatAccount(accountId: string) { - return request(`/api/wechat-accounts/${accountId}`, undefined, "DELETE"); -} - -// ==================== 海报相关接口 ==================== - -// 获取海报列表 -export function getPosters() { - return request("/api/posters", undefined, "GET"); -} - -// 获取海报详情 -export function getPosterDetail(posterId: string) { - return request(`/api/posters/${posterId}`, undefined, "GET"); -} - -// 创建海报 -export function createPoster(data: any) { - return request("/api/posters", data, "POST"); -} - -// 更新海报 -export function updatePoster(posterId: string, data: any) { - return request(`/api/posters/${posterId}`, data, "PUT"); -} - -// 删除海报 -export function deletePoster(posterId: string) { - return request(`/api/posters/${posterId}`, undefined, "DELETE"); -} - -// ==================== 内容相关接口 ==================== - -// 获取内容列表 -export function getContents(params: any) { - return request("/api/contents", params, "GET"); -} - -// 获取内容详情 -export function getContentDetail(contentId: string) { - return request(`/api/contents/${contentId}`, undefined, "GET"); -} - -// 创建内容 -export function createContent(data: any) { - return request("/api/contents", data, "POST"); -} - -// 更新内容 -export function updateContent(contentId: string, data: any) { - return request(`/api/contents/${contentId}`, data, "PUT"); -} - -// 删除内容 -export function deleteContent(contentId: string) { - return request(`/api/contents/${contentId}`, undefined, "DELETE"); -} - -// ==================== 流量池相关接口 ==================== - -// 获取流量池列表 -export function getTrafficPools() { - return request("/api/traffic-pools", undefined, "GET"); -} - -// 获取流量池详情 -export function getTrafficPoolDetail(poolId: string) { - return request(`/api/traffic-pools/${poolId}`, undefined, "GET"); -} - -// 创建流量池 -export function createTrafficPool(data: any) { - return request("/api/traffic-pools", data, "POST"); -} - -// 更新流量池 -export function updateTrafficPool(poolId: string, data: any) { - return request(`/api/traffic-pools/${poolId}`, data, "PUT"); -} - -// 删除流量池 -export function deleteTrafficPool(poolId: string) { - return request(`/api/traffic-pools/${poolId}`, undefined, "DELETE"); -} - -// ==================== 工作台相关接口 ==================== - -// 获取工作台统计数据 -export function getWorkspaceStats() { - return request("/api/workspace/stats", undefined, "GET"); -} - -// 获取自动点赞任务列表 -export function getAutoLikeTasks() { - return request("/api/workspace/auto-like/tasks", undefined, "GET"); -} - -// 创建自动点赞任务 -export function createAutoLikeTask(data: any) { - return request("/api/workspace/auto-like/tasks", data, "POST"); -} - -// 更新自动点赞任务 -export function updateAutoLikeTask(taskId: string, data: any) { - return request(`/api/workspace/auto-like/tasks/${taskId}`, data, "PUT"); -} - -// 删除自动点赞任务 -export function deleteAutoLikeTask(taskId: string) { - return request( - `/api/workspace/auto-like/tasks/${taskId}`, - undefined, - "DELETE", - ); -} - -// ==================== 群发相关接口 ==================== - -// 获取群发任务列表 -export function getGroupPushTasks() { - return request("/api/workspace/group-push/tasks", undefined, "GET"); -} - -// 创建群发任务 -export function createGroupPushTask(data: any) { - return request("/api/workspace/group-push/tasks", data, "POST"); -} - -// 更新群发任务 -export function updateGroupPushTask(taskId: string, data: any) { - return request(`/api/workspace/group-push/tasks/${taskId}`, data, "PUT"); -} - -// 删除群发任务 -export function deleteGroupPushTask(taskId: string) { - return request( - `/api/workspace/group-push/tasks/${taskId}`, - undefined, - "DELETE", - ); -} - -// ==================== 自动建群相关接口 ==================== - -// 获取自动建群任务列表 -export function getAutoGroupTasks() { - return request("/api/workspace/auto-group/tasks", undefined, "GET"); -} - -// 创建自动建群任务 -export function createAutoGroupTask(data: any) { - return request("/api/workspace/auto-group/tasks", data, "POST"); -} - -// 更新自动建群任务 -export function updateAutoGroupTask(taskId: string, data: any) { - return request(`/api/workspace/auto-group/tasks/${taskId}`, data, "PUT"); -} - -// 删除自动建群任务 -export function deleteAutoGroupTask(taskId: string) { - return request( - `/api/workspace/auto-group/tasks/${taskId}`, - undefined, - "DELETE", - ); -} - -// ==================== AI助手相关接口 ==================== - -// 获取AI对话历史 -export function getAIChatHistory() { - return request("/api/workspace/ai-assistant/chat-history", undefined, "GET"); -} - -// 发送AI消息 -export function sendAIMessage(data: any) { - return request("/api/workspace/ai-assistant/send-message", data, "POST"); -} - -// 获取AI分析报告 -export function getAIAnalysisReport() { - return request( - "/api/workspace/ai-assistant/analysis-report", - undefined, - "GET", - ); -} - -// ==================== 订单相关接口 ==================== - -// 获取订单列表 -export function getOrders(params: any) { - return request("/api/orders", params, "GET"); -} - -// 获取订单详情 -export function getOrderDetail(orderId: string) { - return request(`/api/orders/${orderId}`, undefined, "GET"); -} - -// 创建订单 -export function createOrder(data: any) { - return request("/api/orders", data, "POST"); -} - -// 更新订单 -export function updateOrder(orderId: string, data: any) { - return request(`/api/orders/${orderId}`, data, "PUT"); -} - -// 删除订单 -export function deleteOrder(orderId: string) { - return request(`/api/orders/${orderId}`, undefined, "DELETE"); -} - -// ==================== 用户相关接口 ==================== - -// 获取用户信息 -export function getUserInfo() { - return request("/api/user/info", undefined, "GET"); -} - -// 更新用户信息 -export function updateUserInfo(data: any) { - return request("/api/user/info", data, "PUT"); -} - -// 修改密码 -export function changePassword(data: any) { - return request("/api/user/change-password", data, "POST"); -} - -// 上传头像 -export function uploadAvatar(data: any) { - return request("/api/user/upload-avatar", data, "POST"); -} - -// ==================== 文件上传相关接口 ==================== - -// 上传文件 -export function uploadFile(data: any) { - return request("/api/upload/file", data, "POST"); -} - -// 上传图片 -export function uploadImage(data: any) { - return request("/api/upload/image", data, "POST"); -} - -// 删除文件 -export function deleteFile(fileId: string) { - return request(`/api/upload/files/${fileId}`, undefined, "DELETE"); -} - -// ==================== 系统配置相关接口 ==================== - -// 获取系统配置 -export function getSystemConfig() { - return request("/api/system/config", undefined, "GET"); -} - -// 更新系统配置 -export function updateSystemConfig(data: any) { - return request("/api/system/config", data, "PUT"); -} - -// 获取系统通知 -export function getSystemNotifications() { - return request("/api/system/notifications", undefined, "GET"); -} - -// 标记通知为已读 -export function markNotificationAsRead(notificationId: string) { - return request( - `/api/system/notifications/${notificationId}/read`, - undefined, - "PUT", - ); -} diff --git a/Touchkebao/src/pages/mobile/workspace/ai-analyzer/index.module.scss b/Touchkebao/src/pages/mobile/workspace/ai-analyzer/index.module.scss deleted file mode 100644 index b67ed4f0..00000000 --- a/Touchkebao/src/pages/mobile/workspace/ai-analyzer/index.module.scss +++ /dev/null @@ -1,94 +0,0 @@ -.analyzerPage { -} - -.tabs { - background: #fff; - padding: 0 12px; - border-radius: 0 0 12px 12px; - margin-bottom: 8px; -} - -.planList { - display: flex; - flex-direction: column; - gap: 16px; - padding: 0 12px 16px 12px; -} - -.planCard { - background: #fff; - border-radius: 12px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); - padding: 16px 14px 12px 14px; - display: flex; - flex-direction: column; - gap: 8px; -} - -.cardHeader { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 6px; -} - -.cardTitle { - font-size: 16px; - font-weight: 700; - color: #222; -} - -.statusDone { - background: #e6f9e6; - color: #22c55e; - font-size: 12px; - border-radius: 8px; - padding: 2px 10px; - font-weight: 600; -} - -.statusDoing { - background: #e0f2fe; - color: #1677ff; - font-size: 12px; - border-radius: 8px; - padding: 2px 10px; - font-weight: 600; -} - -.cardInfo { - font-size: 13px; - color: #444; - display: flex; - flex-direction: column; - gap: 4px; -} - -.label { - color: #888; - font-size: 12px; - margin-right: 2px; -} - -.keyword { - display: inline-block; - background: #f3f4f6; - color: #1677ff; - border-radius: 6px; - padding: 2px 8px; - font-size: 12px; - margin-right: 6px; - margin-bottom: 2px; -} - -.cardActions { - display: flex; - gap: 10px; - margin-top: 8px; -} - -.actionBtn { - border-radius: 6px !important; - font-size: 13px !important; - padding: 0 12px !important; -} diff --git a/Touchkebao/src/pages/mobile/workspace/ai-analyzer/index.tsx b/Touchkebao/src/pages/mobile/workspace/ai-analyzer/index.tsx deleted file mode 100644 index 0bc12cc3..00000000 --- a/Touchkebao/src/pages/mobile/workspace/ai-analyzer/index.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import React, { useState } from "react"; -import NavCommon from "@/components/NavCommon"; -import Layout from "@/components/Layout/Layout"; -import { Tabs } from "antd-mobile"; -import { Button } from "antd"; -import styles from "./index.module.scss"; -import { PlusOutlined } from "@ant-design/icons"; - -const mockPlans = [ - { - id: "1", - title: "美妆用户分析", - status: "done", - device: "设备1", - wechat: "wxid_abc123", - type: "综合分析", - keywords: ["美妆", "护肤", "彩妆"], - createTime: "2023/12/15 18:30:00", - finishTime: "2023/12/15 19:45:00", - }, - { - id: "2", - title: "健身爱好者分析", - status: "doing", - device: "设备2", - wechat: "wxid_fit456", - type: "好友信息分析", - keywords: ["健身", "运动", "健康"], - createTime: "2023/12/16 17:15:00", - finishTime: "", - }, -]; - -const statusMap = { - all: "全部计划", - doing: "进行中", - done: "已完成", -}; - -const statusTag = { - done: 已完成, - doing: 分析中, -}; - -const AiAnalyzer: React.FC = () => { - const [tab, setTab] = useState<"all" | "doing" | "done">("all"); - - const filteredPlans = - tab === "all" ? mockPlans : mockPlans.filter(p => p.status === tab); - - return ( - - 新建计划 - - } - /> - } - > -
- setTab(key as any)} - className={styles.tabs} - > - - - - -
- {filteredPlans.map(plan => ( -
-
- {plan.title} - {statusTag[plan.status as "done" | "doing"]} -
-
-
- 设备: - {plan.device} | 微信号: {plan.wechat} -
-
- 分析类型: - {plan.type} -
-
- 关键词: - {plan.keywords.map(k => ( - - {k} - - ))} -
-
- 创建时间: - {plan.createTime} -
- {plan.status === "done" && ( -
- 完成时间: - {plan.finishTime} -
- )} -
-
- {plan.status === "done" ? ( - <> - - - - ) : ( - - )} -
-
- ))} -
-
-
- ); -}; - -export default AiAnalyzer; diff --git a/Touchkebao/src/pages/mobile/workspace/ai-assistant/AIAssistant.module.scss b/Touchkebao/src/pages/mobile/workspace/ai-assistant/AIAssistant.module.scss deleted file mode 100644 index 11280551..00000000 --- a/Touchkebao/src/pages/mobile/workspace/ai-assistant/AIAssistant.module.scss +++ /dev/null @@ -1,145 +0,0 @@ -.chatContainer { - display: flex; - flex-direction: column; -} - -.messageList { - flex: 1; - overflow-y: auto; - padding: 16px 12px 80px 12px; - display: flex; - flex-direction: column; - gap: 12px; -} - -.userMessage { - display: flex; - flex-direction: column; - align-items: flex-end; -} - -.aiMessage { - display: flex; - flex-direction: column; - align-items: flex-start; -} - -.bubble { - max-width: 80%; - padding: 10px 14px; - border-radius: 18px; - font-size: 15px; - line-height: 1.6; - word-break: break-word; - background: #fff; - color: #222; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); -} - -.userMessage .bubble { - background: linear-gradient(135deg, #a7e0ff 0%, #5bbcff 100%); - color: #222; - border-bottom-right-radius: 6px; -} - -.aiMessage .bubble { - background: #fff; - color: #222; - border-bottom-left-radius: 6px; -} - -.time { - font-size: 11px; - color: #aaa; - margin: 4px 8px 0 8px; - align-self: flex-end; -} - -.inputBar { - position: fixed; - left: 0; - right: 0; - bottom: 0; - display: flex; - align-items: center; - background: #fff; - padding: 10px 12px 10px 12px; - box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.04); - z-index: 10; -} - -.input { - flex: 1; - border: none; - outline: none; - background: #f3f4f6; - border-radius: 18px; - padding: 10px 14px; - font-size: 15px; - margin-right: 8px; -} - -.sendButton { - background: var( - --primary-gradient, - linear-gradient(135deg, #a7e0ff 0%, #5bbcff 100%) - ); - color: #fff; - border: none; - border-radius: 18px; - padding: 8px 18px; - font-size: 15px; - font-weight: 600; - cursor: pointer; - transition: background 0.2s; -} - -.sendButton:disabled { - background: #e5e7eb; - color: #aaa; - cursor: not-allowed; -} - -.iconBtn { - background: none; - border: none; - outline: none; - margin-right: 6px; - font-size: 20px; - color: #888; - cursor: pointer; - padding: 4px; - border-radius: 50%; - transition: - background 0.2s, - color 0.2s; -} - -.iconBtn:hover, -.iconBtn:active { - background: #f3f4f6; - color: #5bbcff; -} - -.image { - max-width: 180px; - max-height: 180px; - border-radius: 10px; - display: block; -} - -.fileLink { - color: #5bbcff; - text-decoration: none; - font-size: 15px; - word-break: break-all; - display: flex; - align-items: center; -} - -.nav-title { - color: var(--primary-color); - font-weight: 700; - font-size: 18px; - text-shadow: 0 2px 4px rgba(24, 142, 238, 0.2); -} diff --git a/Touchkebao/src/pages/mobile/workspace/ai-assistant/AIAssistant.tsx b/Touchkebao/src/pages/mobile/workspace/ai-assistant/AIAssistant.tsx deleted file mode 100644 index 4265dedb..00000000 --- a/Touchkebao/src/pages/mobile/workspace/ai-assistant/AIAssistant.tsx +++ /dev/null @@ -1,264 +0,0 @@ -import React, { useRef, useState, useEffect } from "react"; -import Layout from "@/components/Layout/Layout"; -import NavCommon from "@/components/NavCommon"; -import { - PictureOutlined, - PaperClipOutlined, - AudioOutlined, -} from "@ant-design/icons"; -import styles from "./AIAssistant.module.scss"; - -interface Message { - id: string; - content: string; - from: "user" | "ai"; - time: string; - type?: "text" | "image" | "file" | "audio"; - fileName?: string; - fileUrl?: string; -} - -const initialMessages: Message[] = [ - { - id: "1", - content: "你好!我是你的AI助手,有什么可以帮助你的吗?", - from: "ai", - time: "15:29", - type: "text", - }, -]; - -const AIAssistant: React.FC = () => { - const [messages, setMessages] = useState(initialMessages); - const [input, setInput] = useState(""); - const [loading, setLoading] = useState(false); - const messagesEndRef = useRef(null); - const fileInputRef = useRef(null); - const imageInputRef = useRef(null); - const [recognizing, setRecognizing] = useState(false); - const recognitionRef = useRef(null); - - useEffect(() => { - messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); - }, [messages]); - - // 语音识别初始化 - useEffect(() => { - if (!("webkitSpeechRecognition" in window)) return; - const SpeechRecognition = (window as any).webkitSpeechRecognition; - recognitionRef.current = new SpeechRecognition(); - recognitionRef.current.continuous = false; - recognitionRef.current.interimResults = false; - recognitionRef.current.lang = "zh-CN"; - recognitionRef.current.onresult = (event: any) => { - const transcript = event.results[0][0].transcript; - setInput(prev => prev + transcript); - setRecognizing(false); - }; - recognitionRef.current.onerror = () => setRecognizing(false); - recognitionRef.current.onend = () => setRecognizing(false); - }, []); - - const handleSend = async () => { - if (!input.trim()) return; - const userMsg: Message = { - id: Date.now().toString(), - content: input, - from: "user", - time: new Date().toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - }), - type: "text", - }; - setMessages(prev => [...prev, userMsg]); - setInput(""); - setLoading(true); - setTimeout(() => { - setMessages(prev => [ - ...prev, - { - id: Date.now().toString() + "-ai", - content: "AI正在思考...(此处可接入真实API)", - from: "ai", - time: new Date().toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - }), - type: "text", - }, - ]); - setLoading(false); - }, 1200); - }; - - // 图片上传 - const handleImageChange = (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (file) { - const url = URL.createObjectURL(file); - setMessages(prev => [ - ...prev, - { - id: Date.now().toString(), - content: url, - from: "user", - time: new Date().toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - }), - type: "image", - fileName: file.name, - fileUrl: url, - }, - ]); - } - e.target.value = ""; - }; - - // 文件上传 - const handleFileChange = (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (file) { - const url = URL.createObjectURL(file); - setMessages(prev => [ - ...prev, - { - id: Date.now().toString(), - content: file.name, - from: "user", - time: new Date().toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - }), - type: "file", - fileName: file.name, - fileUrl: url, - }, - ]); - } - e.target.value = ""; - }; - - // 语音输入 - const handleVoiceInput = () => { - if (!recognitionRef.current) return alert("当前浏览器不支持语音输入"); - if (recognizing) { - recognitionRef.current.stop(); - setRecognizing(false); - } else { - recognitionRef.current.start(); - setRecognizing(true); - } - }; - - return ( - } loading={false}> -
-
- {messages.map(msg => ( -
- {msg.type === "text" && ( -
{msg.content}
- )} - {msg.type === "image" && ( -
- {msg.fileName} -
- )} - {msg.type === "file" && ( - - )} - {/* 语音消息可后续扩展 */} -
{msg.time}
-
- ))} - {loading && ( -
-
AI正在输入...
-
- )} -
-
-
- - - - - - setInput(e.target.value)} - onKeyDown={e => { - if (e.key === "Enter") handleSend(); - }} - disabled={loading} - /> - -
-
- - ); -}; - -export default AIAssistant; diff --git a/Touchkebao/src/pages/mobile/workspace/auto-group/detail/api.ts b/Touchkebao/src/pages/mobile/workspace/auto-group/detail/api.ts deleted file mode 100644 index 86776108..00000000 --- a/Touchkebao/src/pages/mobile/workspace/auto-group/detail/api.ts +++ /dev/null @@ -1,6 +0,0 @@ -import request from "@/api/request"; - -// 获取自动建群任务详情 -export function getAutoGroupDetail(id: string) { - return request(`/api/auto-group/detail/${id}`); -} diff --git a/Touchkebao/src/pages/mobile/workspace/auto-group/detail/index.module.scss b/Touchkebao/src/pages/mobile/workspace/auto-group/detail/index.module.scss deleted file mode 100644 index 5cf097a9..00000000 --- a/Touchkebao/src/pages/mobile/workspace/auto-group/detail/index.module.scss +++ /dev/null @@ -1,149 +0,0 @@ -.autoGroupDetail { - padding: 16px 0 80px 0; - background: #f7f8fa; - min-height: 100vh; -} - -.headerBar { - display: flex; - align-items: center; - height: 48px; - background: #fff; - border-bottom: 1px solid #f0f0f0; - font-size: 18px; - font-weight: 600; - padding: 0 16px; -} - -.title { - font-size: 18px; - font-weight: 600; - color: #222; - flex: 1; - text-align: center; -} - -.infoCard { - margin-bottom: 16px; - border-radius: 12px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03); - border: none; - background: #fff; - padding: 16px; -} -.infoGrid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 16px; -} -.infoTitle { - font-size: 14px; - font-weight: 500; - color: #1677ff; - margin-bottom: 4px; -} -.infoItem { - font-size: 13px; - color: #444; - margin-bottom: 2px; -} - -.progressSection { - margin-top: 16px; -} -.progressCard { - border-radius: 12px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03); - border: none; - background: #fff; - padding: 16px; - margin-bottom: 16px; -} -.progressHeader { - display: flex; - align-items: center; - justify-content: space-between; - font-size: 15px; - font-weight: 500; - margin-bottom: 8px; -} -.groupList { - margin-top: 8px; - display: flex; - flex-direction: column; - gap: 12px; -} -.groupCard { - border-radius: 12px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03); - border: none; - background: #fff; - padding: 12px 16px; -} -.groupHeader { - display: flex; - align-items: center; - justify-content: space-between; - font-size: 15px; - font-weight: 500; - margin-bottom: 8px; -} -.memberGrid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 6px; - margin-top: 8px; -} -.memberItem { - background: #f5f7fa; - border-radius: 8px; - padding: 4px 8px; - font-size: 13px; - color: #333; - display: flex; - align-items: center; -} -.warnText { - color: #faad14; - font-size: 13px; - margin-top: 8px; - display: flex; - align-items: center; -} -.successText { - color: #389e0d; - font-size: 13px; - margin-top: 8px; - display: flex; - align-items: center; -} -.successAlert { - color: #389e0d; - background: #f6ffed; - border-radius: 8px; - padding: 8px 0; - text-align: center; - margin-top: 12px; - font-size: 14px; -} -.emptyCard { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 40px 0; - background: #fff; - border-radius: 12px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03); - margin-top: 32px; -} -.emptyTitle { - font-size: 16px; - color: #888; - margin: 12px 0 4px 0; -} -.emptyDesc { - font-size: 13px; - color: #bbb; - margin-bottom: 16px; -} diff --git a/Touchkebao/src/pages/mobile/workspace/auto-group/detail/index.tsx b/Touchkebao/src/pages/mobile/workspace/auto-group/detail/index.tsx deleted file mode 100644 index a82f5d73..00000000 --- a/Touchkebao/src/pages/mobile/workspace/auto-group/detail/index.tsx +++ /dev/null @@ -1,348 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { useParams, useNavigate } from "react-router-dom"; -import { Card, Button, Toast, ProgressBar, Tag } from "antd-mobile"; -import { TeamOutline, LeftOutline } from "antd-mobile-icons"; -import { AlertOutlined } from "@ant-design/icons"; -import Layout from "@/components/Layout/Layout"; -import NavCommon from "@/components/NavCommon/index"; -import style from "./index.module.scss"; - -interface GroupMember { - id: string; - nickname: string; - wechatId: string; - tags: string[]; -} - -interface Group { - id: string; - members: GroupMember[]; -} - -interface GroupTaskDetail { - id: string; - name: string; - status: "preparing" | "creating" | "completed" | "paused"; - totalGroups: number; - currentGroupIndex: number; - groups: Group[]; - createTime: string; - lastUpdateTime: string; - creator: string; - deviceCount: number; - targetFriends: number; - groupSize: { min: number; max: number }; - timeRange: { start: string; end: string }; - targetTags: string[]; - groupNameTemplate: string; - groupDescription: string; -} - -const mockTaskDetail: GroupTaskDetail = { - id: "1", - name: "VIP客户建群", - status: "creating", - totalGroups: 5, - currentGroupIndex: 2, - groups: Array.from({ length: 5 }).map((_, index) => ({ - id: `group-${index}`, - members: Array.from({ length: Math.floor(Math.random() * 10) + 30 }).map( - (_, mIndex) => ({ - id: `member-${index}-${mIndex}`, - nickname: `用户${mIndex + 1}`, - wechatId: `wx_${mIndex}`, - tags: [`标签${(mIndex % 3) + 1}`], - }), - ), - })), - createTime: "2024-11-20 19:04:14", - lastUpdateTime: "2025-02-06 13:12:35", - creator: "admin", - deviceCount: 2, - targetFriends: 156, - groupSize: { min: 20, max: 50 }, - timeRange: { start: "09:00", end: "21:00" }, - targetTags: ["VIP客户", "高价值"], - groupNameTemplate: "VIP客户交流群{序号}", - groupDescription: "VIP客户专属交流群,提供优质服务", -}; - -const GroupPreview: React.FC<{ - groupIndex: number; - members: GroupMember[]; - isCreating: boolean; - isCompleted: boolean; - onRetry?: () => void; -}> = ({ groupIndex, members, isCreating, isCompleted, onRetry }) => { - const [expanded, setExpanded] = useState(false); - const targetSize = 38; - return ( - -
-
- 群 {groupIndex + 1} - - {isCompleted ? "已完成" : isCreating ? "创建中" : "等待中"} - -
-
- - {members.length}/{targetSize} -
-
- {isCreating && !isCompleted && ( - - )} - {expanded ? ( - <> -
- {members.map(member => ( -
- {member.nickname} - {member.tags.length > 0 && ( - - {member.tags[0]} - - )} -
- ))} -
- - - ) : ( - - )} - {!isCompleted && members.length < targetSize && ( -
- - 群人数不足{targetSize}人 - {onRetry && ( - - )} -
- )} - {isCompleted &&
群创建完成
} -
- ); -}; - -const GroupCreationProgress: React.FC<{ - taskDetail: GroupTaskDetail; - onComplete: () => void; -}> = ({ taskDetail, onComplete }) => { - const [groups, setGroups] = useState(taskDetail.groups); - const [currentGroupIndex, setCurrentGroupIndex] = useState( - taskDetail.currentGroupIndex, - ); - const [status, setStatus] = useState( - taskDetail.status, - ); - - useEffect(() => { - if (status === "creating" && currentGroupIndex < groups.length) { - const timer = setTimeout(() => { - if (currentGroupIndex === groups.length - 1) { - setStatus("completed"); - onComplete(); - } else { - setCurrentGroupIndex(prev => prev + 1); - } - }, 3000); - return () => clearTimeout(timer); - } - }, [status, currentGroupIndex, groups.length, onComplete]); - - const handleRetryGroup = (groupIndex: number) => { - setGroups(prev => - prev.map((group, index) => { - if (index === groupIndex) { - return { - ...group, - members: [ - ...group.members, - { - id: `retry-member-${Date.now()}`, - nickname: `补充用户${group.members.length + 1}`, - wechatId: `wx_retry_${Date.now()}`, - tags: ["新加入"], - }, - ], - }; - } - return group; - }), - ); - }; - - return ( -
- -
-
- 建群进度 - - {status === "preparing" - ? "准备中" - : status === "creating" - ? "创建中" - : "已完成"} - -
-
- {currentGroupIndex + 1}/{groups.length}组 -
-
- -
-
- {groups.map((group, index) => ( - handleRetryGroup(index)} - /> - ))} -
- {status === "completed" && ( -
所有群组已创建完成
- )} -
- ); -}; - -const AutoGroupDetail: React.FC = () => { - const { id } = useParams(); - const navigate = useNavigate(); - const [loading, setLoading] = useState(true); - const [taskDetail, setTaskDetail] = useState(null); - - useEffect(() => { - setLoading(true); - setTimeout(() => { - setTaskDetail(mockTaskDetail); - setLoading(false); - }, 800); - }, [id]); - - const handleComplete = () => { - Toast.show({ content: "所有群组已创建完成" }); - }; - - if (!taskDetail) { - return ( - navigate(-1)} />} - > - - -
任务不存在
-
请检查任务ID是否正确
- -
-
- ); - } - - return ( - navigate(-1)} - /> - } - loading={loading} - > -
- -
-
-
基本信息
-
任务名称:{taskDetail.name}
-
- 创建时间:{taskDetail.createTime} -
-
创建人:{taskDetail.creator}
-
- 执行设备:{taskDetail.deviceCount} 个 -
-
-
-
建群配置
-
- 群组规模:{taskDetail.groupSize.min}-{taskDetail.groupSize.max}{" "} - 人 -
-
- 执行时间:{taskDetail.timeRange.start} -{" "} - {taskDetail.timeRange.end} -
-
- 目标标签:{taskDetail.targetTags.join(", ")} -
-
- 群名称模板:{taskDetail.groupNameTemplate} -
-
-
-
- -
-
- ); -}; - -export default AutoGroupDetail; diff --git a/Touchkebao/src/pages/mobile/workspace/auto-group/form/api.ts b/Touchkebao/src/pages/mobile/workspace/auto-group/form/api.ts deleted file mode 100644 index 68dc7135..00000000 --- a/Touchkebao/src/pages/mobile/workspace/auto-group/form/api.ts +++ /dev/null @@ -1,17 +0,0 @@ -import request from "@/api/request"; - -// 创建朋友圈同步任务 -export const createAutoGroup = (params: any) => - request("/v1/workbench/create", params, "POST"); - -// 更新朋友圈同步任务 -export const updateAutoGroup = (params: any) => - request("/v1/workbench/update", params, "POST"); - -// 获取朋友圈同步任务详情 -export const getAutoGroupDetail = (id: string) => - request("/v1/workbench/detail", { id }, "GET"); - -// 获取朋友圈同步任务列表 -export const getAutoGroupList = (params: any) => - request("/v1/workbench/list", params, "GET"); diff --git a/Touchkebao/src/pages/mobile/workspace/auto-group/form/components/BasicSettings.tsx b/Touchkebao/src/pages/mobile/workspace/auto-group/form/components/BasicSettings.tsx deleted file mode 100644 index 39712e2d..00000000 --- a/Touchkebao/src/pages/mobile/workspace/auto-group/form/components/BasicSettings.tsx +++ /dev/null @@ -1,336 +0,0 @@ -import React, { useImperativeHandle, forwardRef, useEffect } from "react"; -import { Button, Card, Switch, Form, InputNumber } from "antd"; -import { Input } from "antd"; - -const { TextArea } = Input; - -interface BasicSettingsProps { - initialValues?: { - name: string; - startTime: string; - endTime: string; - groupSizeMin: number; - groupSizeMax: number; - maxGroupsPerDay: number; - groupNameTemplate: string; - groupDescription: string; - status: number; - }; -} - -export interface BasicSettingsRef { - validate: () => Promise; - getValues: () => any; -} - -const BasicSettings = forwardRef( - ( - { - initialValues = { - name: "", - startTime: "06:00", - endTime: "23:59", - groupSizeMin: 20, - groupSizeMax: 50, - maxGroupsPerDay: 10, - groupNameTemplate: "", - groupDescription: "", - status: 1, - }, - }, - ref, - ) => { - const [form] = Form.useForm(); - - // 当initialValues变化时,重新设置表单值 - useEffect(() => { - form.setFieldsValue(initialValues); - }, [form, initialValues]); - - // 暴露方法给父组件 - useImperativeHandle(ref, () => ({ - validate: async () => { - try { - await form.validateFields(); - return true; - } catch (error) { - console.log("BasicSettings 表单验证失败:", error); - return false; - } - }, - getValues: () => { - return form.getFieldsValue(); - }, - })); - - return ( -
- -
{ - // 可以在这里处理表单值变化 - }} - > - {/* 任务名称 */} - - - - - {/* 允许建群的时间段 */} - -
- - - - - - - -
-
- - {/* 每日最大建群数 */} - { - const numValue = Number(value); - if (value && (numValue < 1 || numValue > 100)) { - return Promise.reject( - new Error("每日最大建群数在1-100之间"), - ); - } - return Promise.resolve(); - }, - }, - ]} - > - form.setFieldValue("maxGroupsPerDay", value)} - addonBefore={ - - } - addonAfter={ - - } - /> - - - {/* 群组最小人数 */} - { - const numValue = Number(value); - if (value && (numValue < 1 || numValue > 500)) { - return Promise.reject( - new Error("群组最小人数在1-500之间"), - ); - } - return Promise.resolve(); - }, - }, - ]} - > - form.setFieldValue("groupSizeMin", value)} - addonBefore={ - - } - addonAfter={ - - } - /> - - - {/* 群组最大人数 */} - { - const numValue = Number(value); - if (value && (numValue < 1 || numValue > 500)) { - return Promise.reject( - new Error("群组最大人数在1-500之间"), - ); - } - return Promise.resolve(); - }, - }, - ]} - > - form.setFieldValue("groupSizeMax", value)} - addonBefore={ - - } - addonAfter={ - - } - /> - - - {/* 群名称模板 */} - - - - - {/* 群描述 */} - -