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 {
selectedLibraries: string[];
selectedLibraries: (string | number)[];
onSelect: (libraries: string[]) => void;
onSelectDetail?: (libraries: ContentLibraryItem[]) => void;
placeholder?: string;

View File

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

View File

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

View File

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

View File

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