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 {
id: number;
name: string;
memo: string;
imei: string;
wechatId: string;
status: "online" | "offline";

View File

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

View File

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

View File

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

View File

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

View File

@@ -76,7 +76,7 @@ const GroupSelector = forwardRef<GroupSelectorRef, GroupSelectorProps>(
]}
>
<GroupSelection
selectedGroups={selectedGroups}
selectedOptions={selectedGroups}
onSelect={handleGroupSelect}
placeholder="选择要推送的群组"
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;
contentGroups: string[];
wechatGroups: string[];
// 京东联盟相关字段
socialMediaId?: string;
promotionSiteId?: string;
[key: string]: any;
}

View File

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

View File

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