feat: 本次提交更新内容如下

朋友圈同步完成
This commit is contained in:
2025-07-23 17:54:38 +08:00
parent f6edba490d
commit 0baf08b95b
5 changed files with 416 additions and 431 deletions

View File

@@ -45,7 +45,7 @@ const formatDate = (dateStr?: string) => {
// 组件属性接口 // 组件属性接口
interface ContentLibrarySelectionProps { interface ContentLibrarySelectionProps {
selectedLibraries: string[]; selectedLibraries: (string | number)[];
onSelect: (libraries: string[]) => void; onSelect: (libraries: string[]) => void;
onSelectDetail?: (libraries: ContentLibraryItem[]) => void; onSelectDetail?: (libraries: ContentLibraryItem[]) => void;
placeholder?: string; placeholder?: string;

View File

@@ -1,13 +1,8 @@
import React, { useState, useEffect, useCallback } from "react"; import React, { useState, useEffect, useCallback } from "react";
import { useParams, useNavigate } from "react-router-dom"; import { useParams, useNavigate } from "react-router-dom";
import { Button, Switch, message, Spin, Badge } from "antd"; import { Button, Switch, message, Spin } from "antd";
import { import NavCommon from "@/components/NavCommon";
ArrowLeftOutlined, import { EditOutlined } from "@ant-design/icons";
EditOutlined,
ClockCircleOutlined,
DatabaseOutlined,
MobileOutlined,
} from "@ant-design/icons";
import Layout from "@/components/Layout/Layout"; import Layout from "@/components/Layout/Layout";
import style from "./index.module.scss"; import style from "./index.module.scss";
import request from "@/api/request"; import request from "@/api/request";
@@ -117,22 +112,19 @@ const MomentsSyncDetail: React.FC = () => {
return ( return (
<Layout <Layout
header={ header={
<div className={style.headerBar}> <NavCommon
<Button title="查看朋友圈同步任务"
type="text" right={
icon={<ArrowLeftOutlined />} <Button
onClick={() => navigate("/workspace/moments-sync")} icon={<EditOutlined />}
className={style.backBtn} onClick={handleEdit}
/> className={style.editBtn}
<span className={style.title}></span> type="primary"
<Button >
icon={<EditOutlined />}
onClick={handleEdit} </Button>
className={style.editBtn} }
> />
</Button>
</div>
} }
> >
<div className={style.detailBg}> <div className={style.detailBg}>
@@ -155,29 +147,49 @@ const MomentsSyncDetail: React.FC = () => {
size="small" size="small"
/> />
</div> </div>
<div className={style.detailInfoRow}> <div
<div className={style.infoCol}> style={{
{task.config?.devices?.length || 0} display: "flex",
justifyContent: "space-between",
marginTop: 16,
}}
>
<div>
<div style={{ fontWeight: 500, marginBottom: 8 }}></div>
<div style={{ fontSize: 14, color: "#222", marginBottom: 4 }}>
{task.config?.devices?.length || 0}
</div>
<div style={{ fontSize: 14, color: "#222", marginBottom: 4 }}>
{task.config?.contentLibraryNames?.join("") || "-"}
</div>
<div style={{ fontSize: 14, color: "#222", marginBottom: 4 }}>
{task.syncCount || 0}
</div>
<div style={{ fontSize: 14, color: "#222" }}>
{task.creatorName}
</div>
</div> </div>
<div className={style.infoCol}> <div>
{task.config?.contentLibraryNames?.join(",") || "-"} <div style={{ fontWeight: 500, marginBottom: 8 }}></div>
<div style={{ fontSize: 14, color: "#222", marginBottom: 4 }}>
{task.createTime}
</div>
<div style={{ fontSize: 14, color: "#222" }}>
{task.lastSyncTime || "无"}
</div>
</div> </div>
</div> </div>
<div className={style.detailInfoRow}> <div
<div className={style.infoCol}> style={{
{task.syncCount || 0} borderTop: "1px solid #f0f0f0",
</div> margin: "16px 0 0 0",
<div className={style.infoCol}>{task.creatorName}</div> paddingTop: 12,
</div> }}
<div className={style.detailBottom}> >
<div className={style.bottomLeft}> <div style={{ fontWeight: 500, marginBottom: 8 }}></div>
<ClockCircleOutlined className={style.clockIcon} /> <div style={{ color: "#888", fontSize: 14 }}></div>
{task.lastSyncTime || "无"}
</div>
<div className={style.bottomRight}>{task.createTime}</div>
</div> </div>
</div> </div>
{/* 可继续补充更多详情卡片,如同步设置、同步记录等 */}
</div> </div>
</Layout> </Layout>
); );

View File

@@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { Switch, Input, message, Dropdown, Menu } from "antd"; import { Switch, Input, message, Dropdown, Menu } from "antd";
import { NavBar, Button } from "antd-mobile"; import { NavBar, Button } from "antd-mobile";
import NavCommon from "@/components/NavCommon";
import { import {
PlusOutlined, PlusOutlined,
SearchOutlined, SearchOutlined,
@@ -149,17 +150,8 @@ const MomentsSync: React.FC = () => {
<Layout <Layout
header={ header={
<> <>
<NavBar <NavCommon
back={null} title="朋友圈同步"
style={{ background: "#fff" }}
left={
<div className="nav-title">
<ArrowLeftOutlined
twoToneColor="#1677ff"
onClick={() => navigate("/workspace")}
/>
</div>
}
right={ right={
<Button <Button
size="small" size="small"
@@ -169,9 +161,8 @@ const MomentsSync: React.FC = () => {
<PlusOutlined /> <PlusOutlined />
</Button> </Button>
} }
> />
<span className="nav-title"></span>
</NavBar>
<div className="search-bar"> <div className="search-bar">
<div className="search-input-wrapper"> <div className="search-input-wrapper">
<Input <Input
@@ -194,6 +185,7 @@ const MomentsSync: React.FC = () => {
</div> </div>
</> </>
} }
loading={loading}
> >
<div className={style.pageBg}> <div className={style.pageBg}>
<div className={style.taskList}> <div className={style.taskList}>

View File

@@ -1,31 +1,12 @@
import request from "@/api/request"; import request from "@/api/request";
// 创建朋友圈同步任务 // 创建朋友圈同步任务
export const createMomentsSync = (params: { export const createMomentsSync = (params: any) =>
name: string; request("/v1/workbench/create", params, "POST");
devices: string[];
contentLibraries: string[];
syncCount: number;
startTime: string;
endTime: string;
accountType: number;
status: number;
type: number;
}) => request("/v1/workbench/create", params, "POST");
// 更新朋友圈同步任务 // 更新朋友圈同步任务
export const updateMomentsSync = (params: { export const updateMomentsSync = (params: any) =>
id: string; request("/v1/workbench/update", params, "POST");
name: string;
devices: string[];
contentLibraries: string[];
syncCount: number;
startTime: string;
endTime: string;
accountType: number;
status: number;
type: number;
}) => request("/v1/workbench/update", params, "POST");
// 获取朋友圈同步任务详情 // 获取朋友圈同步任务详情
export const getMomentsSyncDetail = (id: string) => export const getMomentsSyncDetail = (id: string) =>

View File

@@ -1,351 +1,351 @@
import React, { useState, useEffect, useCallback } from "react"; import React, { useState, useEffect, useCallback } from "react";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { Button, Input, Switch, message, Spin } from "antd"; import { Button, Input, Switch, message, Spin } from "antd";
import { MinusOutlined, PlusOutlined } from "@ant-design/icons"; import { MinusOutlined, PlusOutlined } from "@ant-design/icons";
import Layout from "@/components/Layout/Layout"; import Layout from "@/components/Layout/Layout";
import style from "./index.module.scss"; import style from "./index.module.scss";
import request from "@/api/request"; import request from "@/api/request";
import StepIndicator from "@/components/StepIndicator"; import StepIndicator from "@/components/StepIndicator";
import { import {
createMomentsSync, createMomentsSync,
updateMomentsSync, updateMomentsSync,
getMomentsSyncDetail, getMomentsSyncDetail,
} from "./api"; } from "./api";
import DeviceSelection from "@/components/DeviceSelection"; import DeviceSelection from "@/components/DeviceSelection";
import ContentLibrarySelection from "@/components/ContentLibrarySelection"; import ContentLibrarySelection from "@/components/ContentLibrarySelection";
import NavCommon from "@/components/NavCommon"; import NavCommon from "@/components/NavCommon";
const steps = [ const steps = [
{ id: 1, title: "基础设置", subtitle: "基础设置" }, { id: 1, title: "基础设置", subtitle: "基础设置" },
{ id: 2, title: "设备选择", subtitle: "设备选择" }, { id: 2, title: "设备选择", subtitle: "设备选择" },
{ id: 3, title: "内容库选择", subtitle: "内容库选择" }, { id: 3, title: "内容库选择", subtitle: "内容库选择" },
]; ];
const defaultForm = { const defaultForm = {
taskName: "", taskName: "",
startTime: "06:00", startTime: "06:00",
endTime: "23:59", endTime: "23:59",
syncCount: 5, syncCount: 5,
syncInterval: 30, syncInterval: 30,
syncType: 1, // 1=业务号 2=人设号 syncType: 1, // 1=业务号 2=人设号
accountType: "business" as "business" | "personal", // 仅UI用 accountType: "business" as "business" | "personal", // 仅UI用
enabled: true, enabled: true,
selectedDevices: [] as string[], selectedDevices: [] as string[],
selectedLibraries: [] as (string | number)[], selectedLibraries: [] as (string | number)[],
contentTypes: ["text", "image", "video"], contentTypes: ["text", "image", "video"],
targetTags: [] as string[], targetTags: [] as string[],
filterKeywords: [] as string[], filterKeywords: [] as string[],
}; };
const NewMomentsSync: React.FC = () => { const NewMomentsSync: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const isEditMode = !!id; const isEditMode = !!id;
const [currentStep, setCurrentStep] = useState(0); const [currentStep, setCurrentStep] = useState(0);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState({ ...defaultForm }); const [formData, setFormData] = useState({ ...defaultForm });
// 获取详情(编辑) // 获取详情(编辑)
const fetchDetail = useCallback(async () => { const fetchDetail = useCallback(async () => {
if (!id) return; if (!id) return;
setLoading(true); setLoading(true);
try { try {
const res = await getMomentsSyncDetail(id); const res = await getMomentsSyncDetail(id);
if (res) { if (res) {
setFormData({ setFormData({
taskName: res.name, taskName: res.name,
startTime: res.timeRange?.start || "06:00", startTime: res.timeRange?.start || "06:00",
endTime: res.timeRange?.end || "23:59", endTime: res.timeRange?.end || "23:59",
syncCount: res.config?.syncCount || res.syncCount || 5, syncCount: res.config?.syncCount || res.syncCount || 5,
syncInterval: res.config?.syncInterval || res.syncInterval || 30, syncInterval: res.config?.syncInterval || res.syncInterval || 30,
syncType: res.accountType === 1 ? 1 : 2, syncType: res.accountType === 1 ? 1 : 2,
accountType: res.accountType === 1 ? "business" : "personal", accountType: res.accountType === 1 ? "business" : "personal",
enabled: res.status === 1, enabled: res.status === 1,
selectedDevices: res.config?.devices || [], selectedDevices: res.config?.devices || [],
selectedLibraries: res.config?.contentLibraryNames || [], selectedLibraries: res.config?.contentLibraryNames || [],
contentTypes: res.config?.contentTypes || ["text", "image", "video"], contentTypes: res.config?.contentTypes || ["text", "image", "video"],
targetTags: res.config?.targetTags || [], targetTags: res.config?.targetTags || [],
filterKeywords: res.config?.filterKeywords || [], filterKeywords: res.config?.filterKeywords || [],
}); });
} }
} catch { } catch {
message.error("获取详情失败"); message.error("获取详情失败");
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [id]); }, [id]);
useEffect(() => { useEffect(() => {
if (isEditMode) fetchDetail(); if (isEditMode) fetchDetail();
}, [isEditMode, fetchDetail]); }, [isEditMode, fetchDetail]);
// 步骤切换 // 步骤切换
const next = () => setCurrentStep((s) => Math.min(s + 1, steps.length - 1)); const next = () => setCurrentStep((s) => Math.min(s + 1, steps.length - 1));
const prev = () => setCurrentStep((s) => Math.max(s - 1, 0)); const prev = () => setCurrentStep((s) => Math.max(s - 1, 0));
// 表单数据更新 // 表单数据更新
const updateForm = (data: Partial<typeof formData>) => { const updateForm = (data: Partial<typeof formData>) => {
setFormData((prev) => ({ ...prev, ...data })); setFormData((prev) => ({ ...prev, ...data }));
}; };
// UI选择账号类型时同步syncType和accountType // UI选择账号类型时同步syncType和accountType
const handleAccountTypeChange = (type: "business" | "personal") => { const handleAccountTypeChange = (type: "business" | "personal") => {
setFormData((prev) => ({ setFormData((prev) => ({
...prev, ...prev,
accountType: type, accountType: type,
syncType: type === "business" ? 1 : 2, syncType: type === "business" ? 1 : 2,
})); }));
}; };
// 提交 // 提交
const handleSubmit = async () => { const handleSubmit = async () => {
if (!formData.taskName.trim()) { if (!formData.taskName.trim()) {
message.error("请输入任务名称"); message.error("请输入任务名称");
return; return;
} }
if (formData.selectedDevices.length === 0) { if (formData.selectedDevices.length === 0) {
message.error("请选择设备"); message.error("请选择设备");
return; return;
} }
if (formData.selectedLibraries.length === 0) { if (formData.selectedLibraries.length === 0) {
message.error("请选择内容库"); message.error("请选择内容库");
return; return;
} }
setLoading(true); setLoading(true);
try { try {
const params = { const params = {
name: formData.taskName, name: formData.taskName,
devices: formData.selectedDevices, devices: formData.selectedDevices,
contentLibraries: formData.selectedLibraries.map(Number), contentLibraries: formData.selectedLibraries.map(Number),
syncInterval: formData.syncInterval, syncInterval: formData.syncInterval,
syncCount: formData.syncCount, syncCount: formData.syncCount,
syncType: formData.syncType, // 账号类型真实传参 syncType: formData.syncType, // 账号类型真实传参
accountType: formData.accountType === "business" ? 1 : 2, // 也要传 accountType: formData.accountType === "business" ? 1 : 2, // 也要传
startTime: formData.startTime, startTime: formData.startTime,
endTime: formData.endTime, endTime: formData.endTime,
contentTypes: formData.contentTypes, contentTypes: formData.contentTypes,
targetTags: formData.targetTags, targetTags: formData.targetTags,
filterKeywords: formData.filterKeywords, filterKeywords: formData.filterKeywords,
type: 2, type: 2,
status: formData.enabled ? 1 : 2, status: formData.enabled ? 1 : 2,
}; };
if (isEditMode && id) { if (isEditMode && id) {
await updateMomentsSync({ id, ...params }); await updateMomentsSync({ id, ...params });
message.success("更新成功"); message.success("更新成功");
navigate(`/workspace/moments-sync/${id}`); navigate(`/workspace/moments-sync/${id}`);
} else { } else {
await createMomentsSync(params); await createMomentsSync(params);
message.success("创建成功"); message.success("创建成功");
navigate("/workspace/moments-sync"); navigate("/workspace/moments-sync");
} }
} catch { } catch {
message.error(isEditMode ? "更新失败" : "创建失败"); message.error(isEditMode ? "更新失败" : "创建失败");
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
// 步骤内容(去除按钮) // 步骤内容(去除按钮)
const renderStep = () => { const renderStep = () => {
if (currentStep === 0) { if (currentStep === 0) {
return ( return (
<div className={style.formStep}> <div className={style.formStep}>
<div className={style.formItem}> <div className={style.formItem}>
<div className={style.formLabel}></div> <div className={style.formLabel}></div>
<Input <Input
value={formData.taskName} value={formData.taskName}
onChange={(e) => updateForm({ taskName: e.target.value })} onChange={(e) => updateForm({ taskName: e.target.value })}
placeholder="请输入任务名称" placeholder="请输入任务名称"
maxLength={30} maxLength={30}
className={style.input} className={style.input}
/> />
</div> </div>
<div className={style.formItem}> <div className={style.formItem}>
<div className={style.formLabel}></div> <div className={style.formLabel}></div>
<div className={style.timeRow}> <div className={style.timeRow}>
<Input <Input
type="time" type="time"
value={formData.startTime} value={formData.startTime}
onChange={(e) => updateForm({ startTime: e.target.value })} onChange={(e) => updateForm({ startTime: e.target.value })}
className={style.inputTime} className={style.inputTime}
/> />
<span className={style.timeTo}></span> <span className={style.timeTo}></span>
<Input <Input
type="time" type="time"
value={formData.endTime} value={formData.endTime}
onChange={(e) => updateForm({ endTime: e.target.value })} onChange={(e) => updateForm({ endTime: e.target.value })}
className={style.inputTime} className={style.inputTime}
/> />
</div> </div>
</div> </div>
<div className={style.formItem}> <div className={style.formItem}>
<div className={style.formLabel}></div> <div className={style.formLabel}></div>
<div className={style.counterRow}> <div className={style.counterRow}>
<button <button
className={style.counterBtn} className={style.counterBtn}
onClick={() => onClick={() =>
updateForm({ syncCount: Math.max(1, formData.syncCount - 1) }) updateForm({ syncCount: Math.max(1, formData.syncCount - 1) })
} }
> >
<MinusOutlined /> <MinusOutlined />
</button> </button>
<span className={style.counterValue}>{formData.syncCount}</span> <span className={style.counterValue}>{formData.syncCount}</span>
<button <button
className={style.counterBtn} className={style.counterBtn}
onClick={() => onClick={() =>
updateForm({ syncCount: formData.syncCount + 1 }) updateForm({ syncCount: formData.syncCount + 1 })
} }
> >
<PlusOutlined /> <PlusOutlined />
</button> </button>
<span className={style.counterUnit}></span> <span className={style.counterUnit}></span>
</div> </div>
</div> </div>
<div className={style.formItem}> <div className={style.formItem}>
<div className={style.formLabel}></div> <div className={style.formLabel}></div>
<div className={style.accountTypeRow}> <div className={style.accountTypeRow}>
<button <button
className={`${style.accountTypeBtn} ${formData.accountType === "business" ? style.accountTypeActive : ""}`} className={`${style.accountTypeBtn} ${formData.accountType === "business" ? style.accountTypeActive : ""}`}
onClick={() => handleAccountTypeChange("business")} onClick={() => handleAccountTypeChange("business")}
> >
</button> </button>
<button <button
className={`${style.accountTypeBtn} ${formData.accountType === "personal" ? style.accountTypeActive : ""}`} className={`${style.accountTypeBtn} ${formData.accountType === "personal" ? style.accountTypeActive : ""}`}
onClick={() => handleAccountTypeChange("personal")} onClick={() => handleAccountTypeChange("personal")}
> >
</button> </button>
</div> </div>
</div> </div>
<div className={style.formItem}> <div className={style.formItem}>
<div className={style.switchRow}> <div className={style.switchRow}>
<span className={style.switchLabel}></span> <span className={style.switchLabel}></span>
<Switch <Switch
checked={formData.enabled} checked={formData.enabled}
onChange={(checked) => updateForm({ enabled: checked })} onChange={(checked) => updateForm({ enabled: checked })}
className={style.switch} className={style.switch}
/> />
</div> </div>
</div> </div>
</div> </div>
); );
} }
if (currentStep === 1) { if (currentStep === 1) {
return ( return (
<div className={style.formStep}> <div className={style.formStep}>
<div className={style.formItem}> <div className={style.formItem}>
<div className={style.formLabel}></div> <div className={style.formLabel}></div>
<DeviceSelection <DeviceSelection
selectedDevices={formData.selectedDevices} selectedDevices={formData.selectedDevices}
onSelect={(devices) => updateForm({ selectedDevices: devices })} onSelect={(devices) => updateForm({ selectedDevices: devices })}
placeholder="请选择设备" placeholder="请选择设备"
showSelectedList={true} showSelectedList={true}
selectedListMaxHeight={200} selectedListMaxHeight={200}
/> />
</div> </div>
</div> </div>
); );
} }
if (currentStep === 2) { if (currentStep === 2) {
return ( return (
<div className={style.formStep}> <div className={style.formStep}>
<div className={style.formItem}> <div className={style.formItem}>
<div className={style.formLabel}></div> <div className={style.formLabel}></div>
<ContentLibrarySelection <ContentLibrarySelection
selectedLibraries={formData.selectedLibraries} selectedLibraries={formData.selectedLibraries}
onSelect={(libs) => updateForm({ selectedLibraries: libs })} onSelect={(libs) => updateForm({ selectedLibraries: libs })}
placeholder="请选择内容库" placeholder="请选择内容库"
showSelectedList={true} showSelectedList={true}
selectedListMaxHeight={200} selectedListMaxHeight={200}
/> />
{formData.selectedLibraries.length > 0 && ( {formData.selectedLibraries.length > 0 && (
<div className={style.selectedTip}> <div className={style.selectedTip}>
: {formData.selectedLibraries.length} : {formData.selectedLibraries.length}
</div> </div>
)} )}
</div> </div>
</div> </div>
); );
} }
return null; return null;
}; };
// 统一底部按钮 // 统一底部按钮
const renderFooter = () => { const renderFooter = () => {
if (loading) return null; if (loading) return null;
if (currentStep === 0) { if (currentStep === 0) {
return ( return (
<div className={style.formStepBtnRow}> <div className={style.formStepBtnRow}>
<Button <Button
type="primary" type="primary"
disabled={!formData.taskName.trim()} disabled={!formData.taskName.trim()}
onClick={next} onClick={next}
className={style.nextBtn} className={style.nextBtn}
block block
> >
</Button> </Button>
</div> </div>
); );
} }
if (currentStep === 1) { if (currentStep === 1) {
return ( return (
<div className={style.formStepBtnRow}> <div className={style.formStepBtnRow}>
<Button onClick={prev} className={style.prevBtn} block> <Button onClick={prev} className={style.prevBtn} block>
</Button> </Button>
<Button type="primary" onClick={next} className={style.nextBtn} block> <Button type="primary" onClick={next} className={style.nextBtn} block>
</Button> </Button>
</div> </div>
); );
} }
if (currentStep === 2) { if (currentStep === 2) {
return ( return (
<div className={style.formStepBtnRow}> <div className={style.formStepBtnRow}>
<Button onClick={prev} className={style.prevBtn} block> <Button onClick={prev} className={style.prevBtn} block>
</Button> </Button>
<Button <Button
type="primary" type="primary"
onClick={handleSubmit} onClick={handleSubmit}
loading={loading} loading={loading}
className={style.completeBtn} className={style.completeBtn}
block block
> >
</Button> </Button>
</div> </div>
); );
} }
return null; return null;
}; };
return ( return (
<Layout <Layout
header={ header={
<NavCommon title={isEditMode ? "编辑朋友圈同步" : "新建朋友圈同步"} /> <NavCommon title={isEditMode ? "编辑朋友圈同步" : "新建朋友圈同步"} />
} }
footer={renderFooter()} footer={renderFooter()}
> >
<div className={style.formBg}> <div className={style.formBg}>
<StepIndicator currentStep={currentStep + 1} steps={steps} /> <StepIndicator currentStep={currentStep + 1} steps={steps} />
{loading ? ( {loading ? (
<div className={style.formLoading}> <div className={style.formLoading}>
<Spin /> <Spin />
</div> </div>
) : ( ) : (
renderStep() renderStep()
)} )}
</div> </div>
</Layout> </Layout>
); );
}; };
export default NewMomentsSync; export default NewMomentsSync;