Merge branch 'yongpxu-dev2' into yongxu-dev3
This commit is contained in:
@@ -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 || "文件上传失败");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
127
nkebao/src/pages/mine/recharge/index.module.scss
Normal file
127
nkebao/src/pages/mine/recharge/index.module.scss
Normal 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;
|
||||
}
|
||||
101
nkebao/src/pages/mine/recharge/index.tsx
Normal file
101
nkebao/src/pages/mine/recharge/index.tsx
Normal 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;
|
||||
115
nkebao/src/pages/mine/userSet/index.module.scss
Normal file
115
nkebao/src/pages/mine/userSet/index.module.scss
Normal 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;
|
||||
}
|
||||
113
nkebao/src/pages/mine/userSet/index.tsx
Normal file
113
nkebao/src/pages/mine/userSet/index.tsx
Normal 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;
|
||||
@@ -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 = "";
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
48
nkebao/src/pages/scenarios/plan/new/steps/friend.module.scss
Normal file
48
nkebao/src/pages/scenarios/plan/new/steps/friend.module.scss
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -40,7 +40,7 @@ const workspaceRoutes = [
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/auto-like/:id/edit",
|
||||
path: "/workspace/auto-like/edit/:id",
|
||||
element: <NewAutoLike />,
|
||||
auth: true,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user