FEAT => 本次更新项目为:

This commit is contained in:
超级老白兔
2025-08-08 17:24:50 +08:00
parent e0c0c59e73
commit 82a8c8b374
10 changed files with 260 additions and 43 deletions

View File

@@ -1,7 +1,7 @@
// 设备选择项接口 // 设备选择项接口
export interface DeviceSelectionItem { export interface DeviceSelectionItem {
id: number; id: number;
name: string; memo: string;
imei: string; imei: string;
wechatId: string; wechatId: string;
status: "online" | "offline"; status: "online" | "offline";

View File

@@ -100,7 +100,7 @@ const DeviceSelection: React.FC<DeviceSelectionProps> = ({
textOverflow: "ellipsis", textOverflow: "ellipsis",
}} }}
> >
{device.name} - {device.wechatId} {device.memo} - {device.wechatId}
</div> </div>
{!readonly && ( {!readonly && (
<Button <Button

View File

@@ -44,7 +44,7 @@ const SelectionPopup: React.FC<SelectionPopupProps> = ({
setDevices( setDevices(
res.list.map((d: any) => ({ res.list.map((d: any) => ({
id: d.id?.toString() || "", id: d.id?.toString() || "",
name: d.memo || d.imei || "", memo: d.memo || d.imei || "",
imei: d.imei || "", imei: d.imei || "",
wechatId: d.wechatId || "", wechatId: d.wechatId || "",
status: d.alive === 1 ? "online" : "offline", status: d.alive === 1 ? "online" : "offline",
@@ -169,7 +169,7 @@ const SelectionPopup: React.FC<SelectionPopupProps> = ({
/> />
<div className={style.deviceInfo}> <div className={style.deviceInfo}>
<div className={style.deviceInfoRow}> <div className={style.deviceInfoRow}>
<span className={style.deviceName}>{device.name}</span> <span className={style.deviceName}>{device.memo}</span>
<div <div
className={ className={
device.status === "online" device.status === "online"

View File

@@ -22,7 +22,7 @@ interface WechatGroup {
interface SelectionPopupProps { interface SelectionPopupProps {
visible: boolean; visible: boolean;
onVisibleChange: (visible: boolean) => void; onVisibleChange: (visible: boolean) => void;
selectedGroups: GroupSelectionItem[]; selectedOptions: GroupSelectionItem[];
onSelect: (groups: GroupSelectionItem[]) => void; onSelect: (groups: GroupSelectionItem[]) => void;
onSelectDetail?: (groups: WechatGroup[]) => void; onSelectDetail?: (groups: WechatGroup[]) => void;
readonly?: boolean; readonly?: boolean;
@@ -35,7 +35,7 @@ interface SelectionPopupProps {
export default function SelectionPopup({ export default function SelectionPopup({
visible, visible,
onVisibleChange, onVisibleChange,
selectedGroups, selectedOptions,
onSelect, onSelect,
onSelectDetail, onSelectDetail,
readonly = false, readonly = false,
@@ -78,9 +78,9 @@ export default function SelectionPopup({
const handleGroupToggle = (group: GroupSelectionItem) => { const handleGroupToggle = (group: GroupSelectionItem) => {
if (readonly) return; if (readonly) return;
const newSelectedGroups = selectedGroups.some(g => g.id === group.id) const newSelectedGroups = selectedOptions.some(g => g.id === group.id)
? selectedGroups.filter(g => g.id !== group.id) ? selectedOptions.filter(g => g.id !== group.id)
: selectedGroups.concat(group); : selectedOptions.concat(group);
onSelect(newSelectedGroups); onSelect(newSelectedGroups);
@@ -97,8 +97,8 @@ export default function SelectionPopup({
const handleConfirm = () => { const handleConfirm = () => {
if (onConfirm) { if (onConfirm) {
onConfirm( onConfirm(
selectedGroups.map(g => g.id), selectedOptions.map(g => g.id),
selectedGroups, selectedOptions,
); );
} }
onVisibleChange(false); onVisibleChange(false);
@@ -155,7 +155,7 @@ export default function SelectionPopup({
currentPage={currentPage} currentPage={currentPage}
totalPages={totalPages} totalPages={totalPages}
loading={loading} loading={loading}
selectedCount={selectedGroups.length} selectedCount={selectedOptions.length}
onPageChange={setCurrentPage} onPageChange={setCurrentPage}
onCancel={() => onVisibleChange(false)} onCancel={() => onVisibleChange(false)}
onConfirm={handleConfirm} onConfirm={handleConfirm}
@@ -172,7 +172,7 @@ export default function SelectionPopup({
{groups.map(group => ( {groups.map(group => (
<div key={group.id} className={style.groupItem}> <div key={group.id} className={style.groupItem}>
<Checkbox <Checkbox
checked={selectedGroups.some(g => g.id === group.id)} checked={selectedOptions.some(g => g.id === group.id)}
onChange={() => !readonly && handleGroupToggle(group)} onChange={() => !readonly && handleGroupToggle(group)}
disabled={readonly} disabled={readonly}
style={{ marginRight: 12 }} style={{ marginRight: 12 }}

View File

@@ -4,7 +4,7 @@ import ContentSelection from "@/components/ContentSelection";
import { ContentItem } from "@/components/ContentSelection/data"; import { ContentItem } from "@/components/ContentSelection/data";
interface ContentSelectorProps { interface ContentSelectorProps {
selectedContent: ContentItem[]; selectedOptions: ContentItem[];
onPrevious: () => void; onPrevious: () => void;
onNext: (data: { onNext: (data: {
contentGroups: string[]; contentGroups: string[];
@@ -18,7 +18,7 @@ export interface ContentSelectorRef {
} }
const ContentSelector = forwardRef<ContentSelectorRef, ContentSelectorProps>( const ContentSelector = forwardRef<ContentSelectorRef, ContentSelectorProps>(
({ selectedContent, onNext }, ref) => { ({ selectedOptions, onNext }, ref) => {
const [form] = Form.useForm(); const [form] = Form.useForm();
// 暴露方法给父组件 // 暴露方法给父组件
@@ -53,7 +53,9 @@ const ContentSelector = forwardRef<ContentSelectorRef, ContentSelectorProps>(
<Form <Form
form={form} form={form}
layout="vertical" layout="vertical"
initialValues={{ contentGroups: selectedContent.map(c => c.id) }} initialValues={{
contentGroups: selectedOptions.map(c => Number(c.id)),
}}
> >
<div style={{ marginBottom: 16 }}> <div style={{ marginBottom: 16 }}>
<h2 style={{ margin: 0, fontSize: 18, fontWeight: 600 }}> <h2 style={{ margin: 0, fontSize: 18, fontWeight: 600 }}>
@@ -73,7 +75,7 @@ const ContentSelector = forwardRef<ContentSelectorRef, ContentSelectorProps>(
]} ]}
> >
<ContentSelection <ContentSelection
selectedContent={selectedContent} selectedOptions={selectedOptions}
onSelect={handleLibrariesChange} onSelect={handleLibrariesChange}
placeholder="选择内容库" placeholder="选择内容库"
showInput={true} showInput={true}

View File

@@ -76,7 +76,7 @@ const GroupSelector = forwardRef<GroupSelectorRef, GroupSelectorProps>(
]} ]}
> >
<GroupSelection <GroupSelection
selectedGroups={selectedGroups} selectedOptions={selectedGroups}
onSelect={handleGroupSelect} onSelect={handleGroupSelect}
placeholder="选择要推送的群组" placeholder="选择要推送的群组"
readonly={false} readonly={false}

View File

@@ -0,0 +1,188 @@
import React, {
useState,
useEffect,
useImperativeHandle,
forwardRef,
} from "react";
import { Form, Select, Card } from "antd";
import request from "@/api/request";
// 京东社交媒体接口
interface JdSocialMedia {
id: string;
name: string;
[key: string]: any;
}
// 京东推广站点接口
interface JdPromotionSite {
id: string;
name: string;
[key: string]: any;
}
interface JingDongLinkProps {
defaultValues?: {
socialMediaId?: string;
promotionSiteId?: string;
};
onNext?: (values: any) => void;
onSave?: (values: any) => void;
loading?: boolean;
}
export interface JingDongLinkRef {
validate: () => Promise<boolean>;
getValues: () => any;
}
const JingDongLink = forwardRef<JingDongLinkRef, JingDongLinkProps>(
(
{
defaultValues = {
socialMediaId: undefined,
promotionSiteId: undefined,
},
onNext,
onSave,
loading = false,
},
ref,
) => {
const [form] = Form.useForm();
const [socialMediaList, setSocialMediaList] = useState<JdSocialMedia[]>([]);
const [promotionSiteList, setPromotionSiteList] = useState<
JdPromotionSite[]
>([]);
const [loadingSocialMedia, setLoadingSocialMedia] = useState(false);
const [loadingPromotionSite, setLoadingPromotionSite] = useState(false);
// 暴露方法给父组件
useImperativeHandle(ref, () => ({
validate: async () => {
try {
await form.validateFields();
return true;
} catch (error) {
console.log("JingDongLink 表单验证失败:", error);
return false;
}
},
getValues: () => {
return form.getFieldsValue();
},
}));
// 获取京东社交媒体列表
const fetchSocialMediaList = async () => {
setLoadingSocialMedia(true);
try {
const response = await request(
"/v1/workbench/getJdSocialMedia",
{},
"GET",
);
if (response && Array.isArray(response)) {
setSocialMediaList(response);
}
} catch (error) {
console.error("获取京东社交媒体列表失败:", error);
} finally {
setLoadingSocialMedia(false);
}
};
// 获取京东推广站点列表
const fetchPromotionSiteList = async (socialMediaId: string) => {
setLoadingPromotionSite(true);
try {
const response = await request(
"/v1/workbench/getJdPromotionSite",
{ socialMediaId },
"GET",
);
if (response && Array.isArray(response)) {
setPromotionSiteList(response);
}
} catch (error) {
console.error("获取京东推广站点列表失败:", error);
} finally {
setLoadingPromotionSite(false);
}
};
// 组件挂载时获取社交媒体列表
useEffect(() => {
fetchSocialMediaList();
}, []);
// 监听社交媒体选择变化
const handleSocialMediaChange = (value: string) => {
// 清空推广站点选择
form.setFieldsValue({ promotionSiteId: undefined });
setPromotionSiteList([]);
if (value) {
fetchPromotionSiteList(value);
}
};
return (
<div style={{ marginBottom: 24 }}>
<Card title="京东联盟">
<Form
form={form}
layout="vertical"
initialValues={defaultValues}
onValuesChange={(changedValues, allValues) => {
// 可以在这里处理表单值变化
}}
>
{/* 京东社交媒体选择 */}
<Form.Item label="京东联盟" style={{ marginBottom: 16 }}>
<div style={{ display: "flex", gap: 12, alignItems: "flex-end" }}>
<Form.Item
name="socialMediaId"
noStyle
rules={[{ required: true, message: "请选择社交媒体" }]}
>
<Select
placeholder="请选择社交媒体"
style={{ width: 200 }}
loading={loadingSocialMedia}
onChange={handleSocialMediaChange}
options={socialMediaList.map(item => ({
label: item.name,
value: item.id,
}))}
/>
</Form.Item>
<Form.Item
name="promotionSiteId"
noStyle
rules={[{ required: true, message: "请选择推广站点" }]}
>
<Select
placeholder="请选择推广站点"
style={{ width: 200 }}
loading={loadingPromotionSite}
disabled={!form.getFieldValue("socialMediaId")}
options={promotionSiteList.map(item => ({
label: item.name,
value: item.id,
}))}
/>
</Form.Item>
</div>
</Form.Item>
</Form>
</Card>
</div>
);
},
);
JingDongLink.displayName = "JingDongLink";
export default JingDongLink;

View File

@@ -30,5 +30,8 @@ export interface FormData {
isEnabled: boolean; isEnabled: boolean;
contentGroups: string[]; contentGroups: string[];
wechatGroups: string[]; wechatGroups: string[];
// 京东联盟相关字段
socialMediaId?: string;
promotionSiteId?: string;
[key: string]: any; [key: string]: any;
} }

View File

@@ -9,7 +9,8 @@ import GroupSelector, { GroupSelectorRef } from "./components/GroupSelector";
import ContentSelector, { import ContentSelector, {
ContentSelectorRef, ContentSelectorRef,
} from "./components/ContentSelector"; } from "./components/ContentSelector";
import type { ContentLibrary, FormData } from "./index.data"; import JingDongLink, { JingDongLinkRef } from "./components/JingDongLink";
import type { FormData } from "./index.data";
import NavCommon from "@/components/NavCommon"; import NavCommon from "@/components/NavCommon";
import { GroupSelectionItem } from "@/components/GroupSelection/data"; import { GroupSelectionItem } from "@/components/GroupSelection/data";
import { ContentItem } from "@/components/ContentSelection/data"; import { ContentItem } from "@/components/ContentSelection/data";
@@ -50,6 +51,7 @@ const NewGroupPush: React.FC = () => {
const basicSettingsRef = useRef<BasicSettingsRef>(null); const basicSettingsRef = useRef<BasicSettingsRef>(null);
const groupSelectorRef = useRef<GroupSelectorRef>(null); const groupSelectorRef = useRef<GroupSelectorRef>(null);
const contentSelectorRef = useRef<ContentSelectorRef>(null); const contentSelectorRef = useRef<ContentSelectorRef>(null);
const jingDongLinkRef = useRef<JingDongLinkRef>(null);
useEffect(() => { useEffect(() => {
if (!id) return; if (!id) return;
@@ -85,14 +87,18 @@ const NewGroupPush: React.FC = () => {
window.alert("请输入任务名称"); window.alert("请输入任务名称");
return; return;
} }
if (formData.groups.length === 0) { if (formData.wechatGroups.length === 0) {
window.alert("请选择至少一个社群"); window.alert("请选择至少一个社群");
return; return;
} }
if (formData.contentLibraries.length === 0) { if (formData.contentGroups.length === 0) {
window.alert("请选择至少一个内容库"); window.alert("请选择至少一个内容库");
return; return;
} }
// 获取京东联盟数据
const jingDongLinkValues = jingDongLinkRef.current?.getValues();
setLoading(true); setLoading(true);
try { try {
const apiData = { const apiData = {
@@ -106,8 +112,11 @@ const NewGroupPush: React.FC = () => {
isLoopPush: formData.isLoopPush, isLoopPush: formData.isLoopPush,
isImmediatePush: formData.isImmediatePush, isImmediatePush: formData.isImmediatePush,
isEnabled: formData.isEnabled, isEnabled: formData.isEnabled,
targetGroups: formData.groups.map(g => g.name), targetGroups: formData.wechatGroups,
contentLibraries: formData.contentLibraries.map(c => c.name), contentLibraries: formData.contentGroups,
// 京东联盟数据
socialMediaId: jingDongLinkValues?.socialMediaId,
promotionSiteId: jingDongLinkValues?.promotionSiteId,
pushMode: formData.isImmediatePush pushMode: formData.isImmediatePush
? ("immediate" as const) ? ("immediate" as const)
: ("scheduled" as const), : ("scheduled" as const),
@@ -239,15 +248,13 @@ const NewGroupPush: React.FC = () => {
{currentStep === 3 && ( {currentStep === 3 && (
<ContentSelector <ContentSelector
ref={contentSelectorRef} ref={contentSelectorRef}
selectedContent={contentGroupsOptions} selectedOptions={contentGroupsOptions}
onPrevious={() => setCurrentStep(2)} onPrevious={() => setCurrentStep(2)}
onNext={handleLibrariesChange} onNext={handleLibrariesChange}
/> />
)} )}
{currentStep === 4 && ( {currentStep === 4 && (
<div style={{ padding: 32, textAlign: "center", color: "#888" }}> <JingDongLink ref={jingDongLinkRef} loading={loading} />
</div>
)} )}
</div> </div>
</div> </div>

View File

@@ -5,7 +5,7 @@ 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 StepIndicator from "@/components/StepIndicator"; import StepIndicator from "@/components/StepIndicator";
import { import {
createMomentsSync, createMomentsSync,
@@ -15,6 +15,8 @@ import {
import DeviceSelection from "@/components/DeviceSelection"; import DeviceSelection from "@/components/DeviceSelection";
import ContentSelection from "@/components/ContentSelection"; import ContentSelection from "@/components/ContentSelection";
import NavCommon from "@/components/NavCommon"; import NavCommon from "@/components/NavCommon";
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
import { ContentItem } from "@/components/ContentSelection/data";
const steps = [ const steps = [
{ id: 1, title: "基础设置", subtitle: "基础设置" }, { id: 1, title: "基础设置", subtitle: "基础设置" },
@@ -31,8 +33,8 @@ const defaultForm = {
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[], deveiceGroups: [] as any[],
selectedLibraries: [] as any[], // 存完整内容库对象数组 contentGroups: [] as any[], // 存完整内容库对象数组
contentTypes: ["text", "image", "video"], contentTypes: ["text", "image", "video"],
targetTags: [] as string[], targetTags: [] as string[],
filterKeywords: [] as string[], filterKeywords: [] as string[],
@@ -45,6 +47,12 @@ const NewMomentsSync: React.FC = () => {
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 [deveiceGroupsOptions, setSelectedDevicesOptions] = useState<
DeviceSelectionItem[]
>([]);
const [contentGroupsOptions, setContentGroupsOptions] = useState<
ContentItem[]
>([]);
// 获取详情(编辑) // 获取详情(编辑)
const fetchDetail = useCallback(async () => { const fetchDetail = useCallback(async () => {
@@ -62,13 +70,15 @@ const NewMomentsSync: React.FC = () => {
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 || [], deveiceGroups: res.config?.devices || [],
// 关键用id字符串数组回填 // 关键用id字符串数组回填
selectedLibraries: res.config?.contentLibraries || [], // 直接用对象数组 contentGroups: res.config?.contentGroups || [], // 直接用对象数组
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 || [],
}); });
setSelectedDevicesOptions(res.config?.deveiceGroupsOptions || []);
setContentGroupsOptions(res.config?.contentGroupsOptions || []);
} }
} catch { } catch {
message.error("获取详情失败"); message.error("获取详情失败");
@@ -98,18 +108,25 @@ const NewMomentsSync: React.FC = () => {
syncType: type === "business" ? 1 : 2, syncType: type === "business" ? 1 : 2,
})); }));
}; };
const handleDevicesChange = (devices: DeviceSelectionItem[]) => {
setSelectedDevicesOptions(devices);
updateForm({ deveiceGroups: devices.map(d => d.id) });
};
const handleContentChange = (libs: ContentItem[]) => {
setContentGroupsOptions(libs);
updateForm({ contentGroups: libs });
};
// 提交 // 提交
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.deveiceGroups.length === 0) {
message.error("请选择设备"); message.error("请选择设备");
return; return;
} }
if (formData.selectedLibraries.length === 0) { if (formData.contentGroups.length === 0) {
message.error("请选择内容库"); message.error("请选择内容库");
return; return;
} }
@@ -117,8 +134,8 @@ const NewMomentsSync: React.FC = () => {
try { try {
const params = { const params = {
name: formData.taskName, name: formData.taskName,
devices: formData.selectedDevices, devices: formData.deveiceGroups,
contentLibraries: formData.selectedLibraries.map((lib: any) => lib.id), contentLibraries: formData.contentGroups.map((lib: any) => lib.id),
syncInterval: formData.syncInterval, syncInterval: formData.syncInterval,
syncCount: formData.syncCount, syncCount: formData.syncCount,
syncType: formData.syncType, // 账号类型真实传参 syncType: formData.syncType, // 账号类型真实传参
@@ -243,8 +260,8 @@ const NewMomentsSync: React.FC = () => {
<div className={style.formItem}> <div className={style.formItem}>
<div className={style.formLabel}></div> <div className={style.formLabel}></div>
<DeviceSelection <DeviceSelection
selectedOptions={formData.selectedDevices} selectedOptions={deveiceGroupsOptions}
onSelect={devices => updateForm({ selectedDevices: devices })} onSelect={handleDevicesChange}
placeholder="请选择设备" placeholder="请选择设备"
showSelectedList={true} showSelectedList={true}
selectedListMaxHeight={200} selectedListMaxHeight={200}
@@ -259,15 +276,15 @@ const NewMomentsSync: React.FC = () => {
<div className={style.formItem}> <div className={style.formItem}>
<div className={style.formLabel}></div> <div className={style.formLabel}></div>
<ContentSelection <ContentSelection
selectedOptions={formData.selectedLibraries} selectedOptions={contentGroupsOptions}
onSelect={libs => updateForm({ selectedLibraries: libs })} onSelect={handleContentChange}
placeholder="请选择内容库" placeholder="请选择内容库"
showSelectedList={true} showSelectedList={true}
selectedListMaxHeight={200} selectedListMaxHeight={200}
/> />
{formData.selectedLibraries.length > 0 && ( {formData.contentGroups.length > 0 && (
<div className={style.selectedTip}> <div className={style.selectedTip}>
: {formData.selectedLibraries.length} : {formData.contentGroups.length}
</div> </div>
)} )}
</div> </div>