Merge branch 'yongpxu-dev' into yongpxu-dev4

This commit is contained in:
笔记本里的永平
2025-07-23 17:38:01 +08:00
7 changed files with 464 additions and 387 deletions

View File

@@ -21,12 +21,7 @@ export async function uploadFile(
}, },
}); });
// 检查响应结果 return res.url;
if (res?.code === 200 && res?.data?.url) {
return res.data.url;
} else {
throw new Error(res?.msg || "文件上传失败");
}
} catch (e: any) { } catch (e: any) {
throw new Error(e?.message || "文件上传失败"); throw new Error(e?.message || "文件上传失败");
} }

View File

@@ -11,7 +11,7 @@ const StepIndicator: React.FC<StepIndicatorProps> = ({
steps, steps,
}) => { }) => {
return ( return (
<div style={{ marginBottom: 24, overflowX: "auto" }}> <div style={{ overflowX: "auto", padding: "30px 0px", background: "#fff" }}>
<Steps current={currentStep - 1}> <Steps current={currentStep - 1}>
{steps.map((step, idx) => ( {steps.map((step, idx) => (
<Steps.Step <Steps.Step

View File

@@ -1,8 +1,7 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { message } from "antd"; import { message } from "antd";
import { NavBar } from "antd-mobile"; import NavCommon from "@/components/NavCommon";
import { ArrowLeftOutlined } from "@ant-design/icons";
import BasicSettings from "./steps/BasicSettings"; import BasicSettings from "./steps/BasicSettings";
import FriendRequestSettings from "./steps/FriendRequestSettings"; import FriendRequestSettings from "./steps/FriendRequestSettings";
import MessageSettings from "./steps/MessageSettings"; import MessageSettings from "./steps/MessageSettings";
@@ -205,25 +204,8 @@ export default function NewPlan() {
<Layout <Layout
header={ header={
<> <>
<NavBar <NavCommon title={isEdit ? "编辑场景计划" : "新建场景计划"} />
back={null} <StepIndicator currentStep={currentStep} steps={steps} />
style={{ background: "#fff" }}
left={
<div className="nav-title">
<ArrowLeftOutlined
twoToneColor="#1677ff"
onClick={() => router(-1)}
/>
</div>
}
>
<span className="nav-title">
{isEdit ? "编辑朋友圈同步" : "新建朋友圈同步"}
</span>
</NavBar>
<div className="px-4 py-6">
<StepIndicator currentStep={currentStep} steps={steps} />
</div>
</> </>
} }
> >

View File

@@ -1,17 +1,6 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef } from "react";
import { import { Form, Input, Button, Tag, Switch, Modal, Spin } from "antd";
Form, import { Button as ButtonMobile } from "antd-mobile";
Input,
Button,
Tag,
Switch,
// Upload,
Modal,
Alert,
Row,
Col,
message,
} from "antd";
import { import {
PlusOutlined, PlusOutlined,
EyeOutlined, EyeOutlined,
@@ -21,6 +10,7 @@ import {
CheckOutlined, CheckOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { uploadFile } from "@/api/common"; import { uploadFile } from "@/api/common";
import styles from "./base.module.scss";
interface BasicSettingsProps { interface BasicSettingsProps {
isEdit: boolean; isEdit: boolean;
@@ -424,31 +414,44 @@ const BasicSettings: React.FC<BasicSettingsProps> = ({
formData.scenario !== 1 ? { display: "none" } : { display: "block" }; formData.scenario !== 1 ? { display: "none" } : { display: "block" };
return ( return (
<div> <div className={styles["basic-container"]}>
{/* 场景选择区块 */} {/* 场景选择区块 */}
{sceneLoading ? ( <div className={styles["basic-scene-select"]}>
<div style={{ padding: 16, textAlign: "center" }}>...</div> {sceneLoading ? (
) : ( <div
<Row gutter={20} className="grid grid-cols-3 gap-4"> style={{
{sceneList.map((scene) => ( display: "flex",
<Col span={8} key={scene.id}> justifyContent: "center",
<Button alignItems: "center",
type="primary" minHeight: 80,
onClick={() => handleScenarioSelect(scene.id)} }}
className={`w-full ${formData.scenario === scene.id ? "bg-blue-500" : "bg-gray-200"}`} >
> <Spin size="large"></Spin>
{scene.name.replace("获客", "")} </div>
</Button> ) : (
</Col> <div className={styles["basic-scene-grid"]}>
))} {sceneList.map((scene) => {
</Row> const selected = formData.scenario === scene.id;
)} return (
<button
key={scene.id}
onClick={() => handleScenarioSelect(scene.id)}
className={
styles["basic-scene-btn"] +
(selected ? " " + styles.selected : "")
}
>
{scene.name.replace("获客", "")}
</button>
);
})}
</div>
)}
</div>
{/* 计划名称输入区 */} {/* 计划名称输入区 */}
<div className="mb-4"></div> <div className={styles["basic-label"]}></div>
<div className="border p-2 mb-4"> <div className={styles["basic-input-block"]}>
<Input <Input
className="w-full"
value={formData.name} value={formData.name}
onChange={(e) => onChange={(e) =>
onChange({ ...formData, name: String(e.target.value) }) onChange({ ...formData, name: String(e.target.value) })
@@ -456,17 +459,16 @@ const BasicSettings: React.FC<BasicSettingsProps> = ({
placeholder="请输入计划名称" placeholder="请输入计划名称"
/> />
</div> </div>
<div className={styles["basic-label"]}></div>
<div className="mb-4"></div>
{/* 标签选择区块 */} {/* 标签选择区块 */}
{formData.scenario && ( {formData.scenario && (
<div className="flex pb-4" style={{ flexWrap: "wrap", gap: 8 }}> <div className={styles["basic-tag-list"]}>
{(currentScene?.scenarioTags || []).map((tag: string) => ( {(currentScene?.scenarioTags || []).map((tag: string) => (
<Tag <Tag
key={tag} key={tag}
color={selectedScenarioTags.includes(tag) ? "blue" : "default"} color={selectedScenarioTags.includes(tag) ? "blue" : "default"}
onClick={() => handleScenarioTagToggle(tag)} onClick={() => handleScenarioTagToggle(tag)}
style={{ marginBottom: 4 }} className={styles["basic-tag-item"]}
> >
{tag} {tag}
</Tag> </Tag>
@@ -477,9 +479,9 @@ const BasicSettings: React.FC<BasicSettingsProps> = ({
key={tag.id} key={tag.id}
color={selectedScenarioTags.includes(tag.id) ? "blue" : "default"} color={selectedScenarioTags.includes(tag.id) ? "blue" : "default"}
onClick={() => handleScenarioTagToggle(tag.id)} onClick={() => handleScenarioTagToggle(tag.id)}
style={{ marginBottom: 4 }}
closable closable
onClose={() => handleRemoveCustomTag(tag.id)} onClose={() => handleRemoveCustomTag(tag.id)}
className={styles["basic-tag-item"]}
> >
{tag.name} {tag.name}
</Tag> </Tag>
@@ -487,49 +489,33 @@ const BasicSettings: React.FC<BasicSettingsProps> = ({
</div> </div>
)} )}
{/* 自定义标签输入区 */} {/* 自定义标签输入区 */}
<div className="flex p" style={{ gap: 8 }}> <div className={styles["basic-custom-tag-input"]}>
<div className="border p-2 flex-1"> <Input
<Input type="text"
type="text" value={customTagInput}
value={customTagInput} onChange={(e) => setCustomTagInput(e.target.value)}
onChange={(e) => setCustomTagInput(e.target.value)} placeholder="添加自定义标签"
placeholder="添加自定义标签" />
className="w-full" <Button type="primary" onClick={handleAddCustomTag}>
/>
</div> </Button>
<div className="pt-1">
<Button type="primary" onClick={handleAddCustomTag}>
</Button>
</div>
</div> </div>
{/* 输入获客成功提示 */} {/* 输入获客成功提示 */}
<div className="flex pt-4"> <div className={styles["basic-success-tip"]}>
<div className="border p-2 flex-1"> <Input
<Input type="text"
type="text" value={tips}
value={tips} onChange={(e) => {
onChange={(e) => { setTips(e.target.value);
setTips(e.target.value); onChange({ ...formData, tips: e.target.value });
onChange({ ...formData, tips: e.target.value });
}}
placeholder="请输入获客成功提示"
className="w-full"
/>
</div>
</div>
{/* 选素材 */}
<div className="my-4" style={openPoster}>
<div className="mb-4"></div>
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(3, 1fr)",
gap: 12,
}} }}
> placeholder="请输入获客成功提示"
/>
</div>
{/* 选素材 */}
<div className={styles["basic-materials"]} style={openPoster}>
<div className={styles["basic-label"]}></div>
<div className={styles["basic-materials-grid"]}>
{[...materials, ...customPosters].map((material) => { {[...materials, ...customPosters].map((material) => {
const isSelected = selectedMaterials.some( const isSelected = selectedMaterials.some(
(m) => m.id === material.id (m) => m.id === material.id
@@ -538,35 +524,15 @@ const BasicSettings: React.FC<BasicSettingsProps> = ({
return ( return (
<div <div
key={material.id} key={material.id}
style={{ className={
border: isSelected ? "2px solid #1890ff" : "2px solid #eee", styles["basic-material-card"] +
borderRadius: 8, (isSelected ? " " + styles.selected : "")
padding: 6, }
cursor: "pointer",
background: isSelected ? "#e6f7ff" : "#fff",
transition: "border 0.2s",
textAlign: "center",
position: "relative",
height: 180 + 12, // 图片高度180+上下padding
overflow: "hidden",
minHeight: 192,
}}
onClick={() => handleMaterialSelect(material)} onClick={() => handleMaterialSelect(material)}
> >
{/* 预览按钮:自定义海报在左上,内置海报在右上 */} {/* 预览按钮:自定义海报在左上,内置海报在右上 */}
<Button <span
style={{ className={styles["basic-material-preview"]}
position: "absolute",
top: 8,
left: isCustom ? 8 : "auto",
right: isCustom ? "auto" : 8,
background: "rgba(0,0,0,0.5)",
border: "none",
borderRadius: "50%",
padding: 2,
zIndex: 2,
cursor: "pointer",
}}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
handlePreviewImage(material.preview); handlePreviewImage(material.preview);
@@ -575,7 +541,7 @@ const BasicSettings: React.FC<BasicSettingsProps> = ({
<EyeOutlined <EyeOutlined
style={{ color: "#fff", width: 18, height: 18 }} style={{ color: "#fff", width: 18, height: 18 }}
/> />
</Button> </span>
{/* 删除自定义海报按钮 */} {/* 删除自定义海报按钮 */}
{isCustom && ( {isCustom && (
<Button <Button
@@ -607,31 +573,9 @@ const BasicSettings: React.FC<BasicSettingsProps> = ({
<img <img
src={material.preview} src={material.preview}
alt={material.name} alt={material.name}
style={{ className={styles["basic-material-img"]}
width: 100,
height: 180,
objectFit: "cover",
borderRadius: 4,
marginBottom: 0,
display: "block",
}}
/> />
<div <div className={styles["basic-material-name"]}>
style={{
position: "absolute",
left: 0,
bottom: 0,
width: "100%",
background: "rgba(0,0,0,0.5)",
color: "#fff",
fontSize: 14,
padding: "4px 0",
borderBottomLeftRadius: 4,
borderBottomRightRadius: 4,
textAlign: "center",
zIndex: 3,
}}
>
{material.name} {material.name}
</div> </div>
</div> </div>
@@ -639,19 +583,7 @@ const BasicSettings: React.FC<BasicSettingsProps> = ({
})} })}
{/* 添加海报卡片 */} {/* 添加海报卡片 */}
<div <div
style={{ className={styles["basic-add-material"]}
border: "2px dashed #bbb",
borderRadius: 8,
padding: 6,
cursor: "pointer",
background: "#fafbfc",
textAlign: "center",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
height: 190,
}}
onClick={() => uploadInputRef.current?.click()} onClick={() => uploadInputRef.current?.click()}
> >
<span style={{ fontSize: 36, color: "#bbb", marginBottom: 8 }}> <span style={{ fontSize: 36, color: "#bbb", marginBottom: 8 }}>
@@ -666,7 +598,6 @@ const BasicSettings: React.FC<BasicSettingsProps> = ({
onChange={async (e) => { onChange={async (e) => {
const file = e.target.files?.[0]; const file = e.target.files?.[0];
if (file) { if (file) {
// 直接上传
try { try {
const url = await uploadFile(file); const url = await uploadFile(file);
const newPoster = { const newPoster = {
@@ -675,9 +606,14 @@ const BasicSettings: React.FC<BasicSettingsProps> = ({
type: "poster", type: "poster",
preview: url, preview: url,
}; };
console.log(newPoster);
setCustomPosters((prev) => [...prev, newPoster]); setCustomPosters((prev) => [...prev, newPoster]);
setSelectedMaterials([newPoster]);
onChange({ ...formData, materials: [newPoster] });
} catch (err) { } catch (err) {
// 可加toast提示 // 可加toast提示
log;
} }
e.target.value = ""; e.target.value = "";
} }
@@ -705,11 +641,10 @@ const BasicSettings: React.FC<BasicSettingsProps> = ({
</Modal> </Modal>
</div> </div>
{/* 订单导入区块优化 */} {/* 订单导入区块优化 */}
<div style={openOrder} className="my-4"> <div className={styles["basic-order-upload"]} style={openOrder}>
<div style={{ fontWeight: 500, marginBottom: 8 }}></div> <div className={styles["basic-order-upload-label"]}></div>
<div style={{ display: "flex", gap: 12, marginBottom: 4 }}> <div className={styles["basic-order-upload-actions"]}>
<Button <Button
type="button"
style={{ display: "flex", alignItems: "center", gap: 4 }} style={{ display: "flex", alignItems: "center", gap: 4 }}
onClick={handleDownloadTemplate} onClick={handleDownloadTemplate}
> >
@@ -741,78 +676,68 @@ const BasicSettings: React.FC<BasicSettingsProps> = ({
/> />
</Button> </Button>
</div> </div>
<div style={{ color: "#888", fontSize: 13, marginBottom: 8 }}> <div className={styles["basic-order-upload-tip"]}>
CSVExcel CSVExcel
</div> </div>
</div> </div>
{/* 电话获客设置区块,仅在选择电话获客场景时显示 */} {/* 电话获客设置区块,仅在选择电话获客场景时显示 */}
{formData.scenario === 5 && ( {formData.scenario === 5 && (
<div style={{ margin: "16px 0" }}> <div className={styles["basic-phone-settings"]}>
<div <div style={{ fontWeight: 600, fontSize: 16, marginBottom: 16 }}>
style={{
background: "#f7f8fa", </div>
borderRadius: 10, <div style={{ display: "flex", flexDirection: "column", gap: 18 }}>
padding: 20, <div
boxShadow: "0 2px 8px rgba(0,0,0,0.03)", style={{
marginBottom: 12, display: "flex",
}} alignItems: "center",
> justifyContent: "space-between",
<div style={{ fontWeight: 600, fontSize: 16, marginBottom: 16 }}> }}
>
<span></span>
<Switch
checked={phoneSettings.autoAdd}
onChange={(v) =>
setPhoneSettings((s) => ({ ...s, autoAdd: v }))
}
/>
</div> </div>
<div style={{ display: "flex", flexDirection: "column", gap: 18 }}> <div
<div style={{
style={{ display: "flex",
display: "flex", alignItems: "center",
alignItems: "center", justifyContent: "space-between",
justifyContent: "space-between", }}
}} >
> <span></span>
<span></span> <Switch
<Switch checked={phoneSettings.speechToText}
checked={phoneSettings.autoAdd} onChange={(v) =>
onChange={(v) => setPhoneSettings((s) => ({ ...s, speechToText: v }))
setPhoneSettings((s) => ({ ...s, autoAdd: v })) }
} />
/> </div>
</div> <div
<div style={{
style={{ display: "flex",
display: "flex", alignItems: "center",
alignItems: "center", justifyContent: "space-between",
justifyContent: "space-between", }}
}} >
> <span></span>
<span></span> <Switch
<Switch checked={phoneSettings.questionExtraction}
checked={phoneSettings.speechToText} onChange={(v) =>
onChange={(v) => setPhoneSettings((s) => ({ ...s, questionExtraction: v }))
setPhoneSettings((s) => ({ ...s, speechToText: v })) }
} />
/>
</div>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
}}
>
<span></span>
<Switch
checked={phoneSettings.questionExtraction}
onChange={(v) =>
setPhoneSettings((s) => ({ ...s, questionExtraction: v }))
}
/>
</div>
</div> </div>
</div> </div>
</div> </div>
)} )}
{/* 微信群设置区块,仅在选择微信群场景时显示 */} {/* 微信群设置区块,仅在选择微信群场景时显示 */}
{formData.scenario === 7 && ( {formData.scenario === 7 && (
<div style={{ margin: "16px 0" }}> <div className={styles["basic-wechat-group"]}>
<div style={{ marginBottom: 8 }}> <div style={{ marginBottom: 8 }}>
<Input <Input
value={weixinqunName} value={weixinqunName}
@@ -833,24 +758,19 @@ const BasicSettings: React.FC<BasicSettingsProps> = ({
</div> </div>
</div> </div>
)} )}
<div className={styles["basic-footer-switch"]}>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
margin: "16px 0",
}}
>
<span></span> <span></span>
<Switch <Switch
checked={formData.enabled} checked={formData.enabled}
onChange={(value) => onChange({ ...formData, enabled: value })} onChange={(value) => onChange({ ...formData, enabled: value })}
/> />
</div> </div>
<Button className="mt-4" type="primary" onClick={onNext}>
<div className={styles["basic-footer-switch"]}>
</Button> <ButtonMobile block color="primary" onClick={onNext}>
</ButtonMobile>
</div>
</div> </div>
); );
}; };

View File

@@ -13,6 +13,7 @@ import {
} from "antd"; } from "antd";
import { QuestionCircleOutlined, MessageOutlined } from "@ant-design/icons"; import { QuestionCircleOutlined, MessageOutlined } from "@ant-design/icons";
import DeviceSelection from "@/components/DeviceSelection"; import DeviceSelection from "@/components/DeviceSelection";
import Layout from "@/components/Layout/Layout";
interface FriendRequestSettingsProps { interface FriendRequestSettingsProps {
formData: any; formData: any;
@@ -97,143 +98,151 @@ const FriendRequestSettings: React.FC<FriendRequestSettingsProps> = ({
return ( return (
<> <>
<div className="space-y-6"> <Layout
<div> footer={
<span className="font-medium text-base"></span> <div className="p-4 border-t bg-white">
<div className="mt-2"> <div className="flex justify-between">
<DeviceSelection <Button onClick={onPrev}></Button>
selectedDevices={selectedDevices.map((d) => d.id)} <Button type="primary" onClick={handleNext}>
onSelect={(deviceIds) => {
const newSelectedDevices = deviceIds.map((id) => ({ </Button>
id, </div>
name: `设备 ${id}`, </div>
status: "online", }
})); >
setSelectedDevices(newSelectedDevices); <div className="p-4 space-y-6">
onChange({ ...formData, device: deviceIds }); <div>
}} <span className="font-medium text-base"></span>
placeholder="选择设备" <div className="mt-2">
/> <DeviceSelection
selectedDevices={selectedDevices.map((d) => d.id)}
onSelect={(deviceIds) => {
const newSelectedDevices = deviceIds.map((id) => ({
id,
name: `设备 ${id}`,
status: "online",
}));
setSelectedDevices(newSelectedDevices);
onChange({ ...formData, device: deviceIds });
}}
placeholder="选择设备"
/>
</div>
</div> </div>
</div>
<div className="mb-4"> <div className="mb-4">
<div className="flex items-center space-x-2 mb-1 relative"> <div className="flex items-center space-x-2 mb-1 relative">
<span className="font-medium text-base"></span> <span className="font-medium text-base"></span>
<span <span
className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-gray-200 text-gray-500 text-xs cursor-pointer hover:bg-gray-300 transition-colors" className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-gray-200 text-gray-500 text-xs cursor-pointer hover:bg-gray-300 transition-colors"
onMouseEnter={() => setShowRemarkTip(true)} onMouseEnter={() => setShowRemarkTip(true)}
onMouseLeave={() => setShowRemarkTip(false)} onMouseLeave={() => setShowRemarkTip(false)}
onClick={() => setShowRemarkTip((v) => !v)} onClick={() => setShowRemarkTip((v) => !v)}
> >
? ?
</span> </span>
{showRemarkTip && ( {showRemarkTip && (
<div className="absolute left-24 top-0 z-20 w-64 p-3 bg-white border border-gray-200 rounded shadow-lg text-sm text-gray-700"> <div className="absolute left-24 top-0 z-20 w-64 p-3 bg-white border border-gray-200 rounded shadow-lg text-sm text-gray-700">
<div></div> <div></div>
<div className="mt-2 text-xs text-gray-500"></div> <div className="mt-2 text-xs text-gray-500">
<div className="mt-1 text-blue-600">
{formData.remarkType === "phone" && </div>
`138****1234+${getScenarioTitle()}`} <div className="mt-1 text-blue-600">
{formData.remarkType === "nickname" && {formData.remarkType === "phone" &&
`小红书用户2851+${getScenarioTitle()}`} `138****1234+${getScenarioTitle()}`}
{formData.remarkType === "source" && {formData.remarkType === "nickname" &&
`抖音直播+${getScenarioTitle()}`} `小红书用户2851+${getScenarioTitle()}`}
{formData.remarkType === "source" &&
`抖音直播+${getScenarioTitle()}`}
</div>
</div> </div>
</div> )}
)} </div>
</div> <Select
<Select value={formData.remarkType || "phone"}
value={formData.remarkType || "phone"} onChange={(value) => onChange({ ...formData, remarkType: value })}
onChange={(value) => onChange({ ...formData, remarkType: value })} className="w-full mt-2"
className="w-full mt-2"
>
{remarkTypes.map((type) => (
<Select.Option key={type.value} value={type.value}>
{type.label}
</Select.Option>
))}
</Select>
</div>
<div>
<div className="flex items-center justify-between">
<span className="font-medium text-base"></span>
<Button
onClick={() => setIsTemplateDialogOpen(true)}
className="text-blue-500"
> >
<MessageOutlined className="h-4 w-4 mr-2" /> {remarkTypes.map((type) => (
<Select.Option key={type.value} value={type.value}>
</Button> {type.label}
</Select.Option>
))}
</Select>
</div> </div>
<Input
value={formData.greeting}
onChange={(e) =>
onChange({ ...formData, greeting: e.target.value })
}
placeholder="请输入招呼语"
className="mt-2"
/>
</div>
<div> <div>
<span className="font-medium text-base"></span> <div className="flex items-center justify-between">
<div className="flex items-center space-x-2 mt-2"> <span className="font-medium text-base"></span>
<Button
onClick={() => setIsTemplateDialogOpen(true)}
className="text-blue-500"
>
<MessageOutlined className="h-4 w-4 mr-2" />
</Button>
</div>
<Input <Input
type="number" value={formData.greeting}
value={formData.addFriendInterval || 1}
onChange={(e) => onChange={(e) =>
onChange({ onChange({ ...formData, greeting: e.target.value })
...formData,
addFriendInterval: Number(e.target.value),
})
} }
/> placeholder="请输入招呼语"
<div className="w-10"></div> className="mt-2"
</div>
</div>
<div>
<span className="font-medium text-base"></span>
<div className="flex items-center space-x-2 mt-2">
<Input
type="time"
value={formData.addFriendTimeStart || "09:00"}
onChange={(e) =>
onChange({ ...formData, addFriendTimeStart: e.target.value })
}
className="w-32"
/>
<span></span>
<Input
type="time"
value={formData.addFriendTimeEnd || "18:00"}
onChange={(e) =>
onChange({ ...formData, addFriendTimeEnd: e.target.value })
}
className="w-32"
/> />
</div> </div>
</div>
{hasWarnings && ( <div>
<Alert <span className="font-medium text-base"></span>
message="警告" <div className="flex items-center space-x-2 mt-2">
description="您有未完成的设置项,建议完善后再进入下一步。" <Input
type="warning" type="number"
showIcon value={formData.addFriendInterval || 1}
className="bg-amber-50 border-amber-200" onChange={(e) =>
/> onChange({
)} ...formData,
addFriendInterval: Number(e.target.value),
})
}
/>
<div className="w-10"></div>
</div>
</div>
<div className="flex justify-between pt-4"> <div>
<Button onClick={onPrev}></Button> <span className="font-medium text-base"></span>
<Button type="primary" onClick={handleNext}> <div className="flex items-center space-x-2 mt-2">
<Input
</Button> type="time"
value={formData.addFriendTimeStart || "09:00"}
onChange={(e) =>
onChange({ ...formData, addFriendTimeStart: e.target.value })
}
className="w-32"
/>
<span></span>
<Input
type="time"
value={formData.addFriendTimeEnd || "18:00"}
onChange={(e) =>
onChange({ ...formData, addFriendTimeEnd: e.target.value })
}
className="w-32"
/>
</div>
</div>
{hasWarnings && (
<Alert
message="警告"
description="您有未完成的设置项,建议完善后再进入下一步。"
type="warning"
showIcon
className="bg-amber-50 border-amber-200"
/>
)}
</div> </div>
</div> </Layout>
<Modal <Modal
open={isTemplateDialogOpen} open={isTemplateDialogOpen}

View File

@@ -13,6 +13,7 @@ import {
LinkOutlined, LinkOutlined,
TeamOutlined, TeamOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import Layout from "@/components/Layout/Layout";
interface MessageContent { interface MessageContent {
id: string; id: string;
@@ -533,23 +534,29 @@ const MessageSettings: React.FC<MessageSettingsProps> = ({
return ( return (
<> <>
<div className="space-y-6"> <Layout
<div className="flex items-center justify-between"> footer={
<h2 className="text-lg font-semibold"></h2> <div className="p-4 border-t bg-white">
<Button onClick={() => setIsAddDayPlanOpen(true)}> <div className="flex justify-between">
<PlusOutlined className="h-4 w-4" /> <Button onClick={onPrev}></Button>
</Button> <Button type="primary" onClick={onNext}>
</div>
</Button>
</div>
</div>
}
>
<div className="p-4 space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold"></h2>
<Button onClick={() => setIsAddDayPlanOpen(true)}>
<PlusOutlined className="h-4 w-4" />
</Button>
</div>
<Tabs defaultActiveKey="0" items={items} /> <Tabs defaultActiveKey="0" items={items} />
<div className="flex justify-between pt-4">
<Button onClick={onPrev}></Button>
<Button type="primary" onClick={onNext}>
</Button>
</div> </div>
</div> </Layout>
{/* 添加天数计划弹窗 */} {/* 添加天数计划弹窗 */}
<Modal <Modal

View File

@@ -0,0 +1,164 @@
.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;
}
.basic-success-tip {
display: flex;
padding-top: 16px;
}
.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;
}