Merge branch 'yongpxu-dev2' into yongxu-dev3

This commit is contained in:
笔记本里的永平
2025-07-23 19:20:10 +08:00
16 changed files with 927 additions and 310 deletions

View File

@@ -1,28 +1,27 @@
import request from "./request";
/**
* 通用文件上传方法(支持图片、文件)
* @param {File} file - 要上传的文件对象
* @param {string} [uploadUrl='/v1/attachment/upload'] - 上传接口地址
* @returns {Promise<string>} - 上传成功后返回文件url
*/
export async function uploadFile(
file: File,
uploadUrl: string = "/v1/attachment/upload"
): Promise<string> {
try {
// 创建 FormData 对象用于文件上传
const formData = new FormData();
formData.append("file", file);
// 使用 request 方法上传文件,设置正确的 Content-Type
const res = await request(uploadUrl, formData, "POST", {
headers: {
"Content-Type": "multipart/form-data",
},
});
return res.url;
} catch (e: any) {
throw new Error(e?.message || "文件上传失败");
}
}
import request from "./request";
/**
* 通用文件上传方法(支持图片、文件)
* @param {File} file - 要上传的文件对象
* @param {string} [uploadUrl='/v1/attachment/upload'] - 上传接口地址
* @returns {Promise<string>} - 上传成功后返回文件url
*/
export async function uploadFile(
file: File,
uploadUrl: string = "/v1/attachment/upload"
): Promise<string> {
try {
// 创建 FormData 对象用于文件上传
const formData = new FormData();
formData.append("file", file);
// 使用 request 方法上传文件,设置正确的 Content-Type
const res = await request(uploadUrl, formData, "POST", {
headers: {
"Content-Type": "multipart/form-data",
},
});
return res.url;
} catch (e: any) {
throw new Error(e?.message || "文件上传失败");
}
}

View File

@@ -150,9 +150,18 @@ export default function FriendSelection({
};
// 获取已选好友详细信息
const selectedFriendObjs = friends.filter((friend) =>
selectedFriends.includes(friend.id)
);
const selectedFriendObjs = [
...friends.filter((friend) => selectedFriends.includes(friend.id)),
...selectedFriends
.filter((id) => !friends.some((friend) => friend.id === id))
.map((id) => ({
id,
nickname: id,
wechatId: id,
avatar: "",
customer: "",
})),
];
// 删除已选好友
const handleRemoveFriend = (id: string) => {

View File

@@ -3,54 +3,118 @@
}
.user-card {
margin-bottom: 16px;
border-radius: 12px;
overflow: hidden;
:global(.adm-card-body) {
padding: 20px;
}
border-radius: 16px;
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
margin: 16px 0 12px 0;
padding: 0 0 0 0;
}
.user-info {
.user-info-row {
display: flex;
align-items: center;
gap: 16px;
padding: 20px 24px 16px 24px;
}
.user-avatar {
width: 60px;
height: 60px;
width: 56px;
height: 56px;
border-radius: 50%;
background: #666;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
font-weight: 700;
color: #1890ff;
margin-right: 18px;
overflow: hidden;
border: 2px solid var(--primary-color);
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.avatar-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
font-weight: 700;
color: #1890ff;
background: #e6f7ff;
border-radius: 50%;
}
.user-details {
.user-main-info {
flex: 1;
min-width: 0;
position: relative;
}
.user-main-row {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.user-name {
font-size: 18px;
font-weight: 600;
color: #333;
font-size: 20px;
font-weight: 700;
color: #222;
margin-right: 2px;
}
.user-level {
font-size: 14px;
color: var(--primary-color);
margin-bottom: 4px;
.role-badge {
background: #fa8c16;
color: #fff;
font-size: 13px;
font-weight: 500;
border-radius: 12px;
padding: 2px 10px;
margin-right: 8px;
}
.user-points {
font-size: 12px;
.balance-label {
color: #666;
font-size: 15px;
margin-right: 2px;
}
.balance-value {
color: #16b364;
font-size: 20px;
font-weight: 700;
margin-right: 4px;
}
.recharge-btn {
margin-right: 8px;
padding: 0 14px;
font-size: 14px;
height: 28px;
line-height: 28px;
border-radius: 8px;
}
.icon-setting{
font-size: 26px;
color: #666;
position: absolute;
right: 0px;
top: 0px;
}
.icon-btn {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 50%;
background: none;
font-size: 20px;
color: #666;
cursor: pointer;
margin-left: 2px;
}
.last-login {
color: #888;
font-size: 13px;
margin-top: 6px;
margin-left: 2px;
}
.menu-card {
@@ -124,6 +188,7 @@
.user-avatar {
width: 50px;
height: 50px;
background: #666;
}
.user-name {

View File

@@ -23,6 +23,7 @@ const Mine: React.FC = () => {
wechat: 25,
traffic: 8,
content: 156,
balance: 0,
});
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
@@ -90,6 +91,7 @@ const Mine: React.FC = () => {
wechat: res.wechatNum,
traffic: 999,
content: 999,
balance: res.balance || 0,
});
} catch (error) {
console.error("加载统计数据失败:", error);
@@ -173,45 +175,51 @@ const Mine: React.FC = () => {
footer={<MeauMobile activeKey="mine" />}
>
<div className={style["mine-page"]}>
{/* 用户信息卡片 */}
{/* 用户信息卡片(严格按图片风格) */}
<Card className={style["user-card"]}>
<div className={style["user-info"]}>
<div className={style["user-avatar"]}>{renderUserAvatar()}</div>
<div className={style["user-details"]}>
<div
style={{
display: "flex",
alignItems: "center",
gap: "8px",
}}
>
<div className={style["user-name"]}>{currentUserInfo.name}</div>
<span
style={{
padding: "2px 8px",
backgroundColor: "#fa8c16",
color: "white",
borderRadius: "12px",
fontSize: "12px",
fontWeight: "500",
}}
>
<div className={style["user-info-row"]}>
{/* 头像 */}
<div className={style["user-avatar"]}>
{currentUserInfo.avatar ? (
<img src={currentUserInfo.avatar} />
) : (
<div className={style["avatar-placeholder"]}></div>
)}
</div>
{/* 右侧内容 */}
<div className={style["user-main-info"]}>
<div className={style["user-main-row"]}>
<span className={style["user-name"]}>
{currentUserInfo.name}
</span>
<span className={style["role-badge"]}>
{currentUserInfo.role}
</span>
<span className={style["icon-btn"]}>
<i className="iconfont icon-bell" />
</span>
</div>
<div
style={{ fontSize: "14px", color: "#666", marginBottom: "4px" }}
>
{currentUserInfo.email}
<div>
<span className={style["balance-label"]}></span>
<span className={style["balance-value"]}>
{Number(stats.balance || 0).toFixed(2)}
</span>
<Button
size="small"
color="primary"
onClick={() => navigate("/recharge")}
>
</Button>
</div>
<div style={{ fontSize: "12px", color: "#666" }}>
: {currentUserInfo.lastLogin}
<div className={style["last-login"]}>
{currentUserInfo.lastLogin}
</div>
</div>
<div
style={{ display: "flex", flexDirection: "column", gap: "8px" }}
>
<SettingOutlined style={{ fontSize: "20px", color: "#666" }} />
<SettingOutlined
className={style["icon-setting"]}
onClick={() => navigate("/settings")}
/>
</div>
</div>
</Card>

View File

@@ -0,0 +1,127 @@
.recharge-page {
padding: 16px 0 60px 0;
background: #f7f8fa;
min-height: 100vh;
}
.balance-card {
margin: 16px;
background: #f6ffed;
border: 1px solid #b7eb8f;
border-radius: 12px;
padding: 18px 0 18px 0;
display: flex;
align-items: center;
.balance-content {
display: flex;
color: #16b364;
padding-left: 30px;
}
.wallet-icon {
color: #16b364;
font-size: 30px;
flex-shrink: 0;
}
.balance-info {
margin-left: 15px;
display: flex;
flex-direction: column;
justify-content: center;
}
.balance-label {
font-size: 14px;
font-weight: normal;
color: #666;
margin-bottom: 2px;
}
.balance-amount {
font-size: 24px;
font-weight: 700;
color: #16b364;
line-height: 1.1;
}
}
.quick-card {
margin: 16px;
.quick-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: flex-start;
margin-bottom: 8px;
}
}
.desc-card {
margin: 16px;
background: #fffbe6;
border: 1px solid #ffe58f;
}
.warn-card {
margin: 16px;
background: #fff2e8;
border: 1px solid #ffbb96;
}
.quick-title {
font-weight: 500;
margin-bottom: 8px;
font-size: 16px;
}
.quick-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: flex-start;
margin-bottom: 8px;
}
.quick-btn {
min-width: 80px;
margin: 4px 0;
font-size: 16px;
border-radius: 8px;
}
.quick-btn-active {
@extend .quick-btn;
font-weight: 600;
}
.recharge-main-btn {
margin-top: 16px;
font-size: 18px;
border-radius: 8px;
}
.desc-title {
font-weight: 500;
margin-bottom: 8px;
font-size: 16px;
}
.desc-text {
color: #666;
font-size: 14px;
}
.warn-content {
display: flex;
align-items: center;
gap: 8px;
color: #faad14;
font-size: 14px;
}
.warn-icon {
font-size: 30px;
color: #faad14;
flex-shrink: 0;
}
.warn-info {
display: flex;
flex-direction: column;
}
.warn-title {
font-weight: 600;
font-size: 15px;
}
.warn-text {
color: #faad14;
font-size: 14px;
}

View File

@@ -0,0 +1,101 @@
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { Card, Button, Toast, NavBar } from "antd-mobile";
import { useUserStore } from "@/store/module/user";
import style from "./index.module.scss";
import { WalletOutlined, WarningOutlined } from "@ant-design/icons";
import NavCommon from "@/components/NavCommon";
import Layout from "@/components/Layout/Layout";
const quickAmounts = [50, 100, 200, 500, 1000];
const Recharge: React.FC = () => {
const navigate = useNavigate();
const { user } = useUserStore();
// 假设余额从后端接口获取实际可用props或store传递
const [balance, setBalance] = useState(0);
const [selected, setSelected] = useState<number | null>(null);
const [loading, setLoading] = useState(false);
// 充值操作
const handleRecharge = async () => {
if (!selected) {
Toast.show({ content: "请选择充值金额", position: "top" });
return;
}
setLoading(true);
setTimeout(() => {
setBalance((b) => b + selected);
Toast.show({ content: `充值成功,已到账¥${selected}` });
setLoading(false);
}, 1200);
};
return (
<Layout header={<NavCommon title="账户充值" />}>
<div className={style["recharge-page"]}>
<Card className={style["balance-card"]}>
<div className={style["balance-content"]}>
<WalletOutlined className={style["wallet-icon"]} />
<div className={style["balance-info"]}>
<div className={style["balance-label"]}></div>
<div className={style["balance-amount"]}>
{balance.toFixed(2)}
</div>
</div>
</div>
</Card>
<Card className={style["quick-card"]}>
<div className={style["quick-title"]}></div>
<div className={style["quick-list"]}>
{quickAmounts.map((amt) => (
<Button
key={amt}
color={selected === amt ? "primary" : "default"}
className={
selected === amt
? style["quick-btn-active"]
: style["quick-btn"]
}
onClick={() => setSelected(amt)}
>
{amt}
</Button>
))}
</div>
<Button
block
color="primary"
size="large"
className={style["recharge-main-btn"]}
loading={loading}
onClick={handleRecharge}
>
</Button>
</Card>
<Card className={style["desc-card"]}>
<div className={style["desc-title"]}></div>
<div className={style["desc-text"]}>
使
</div>
</Card>
{balance < 10 && (
<Card className={style["warn-card"]}>
<div className={style["warn-content"]}>
<WarningOutlined className={style["warn-icon"]} />
<div className={style["warn-info"]}>
<div className={style["warn-title"]}></div>
<div className={style["warn-text"]}>
使
</div>
</div>
</div>
</Card>
)}
</div>
</Layout>
);
};
export default Recharge;

View File

@@ -0,0 +1,115 @@
.user-set-page {
background: #f7f8fa;
}
.user-card {
margin: 18px 16px 0 16px;
border-radius: 14px;
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
}
.user-info {
display: flex;
align-items: flex-start;
padding: 24px 20px 20px 20px;
}
.avatar {
width: 64px;
height: 64px;
border-radius: 50%;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
font-size: 30px;
font-weight: 700;
color: #1890ff;
margin-right: 22px;
overflow: hidden;
}
.avatar-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 30px;
font-weight: 700;
color: #1890ff;
background: #e6f7ff;
border-radius: 50%;
}
.info-list {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 14px;
}
.info-item {
display: flex;
align-items: center;
font-size: 16px;
}
.label {
color: #888;
min-width: 70px;
font-size: 15px;
}
.value {
color: #222;
font-weight: 500;
font-size: 16px;
margin-left: 8px;
word-break: break-all;
}
.avatar-upload {
position: relative;
cursor: pointer;
width: 64px;
height: 64px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
overflow: hidden;
background: #f5f5f5;
transition: box-shadow 0.2s;
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
}
.avatar-upload img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 50%;
}
.avatar-edit {
position: absolute;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.45);
color: #fff;
font-size: 13px;
text-align: center;
padding: 3px 0 2px 0;
border-radius: 0 0 32px 32px;
opacity: 0;
transition: opacity 0.2s;
pointer-events: none;
}
.avatar-upload:hover .avatar-edit {
opacity: 1;
pointer-events: auto;
}
.edit-input {
flex: 1;
min-width: 0;
font-size: 16px;
border-radius: 8px;
border: 1px solid #e5e6eb;
padding: 4px 10px;
background: #fafbfc;
}
.save-btn {
padding: 12px;
background: #fff;
}

View File

@@ -0,0 +1,113 @@
import React, { useRef, useState } from "react";
import { useUserStore } from "@/store/module/user";
import { Card, Button, Input, Toast } from "antd-mobile";
import style from "./index.module.scss";
import { useNavigate } from "react-router-dom";
import Layout from "@/components/Layout/Layout";
import NavCommon from "@/components/NavCommon";
const UserSetting: React.FC = () => {
const { user, setUser } = useUserStore();
const navigate = useNavigate();
const [nickname, setNickname] = useState(user?.username || "");
const [avatar, setAvatar] = useState(user?.avatar || "");
const [uploading, setUploading] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
// 头像上传
const handleAvatarChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (ev) => {
setAvatar(ev.target?.result as string);
};
reader.readAsDataURL(file);
};
// 保存
const handleSave = async () => {
if (!nickname.trim()) {
Toast.show({ content: "昵称不能为空", position: "top" });
return;
}
if (!user) return;
setUser({ ...user, id: user.id, username: nickname, avatar });
Toast.show({ content: "保存成功", position: "top" });
navigate(-1);
};
return (
<Layout
header={<NavCommon title="用户信息修改" />}
footer={
<div className={style["save-btn"]}>
<Button
block
color="primary"
onClick={handleSave}
loading={uploading}
>
</Button>
</div>
}
>
<div className={style["user-set-page"]}>
<Card className={style["user-card"]}>
<div className={style["user-info"]}>
<div className={style["avatar"]}>
<div
className={style["avatar-upload"]}
onClick={() => fileInputRef.current?.click()}
>
{avatar ? (
<img src={avatar} alt="头像" />
) : (
<div className={style["avatar-placeholder"]}></div>
)}
<div className={style["avatar-edit"]}></div>
<input
ref={fileInputRef}
type="file"
accept="image/*"
style={{ display: "none" }}
onChange={handleAvatarChange}
disabled={uploading}
/>
</div>
</div>
<div className={style["info-list"]}>
<div className={style["info-item"]}>
<span className={style["label"]}></span>
<Input
className={style["edit-input"]}
value={nickname}
onChange={setNickname}
maxLength={12}
placeholder="请输入昵称"
/>
</div>
<div className={style["info-item"]}>
<span className={style["label"]}></span>
<span className={style["value"]}>{user?.phone || "-"}</span>
</div>
<div className={style["info-item"]}>
<span className={style["label"]}></span>
<span className={style["value"]}>{user?.account || "-"}</span>
</div>
<div className={style["info-item"]}>
<span className={style["label"]}></span>
<span className={style["value"]}>
{user?.isAdmin === 1 ? "管理员" : "普通用户"}
</span>
</div>
</div>
</div>
</Card>
</div>
</Layout>
);
};
export default UserSetting;

View File

@@ -598,6 +598,7 @@ const BasicSettings: React.FC<BasicSettingsProps> = ({
onChange={async (e) => {
const file = e.target.files?.[0];
if (file) {
// 直接上传
try {
const url = await uploadFile(file);
const newPoster = {
@@ -606,14 +607,9 @@ const BasicSettings: React.FC<BasicSettingsProps> = ({
type: "poster",
preview: url,
};
console.log(newPoster);
setCustomPosters((prev) => [...prev, newPoster]);
setSelectedMaterials([newPoster]);
onChange({ ...formData, materials: [newPoster] });
} catch (err) {
// 可加toast提示
log;
}
e.target.value = "";
}

View File

@@ -1,19 +1,10 @@
"use client";
import React, { useState, useEffect } from "react";
import {
Form,
Input,
Button,
Checkbox,
Modal,
Alert,
Select,
message,
} from "antd";
import { QuestionCircleOutlined, MessageOutlined } from "@ant-design/icons";
import { Input, Button, Modal, Alert, Select } from "antd";
import { MessageOutlined } from "@ant-design/icons";
import DeviceSelection from "@/components/DeviceSelection";
import Layout from "@/components/Layout/Layout";
import styles from "./friend.module.scss";
interface FriendRequestSettingsProps {
formData: any;
@@ -46,8 +37,8 @@ const FriendRequestSettings: React.FC<FriendRequestSettingsProps> = ({
}) => {
const [isTemplateDialogOpen, setIsTemplateDialogOpen] = useState(false);
const [hasWarnings, setHasWarnings] = useState(false);
const [selectedDevices, setSelectedDevices] = useState<any[]>(
formData.selectedDevices || []
const [selectedDevices, setSelectedDevices] = useState<string[]>(
formData.device || []
);
const [showRemarkTip, setShowRemarkTip] = useState(false);
@@ -97,171 +88,156 @@ const FriendRequestSettings: React.FC<FriendRequestSettingsProps> = ({
};
return (
<>
<Layout
footer={
<div className="p-4 border-t bg-white">
<div className="flex justify-between">
<Button onClick={onPrev}></Button>
<Button type="primary" onClick={handleNext}>
</Button>
</div>
</div>
}
>
<div className="p-4 space-y-6">
<div>
<span className="font-medium text-base"></span>
<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 className={styles["friend-container"]}>
{/* 选择设备区块 */}
<div className={styles["friend-label"]}></div>
<div className={styles["friend-block"]}>
<DeviceSelection
selectedDevices={selectedDevices}
onSelect={(deviceIds) => {
setSelectedDevices(deviceIds);
onChange({ ...formData, device: deviceIds });
}}
placeholder="选择设备"
/>
</div>
<div className="mb-4">
<div className="flex items-center space-x-2 mb-1 relative">
<span className="font-medium text-base"></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"
onMouseEnter={() => setShowRemarkTip(true)}
onMouseLeave={() => setShowRemarkTip(false)}
onClick={() => setShowRemarkTip((v) => !v)}
>
?
</span>
{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></div>
<div className="mt-2 text-xs text-gray-500">
</div>
<div className="mt-1 text-blue-600">
{formData.remarkType === "phone" &&
`138****1234+${getScenarioTitle()}`}
{formData.remarkType === "nickname" &&
`小红书用户2851+${getScenarioTitle()}`}
{formData.remarkType === "source" &&
`抖音直播+${getScenarioTitle()}`}
</div>
</div>
)}
{/* 好友备注区块 */}
<div className={styles["friend-label"]}></div>
<div className={styles["friend-block"]} style={{ position: "relative" }}>
<Select
value={formData.remarkType || "phone"}
onChange={(value) => onChange({ ...formData, remarkType: value })}
style={{ width: "100%" }}
>
{remarkTypes.map((type) => (
<Select.Option key={type.value} value={type.value}>
{type.label}
</Select.Option>
))}
</Select>
<span
className={styles["friend-remark-q"]}
onMouseEnter={() => setShowRemarkTip(true)}
onMouseLeave={() => setShowRemarkTip(false)}
>
?
</span>
{showRemarkTip && (
<div className={styles["friend-remark-tip"]}>
<div></div>
<div style={{ marginTop: 8, color: "#888", fontSize: 12 }}>
</div>
<Select
value={formData.remarkType || "phone"}
onChange={(value) => onChange({ ...formData, remarkType: value })}
className="w-full mt-2"
<div style={{ marginTop: 4, color: "#1677ff" }}>
{formData.remarkType === "phone" &&
`138****1234+${getScenarioTitle()}`}
{formData.remarkType === "nickname" &&
`小红书用户2851+${getScenarioTitle()}`}
{formData.remarkType === "source" &&
`抖音直播+${getScenarioTitle()}`}
</div>
</div>
)}
</div>
{/* 招呼语区块 */}
<div className={styles["friend-label"]}></div>
<div className={styles["friend-block"]}>
<Input
value={formData.greeting}
onChange={(e) => onChange({ ...formData, greeting: e.target.value })}
placeholder="请输入招呼语"
suffix={
<Button
type="link"
onClick={() => setIsTemplateDialogOpen(true)}
style={{ padding: 0 }}
>
{remarkTypes.map((type) => (
<Select.Option key={type.value} value={type.value}>
{type.label}
</Select.Option>
))}
</Select>
</div>
<MessageOutlined />
</Button>
}
/>
</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" />
</Button>
</div>
<Input
value={formData.greeting}
onChange={(e) =>
onChange({ ...formData, greeting: e.target.value })
}
placeholder="请输入招呼语"
className="mt-2"
/>
</div>
{/* 添加间隔区块 */}
<div className={styles["friend-label"]}></div>
<div
className={styles["friend-interval-row"] + " " + styles["friend-block"]}
>
<Input
type="number"
value={formData.addFriendInterval || 1}
onChange={(e) =>
onChange({
...formData,
addFriendInterval: Number(e.target.value),
})
}
style={{ width: 100 }}
/>
<span></span>
</div>
<div>
<span className="font-medium text-base"></span>
<div className="flex items-center space-x-2 mt-2">
<Input
type="number"
value={formData.addFriendInterval || 1}
onChange={(e) =>
onChange({
...formData,
addFriendInterval: Number(e.target.value),
})
}
/>
<div className="w-10"></div>
</div>
</div>
{/* 允许加人时间段区块 */}
<div className={styles["friend-label"]}></div>
<div className={styles["friend-time-row"] + " " + styles["friend-block"]}>
<Input
type="time"
value={formData.addFriendTimeStart || "09:00"}
onChange={(e) =>
onChange({ ...formData, addFriendTimeStart: e.target.value })
}
style={{ width: 120 }}
/>
<span></span>
<Input
type="time"
value={formData.addFriendTimeEnd || "18:00"}
onChange={(e) =>
onChange({ ...formData, addFriendTimeEnd: e.target.value })
}
style={{ width: 120 }}
/>
</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>
{hasWarnings && (
<Alert
message="警告"
description="您有未完成的设置项,建议完善后再进入下一步。"
type="warning"
showIcon
style={{ marginBottom: 16 }}
/>
)}
{hasWarnings && (
<Alert
message="警告"
description="您有未完成的设置项,建议完善后再进入下一步。"
type="warning"
showIcon
className="bg-amber-50 border-amber-200"
/>
)}
</div>
</Layout>
{/* 底部按钮 */}
<div className={styles["friend-footer"]}>
<Button onClick={onPrev}></Button>
<Button type="primary" onClick={handleNext}>
</Button>
</div>
{/* 招呼语模板弹窗 */}
<Modal
open={isTemplateDialogOpen}
onCancel={() => setIsTemplateDialogOpen(false)}
footer={null}
>
<div className="space-y-2">
<div>
{greetingTemplates.map((template, index) => (
<Button
key={index}
onClick={() => handleTemplateSelect(template)}
style={{ width: "100%", marginBottom: 8 }}
className={styles["friend-modal-btn"]}
>
{template}
</Button>
))}
</div>
</Modal>
</>
</div>
);
};

View File

@@ -1,5 +1,5 @@
import React, { useState } from "react";
import { Form, Input, Button, Tabs, Modal, Alert, Upload, message } from "antd";
import { Input, Button, Tabs, Modal, Alert, message } from "antd";
import {
PlusOutlined,
CloseOutlined,
@@ -13,7 +13,7 @@ import {
LinkOutlined,
TeamOutlined,
} from "@ant-design/icons";
import Layout from "@/components/Layout/Layout";
import styles from "./messages.module.scss";
interface MessageContent {
id: string;
@@ -194,9 +194,9 @@ const MessageSettings: React.FC<MessageSettingsProps> = ({
key: plan.day.toString(),
label: plan.day === 0 ? "即时消息" : `${plan.day}`,
children: (
<div className="space-y-4">
<div className={styles["messages-day-panel"]}>
{plan.messages.map((message, messageIndex) => (
<div key={message.id} className="space-y-4 p-4 bg-gray-50 rounded-lg">
<div key={message.id} className={styles["messages-message-card"]}>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
{plan.day === 0 ? (
@@ -524,7 +524,10 @@ const MessageSettings: React.FC<MessageSettingsProps> = ({
</div>
))}
<Button onClick={() => handleAddMessage(dayIndex)} className="w-full">
<Button
onClick={() => handleAddMessage(dayIndex)}
className={styles["messages-add-message-btn"]}
>
<PlusOutlined className="w-4 h-4 mr-2" />
</Button>
@@ -533,31 +536,24 @@ const MessageSettings: React.FC<MessageSettingsProps> = ({
}));
return (
<>
<Layout
footer={
<div className="p-4 border-t bg-white">
<div className="flex justify-between">
<Button onClick={onPrev}></Button>
<Button type="primary" onClick={onNext}>
</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} />
</div>
</Layout>
<div className={styles["messages-container"]}>
<div className={styles["messages-header"]}>
<h2 className={styles["messages-title"]}></h2>
<Button onClick={() => setIsAddDayPlanOpen(true)}>
<PlusOutlined />
</Button>
</div>
<Tabs
defaultActiveKey="0"
items={items}
className={styles["messages-tab"]}
/>
<div className={styles["messages-footer"]}>
<Button onClick={onPrev}></Button>
<Button type="primary" onClick={onNext}>
</Button>
</div>
{/* 添加天数计划弹窗 */}
<Modal
title="添加消息计划"
@@ -569,11 +565,13 @@ const MessageSettings: React.FC<MessageSettingsProps> = ({
}}
>
<p className="text-sm text-gray-500 mb-4"></p>
<Button onClick={handleAddDayPlan} className="w-full">
<Button
onClick={handleAddDayPlan}
className={styles["messages-modal-btn"]}
>
{dayPlans.length}
</Button>
</Modal>
{/* 选择群聊弹窗 */}
<Modal
title="选择群聊"
@@ -584,15 +582,14 @@ const MessageSettings: React.FC<MessageSettingsProps> = ({
setIsGroupSelectOpen(false);
}}
>
<div className="space-y-2">
<div>
{mockGroups.map((group) => (
<div
key={group.id}
className={`p-4 rounded-lg cursor-pointer hover:bg-gray-100 ${
selectedGroupId === group.id
? "bg-blue-50 border border-blue-200"
: ""
}`}
className={
styles["messages-group-select-item"] +
(selectedGroupId === group.id ? " " + styles.selected : "")
}
onClick={() => handleSelectGroup(group.id)}
>
<div className="font-medium">{group.name}</div>
@@ -603,7 +600,7 @@ const MessageSettings: React.FC<MessageSettingsProps> = ({
))}
</div>
</Modal>
</>
</div>
);
};

View File

@@ -0,0 +1,48 @@
.friend-container {
padding: 12px;
}
.friend-label {
margin-bottom: 12px;
font-weight: 500;
}
.friend-block {
margin-bottom: 16px;
}
.friend-remark-tip {
position: absolute;
right: 0;
top: 36px;
z-index: 10;
background: #fff;
border: 1px solid #eee;
border-radius: 6px;
padding: 12px;
width: 220px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.friend-remark-q {
position: absolute;
right: 8px;
top: 8px;
cursor: pointer;
color: #888;
}
.friend-interval-row {
display: flex;
align-items: center;
gap: 8px;
}
.friend-time-row {
display: flex;
align-items: center;
gap: 8px;
}
.friend-footer {
display: flex;
justify-content: space-between;
margin-top: 32px;
}
.friend-modal-btn {
width: 100%;
margin-bottom: 8px;
}

View File

@@ -0,0 +1,60 @@
.messages-container {
padding: 16px;
}
.messages-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
}
.messages-title {
font-size: 18px;
font-weight: 600;
}
.messages-tab {
margin-bottom: 16px;
}
.messages-day-panel {
background: #fafbfc;
border-radius: 10px;
padding: 16px;
margin-bottom: 16px;
}
.messages-message-card {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.03);
padding: 16px;
margin-bottom: 16px;
}
.messages-message-type-btns {
display: flex;
gap: 8px;
margin-bottom: 8px;
}
.messages-add-message-btn {
width: 100%;
margin-top: 8px;
}
.messages-footer {
display: flex;
justify-content: space-between;
margin-top: 32px;
}
.messages-modal-btn {
width: 100%;
margin-bottom: 8px;
}
.messages-group-select-item {
padding: 16px;
border-radius: 8px;
cursor: pointer;
background: #fff;
margin-bottom: 8px;
border: 1px solid #eee;
transition: border 0.2s, background 0.2s;
}
.messages-group-select-item.selected {
background: #e6f7ff;
border: 1.5px solid #1677ff;
}

View File

@@ -1,6 +1,7 @@
import React, { useState, useEffect, useRef } from "react";
import { useNavigate } from "react-router-dom";
import { NavBar, Button, Toast, SpinLoading, Dialog, Card } from "antd-mobile";
import { Button, Toast, SpinLoading, Dialog, Card } from "antd-mobile";
import NavCommon from "@/components/NavCommon";
import { Input } from "antd";
import {
PlusOutlined,
@@ -230,25 +231,15 @@ const AutoLike: React.FC = () => {
<Layout
header={
<>
<NavBar
back={null}
style={{ background: "#fff" }}
left={
<div className="nav-title">
<ArrowLeftOutlined
twoToneColor="#1677ff"
onClick={() => navigate(-1)}
/>
</div>
}
<NavCommon
title="自动点赞"
backFn={() => navigate("/workspace")}
right={
<Button size="small" color="primary" onClick={handleCreateNew}>
<PlusOutlined />
</Button>
}
>
<span className="nav-title"></span>
</NavBar>
/>
{/* 搜索栏 */}
<div className="search-bar">

View File

@@ -2,6 +2,8 @@ import Home from "@/pages/home/index";
import Mine from "@/pages/mine/index";
import WechatAccounts from "@/pages/wechat-accounts/list/index";
import WechatAccountDetail from "@/pages/wechat-accounts/detail/index";
import Recharge from "@/pages/mine/recharge/index";
import UserSetting from "@/pages/mine/userSet/index";
const routes = [
// 基础路由
@@ -26,6 +28,16 @@ const routes = [
element: <WechatAccountDetail />,
auth: true,
},
{
path: "/recharge",
element: <Recharge />,
auth: true,
},
{
path: "/settings",
element: <UserSetting />,
auth: true,
},
];
export default routes;

View File

@@ -40,7 +40,7 @@ const workspaceRoutes = [
auth: true,
},
{
path: "/workspace/auto-like/:id/edit",
path: "/workspace/auto-like/edit/:id",
element: <NewAutoLike />,
auth: true,
},