feat: 本次提交更新内容如下

定版本,准备大批量迁移
This commit is contained in:
笔记本里的永平
2025-07-19 11:40:02 +08:00
parent 9630389e22
commit b128410346
59 changed files with 2565 additions and 914 deletions

View File

@@ -1,7 +0,0 @@
import React from "react";
const About: React.FC = () => {
return <h1> About</h1>;
};
export default About;

View File

@@ -0,0 +1,14 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const ContactImport: React.FC = () => {
return (
<PlaceholderPage
title="联系人导入"
showAddButton
addButtonText="导入联系人"
/>
);
};
export default ContactImport;

View File

@@ -0,0 +1,10 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const Content: React.FC = () => {
return (
<PlaceholderPage title="内容管理" showAddButton addButtonText="新建内容" />
);
};
export default Content;

View File

@@ -0,0 +1,8 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const NewContent: React.FC = () => {
return <PlaceholderPage title="新建内容" />;
};
export default NewContent;

View File

@@ -0,0 +1,10 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const Materials: React.FC = () => {
return (
<PlaceholderPage title="素材管理" showAddButton addButtonText="新建素材" />
);
};
export default Materials;

View File

@@ -0,0 +1,8 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const MaterialsNew: React.FC = () => {
return <PlaceholderPage title="新建素材" />;
};
export default MaterialsNew;

View File

@@ -0,0 +1,30 @@
import React from "react";
import { NavBar } from "antd-mobile";
import Layout from "@/components/Layout/Layout";
import MeauMobile from "@/components/MeauMobile/MeauMoible";
const DeviceDetail: React.FC = () => {
return (
<Layout
header={
<NavBar
backArrow
style={{ background: "#fff" }}
onBack={() => window.history.back()}
>
<div style={{ color: "var(--primary-color)", fontWeight: 600 }}>
</div>
</NavBar>
}
footer={<MeauMobile />}
>
<div style={{ padding: 20, textAlign: "center", color: "#666" }}>
<h3></h3>
<p>...</p>
</div>
</Layout>
);
};
export default DeviceDetail;

View File

@@ -0,0 +1,37 @@
import React from "react";
import { NavBar, Button } from "antd-mobile";
import { AddOutline } from "antd-mobile-icons";
import Layout from "@/components/Layout/Layout";
import MeauMobile from "@/components/MeauMobile/MeauMoible";
const Devices: React.FC = () => {
return (
<Layout
header={
<NavBar
back={null}
style={{ background: "#fff" }}
left={
<div style={{ color: "var(--primary-color)", fontWeight: 600 }}>
</div>
}
right={
<Button size="small" color="primary">
<AddOutline />
<span style={{ marginLeft: 4, fontSize: 12 }}></span>
</Button>
}
/>
}
footer={<MeauMobile />}
>
<div style={{ padding: 20, textAlign: "center", color: "#666" }}>
<h3></h3>
<p>...</p>
</div>
</Layout>
);
};
export default Devices;

View File

@@ -1,439 +1,52 @@
.login-page {
min-height: 100vh;
background: var(--primary-gradient);
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
}
.nav-title {
font-size: 18px;
font-weight: 600;
color: var(--primary-color);
}
.login-content {
padding: 40px 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 15px;
position: relative;
overflow: hidden;
}
// 背景装饰
.bg-decoration {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
z-index: 0;
}
.bg-circle {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
animation: float 6s ease-in-out infinite;
&:nth-child(1) {
width: 200px;
height: 200px;
top: -100px;
right: -100px;
animation-delay: 0s;
}
&:nth-child(2) {
width: 150px;
height: 150px;
bottom: -75px;
left: -75px;
animation-delay: 2s;
}
&:nth-child(3) {
width: 100px;
height: 100px;
top: 50%;
right: 10%;
animation-delay: 4s;
}
}
@keyframes float {
0%, 100% {
transform: translateY(0px) rotate(0deg);
}
50% {
transform: translateY(-20px) rotate(180deg);
}
}
.login-container {
width: 100%;
max-width: 420px;
background: #ffffff;
backdrop-filter: blur(20px);
border-radius: 24px;
padding: 24px 20px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
position: relative;
z-index: 1;
border: 1px solid rgba(255, 255, 255, 0.2);
min-height: calc(100vh - 60px);
}
.login-header {
text-align: center;
margin-bottom: 24px;
margin-bottom: 40px;
}
.logo-section {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
margin-bottom: 16px;
}
.logo-icon {
width: 40px;
height: 40px;
background: var(--primary-gradient);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 20px;
box-shadow: 0 6px 12px var(--primary-shadow);
}
.app-name {
font-size: 24px;
font-weight: 800;
background: var(--primary-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin: 0;
}
.subtitle {
font-size: 13px;
color: #666;
margin: 0;
}
.form-container {
margin-bottom: 20px;
}
// 标签页样式
.tab-container {
display: flex;
background: #f8f9fa;
border-radius: 10px;
padding: 3px;
margin-bottom: 24px;
position: relative;
}
.tab-item {
flex: 1;
text-align: center;
padding: 10px 12px;
font-size: 13px;
font-weight: 500;
color: #666;
cursor: pointer;
border-radius: 7px;
transition: all 0.3s ease;
position: relative;
z-index: 2;
&.active {
color: var(--primary-color);
font-weight: 600;
background: white;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
}
.tab-indicator {
display: none; // 隐藏分割线指示器
}
// 表单样式
.login-form {
:global(.adm-form) {
--adm-font-size-main: 14px;
}
}
.input-group {
margin-bottom: 18px;
}
.input-label {
display: block;
font-size: 13px;
font-weight: 600;
color: #333;
margin-bottom: 6px;
}
.input-wrapper {
position: relative;
display: flex;
align-items: center;
background: #f8f9fa;
border: 2px solid transparent;
border-radius: 10px;
transition: all 0.3s ease;
&:focus-within {
border-color: var(--primary-color);
background: white;
box-shadow: 0 0 0 3px var(--primary-shadow-light);
}
}
.input-prefix {
padding: 0 12px;
color: #666;
font-size: 13px;
font-weight: 500;
border-right: 1px solid #e5e5e5;
}
.phone-input,
.password-input,
.code-input {
flex: 1;
border: none !important;
background: transparent !important;
padding: 12px 14px !important;
font-size: 15px !important;
color: #333 !important;
&::placeholder {
color: #999;
}
&:focus {
box-shadow: none !important;
}
}
.eye-icon {
padding: 0 12px;
color: #666;
cursor: pointer;
transition: color 0.3s ease;
&:hover {
color: var(--primary-color);
}
}
.send-code-btn {
padding: 6px 12px;
margin-right: 6px;
background: var(--primary-gradient);
color: white;
border: none;
border-radius: 6px;
font-size: 11px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
&:hover:not(.disabled) {
transform: translateY(-1px);
box-shadow: 0 3px 8px var(--primary-shadow);
}
&.disabled {
background: #e5e5e5;
color: #999;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
}
.agreement-section {
margin-bottom: 24px;
}
.agreement-checkbox {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: #666;
line-height: 1.3;
white-space: nowrap;
:global(.adm-checkbox) {
margin-top: 0;
flex-shrink: 0;
transform: scale(0.8);
}
}
.agreement-text {
flex: 1;
display: flex;
align-items: center;
flex-wrap: nowrap;
white-space: nowrap;
overflow: visible;
text-overflow: clip;
font-size: 13px;
}
.agreement-link {
.login-title {
font-size: 28px;
font-weight: 700;
color: var(--primary-color);
cursor: pointer;
text-decoration: none;
white-space: nowrap;
font-size: 11px;
&:hover {
text-decoration: underline;
}
margin-bottom: 8px;
}
.login-btn {
height: 46px;
font-size: 15px;
font-weight: 600;
border-radius: 10px;
.login-subtitle {
font-size: 14px;
color: #666;
line-height: 1.4;
}
.login-form {
width: 100%;
max-width: 320px;
}
.login-button {
margin-top: 20px;
border-radius: 20px;
background: var(--primary-gradient);
border: none;
box-shadow: 0 6px 12px var(--primary-shadow);
transition: all 0.3s ease;
&:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 8px 16px var(--primary-shadow-dark);
}
&:disabled {
background: #e5e5e5;
color: #999;
transform: none;
box-shadow: none;
}
}
.divider {
position: relative;
text-align: center;
margin: 24px 0;
&::before {
content: '';
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 1px;
background: #e5e5e5;
}
span {
background: rgba(255, 255, 255, 0.95);
padding: 0 12px;
color: #999;
font-size: 11px;
font-weight: 500;
}
}
.third-party-login {
display: flex;
justify-content: center;
gap: 20px;
}
.third-party-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
cursor: pointer;
padding: 12px;
border-radius: 10px;
transition: all 0.3s ease;
&:hover {
background: #f8f9fa;
transform: translateY(-1px);
}
span {
font-size: 11px;
color: #666;
font-weight: 500;
}
}
.wechat-icon,
.apple-icon {
width: 36px;
height: 36px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 18px;
transition: all 0.3s ease;
}
.wechat-icon {
background: #07c160;
box-shadow: 0 3px 8px rgba(7, 193, 96, 0.3);
&:hover {
box-shadow: 0 4px 12px rgba(7, 193, 96, 0.4);
}
svg {
width: 20px;
height: 20px;
}
}
.apple-icon {
background: #000;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.3);
&:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
svg {
width: 20px;
height: 20px;
}
}
// 响应式设计
@media (max-width: 480px) {
.login-container {
padding: 24px 20px;
margin: 0 12px;
}
.app-name {
font-size: 22px;
}
.third-party-login {
gap: 16px;
}
.third-party-item {
padding: 10px;
}
.wechat-icon,
.apple-icon {
width: 32px;
height: 32px;
}
box-shadow: 0 2px 8px var(--primary-shadow);
height: 44px;
font-weight: 600;
}

View File

@@ -1,331 +1,64 @@
import React, { useState, useEffect } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { Form, Input, Button, Toast, Tabs, Checkbox } from "antd-mobile";
import {
EyeInvisibleOutline,
EyeOutline,
UserOutline,
} from "antd-mobile-icons";
import { useUserStore } from "@/store/module/user";
import { loginWithPassword, loginWithCode, sendVerificationCode } from "./api";
import React from "react";
import { NavBar, Form, Input, Button, Toast } from "antd-mobile";
import Layout from "@/components/Layout/Layout";
import style from "./login.module.scss";
const Login: React.FC = () => {
const [form] = Form.useForm();
const [activeTab, setActiveTab] = useState(1); // 1: 密码登录, 2: 验证码登录
const [loading, setLoading] = useState(false);
const [countdown, setCountdown] = useState(0);
const [showPassword, setShowPassword] = useState(false);
const [agreeToTerms, setAgreeToTerms] = useState(false);
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const { login } = useUserStore();
// 倒计时效果
useEffect(() => {
if (countdown > 0) {
const timer = setTimeout(() => setCountdown(countdown - 1), 1000);
return () => clearTimeout(timer);
}
}, [countdown]);
// 检查URL是否为登录页面
const isLoginPage = (url: string) => {
try {
const urlObj = new URL(url, window.location.origin);
return urlObj.pathname === "/login" || urlObj.pathname.endsWith("/login");
} catch {
return false;
}
};
// 发送验证码
const handleSendVerificationCode = async () => {
const account = form.getFieldValue("account");
if (!account) {
Toast.show({ content: "请输入手机号", position: "top" });
return;
}
// 手机号格式验证
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(account)) {
Toast.show({ content: "请输入正确的11位手机号", position: "top" });
return;
}
try {
setLoading(true);
await sendVerificationCode({
mobile: account,
type: "login",
});
Toast.show({ content: "验证码已发送", position: "top" });
setCountdown(60);
} catch (error) {
// 错误已在request中处理这里不需要额外处理
} finally {
setLoading(false);
}
};
// 登录处理
const handleLogin = async (values: any) => {
if (!agreeToTerms) {
Toast.show({ content: "请同意用户协议和隐私政策", position: "top" });
return;
}
setLoading(true);
try {
// 添加typeId参数
const loginParams = {
...values,
typeId: activeTab as number,
};
let response;
if (activeTab === 1) {
response = await loginWithPassword(loginParams);
} else {
response = await loginWithCode(loginParams);
}
console.log(response, "response");
// 更新状态管理token会自动存储到localStorage用户信息存储在状态管理中
login(response.token, response.member);
Toast.show({ content: "登录成功", position: "top" });
// 跳转到首页或重定向URL
const returnUrl = searchParams.get("returnUrl");
if (returnUrl) {
const decodedUrl = decodeURIComponent(returnUrl);
if (isLoginPage(decodedUrl)) {
navigate("/");
} else {
window.location.href = decodedUrl;
}
} else {
navigate("/");
}
} catch (error: any) {
// 错误已在request中处理这里不需要额外处理
} finally {
setLoading(false);
}
};
// 第三方登录处理
const handleWechatLogin = () => {
Toast.show({ content: "微信登录功能开发中", position: "top" });
};
const handleAppleLogin = () => {
Toast.show({ content: "Apple登录功能开发中", position: "top" });
const onFinish = (values: any) => {
console.log("登录信息:", values);
Toast.show({
content: "登录功能待实现",
position: "top",
});
};
return (
<div className={style["login-page"]}>
{/* 背景装饰 */}
<div className={style["bg-decoration"]}>
<div className={style["bg-circle"]}></div>
<div className={style["bg-circle"]}></div>
<div className={style["bg-circle"]}></div>
</div>
<Layout>
<div className={style["login-page"]}>
<NavBar back={null} style={{ background: "#fff" }}>
<div className={style["nav-title"]}></div>
</NavBar>
<div className={style["login-container"]}>
{/* Logo和标题区域 */}
<div className={style["login-header"]}>
<div className={style["logo-section"]}>
<div className={style["logo-icon"]}>
<UserOutline />
</div>
<h1 className={style["app-name"]}></h1>
</div>
<p className={style["subtitle"]}>使</p>
</div>
{/* 登录表单 */}
<div className={style["form-container"]}>
{/* 标签页切换 */}
<div className={style["tab-container"]}>
<div
className={`${style["tab-item"]} ${
activeTab === 1 ? style["active"] : ""
}`}
onClick={() => setActiveTab(1)}
>
</div>
<div
className={`${style["tab-item"]} ${
activeTab === 2 ? style["active"] : ""
}`}
onClick={() => setActiveTab(2)}
>
</div>
<div
className={`${style["tab-indicator"]} ${
activeTab === 2 ? style["slide"] : ""
}`}
></div>
<div className={style["login-content"]}>
<div className={style["login-header"]}>
<h1 className={style["login-title"]}></h1>
<p className={style["login-subtitle"]}></p>
</div>
<Form
form={form}
layout="vertical"
onFinish={onFinish}
className={style["login-form"]}
onFinish={handleLogin}
>
{/* 手机号输入 */}
<Form.Item
name="account"
label="手机号"
rules={[
{ required: true, message: "请输入手机号" },
{
pattern: /^1[3-9]\d{9}$/,
message: "请输入正确的11位手机号",
},
]}
label="用户名"
name="username"
rules={[{ required: true, message: "请输入用户名" }]}
>
<div className={style["input-wrapper"]}>
<span className={style["input-prefix"]}>+86</span>
<Input
placeholder="请输入手机号"
clearable
className={style["phone-input"]}
/>
</div>
<Input placeholder="请输入用户名" />
</Form.Item>
{/* 密码输入 */}
{activeTab === 1 && (
<Form.Item
name="password"
label="密码"
rules={[{ required: true, message: "请输入密码" }]}
>
<div className={style["input-wrapper"]}>
<Input
placeholder="请输入密码"
clearable
type={showPassword ? "text" : "password"}
className={style["password-input"]}
/>
<div
className={style["eye-icon"]}
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? <EyeOutline /> : <EyeInvisibleOutline />}
</div>
</div>
</Form.Item>
)}
<Form.Item
label="密码"
name="password"
rules={[{ required: true, message: "请输入密码" }]}
>
<Input type="password" placeholder="请输入密码" />
</Form.Item>
{/* 验证码输入 */}
{activeTab === 2 && (
<Form.Item
name="verificationCode"
label="验证码"
rules={[{ required: true, message: "请输入验证码" }]}
>
<div className={style["input-wrapper"]}>
<Input
placeholder="请输入验证码"
clearable
className={style["code-input"]}
/>
<button
type="button"
className={`${style["send-code-btn"]} ${
countdown > 0 ? style["disabled"] : ""
}`}
onClick={handleSendVerificationCode}
disabled={loading || countdown > 0}
>
{countdown > 0 ? `${countdown}s` : "获取验证码"}
</button>
</div>
</Form.Item>
)}
{/* 用户协议 */}
<div className={style["agreement-section"]}>
<Checkbox
checked={agreeToTerms}
onChange={setAgreeToTerms}
className={style["agreement-checkbox"]}
>
<span className={style["agreement-text"]}>
<span className={style["agreement-link"]}>
</span>
<span className={style["agreement-link"]}></span>
</span>
</Checkbox>
</div>
{/* 登录按钮 */}
<Button
block
type="submit"
color="primary"
loading={loading}
size="large"
className={style["login-btn"]}
type="submit"
className={style["login-button"]}
>
{loading ? "登录中..." : "登录"}
</Button>
</Form>
{/* 分割线 */}
<div className={style["divider"]}>
<span></span>
</div>
{/* 第三方登录 */}
<div className={style["third-party-login"]}>
<div
className={style["third-party-item"]}
onClick={handleWechatLogin}
>
<div className={style["wechat-icon"]}>
<svg
viewBox="0 0 24 24"
fill="currentColor"
height="24"
width="24"
className={style["wechat-icon"]}
>
<path d="M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 0 1 .213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.326.326 0 0 0 .167-.054l1.903-1.114a.864.864 0 0 1 .717-.098 10.16 10.16 0 0 0 2.837.403c.276 0 .543-.027.81-.05-.857-2.578.157-4.972 1.932-6.446 1.703-1.415 3.882-1.98 5.853-1.838-.576-3.583-4.196-6.348-8.595-6.348zM5.959 5.48c.609 0 1.104.498 1.104 1.112 0 .612-.495 1.11-1.104 1.11-.612 0-1.108-.498-1.108-1.11 0-.614.496-1.112 1.108-1.112zm5.315 0c.61 0 1.107.498 1.107 1.112 0 .612-.497 1.11-1.107 1.11-.611 0-1.105-.498-1.105-1.11 0-.614.494-1.112 1.105-1.112z"></path>
<path d="M23.002 15.816c0-3.309-3.136-6-7-6-3.863 0-7 2.691-7 6 0 3.31 3.137 6 7 6 .814 0 1.601-.099 2.338-.285a.7.7 0 0 1 .579.08l1.5.87a.267.267 0 0 0 .135.044c.13 0 .236-.108.236-.241 0-.06-.023-.118-.038-.17l-.309-1.167a.476.476 0 0 1 .172-.534c1.645-1.17 2.387-2.835 2.387-4.597zm-9.498-1.19c-.497 0-.9-.407-.9-.908a.905.905 0 0 1 .9-.91c.498 0 .9.408.9.91 0 .5-.402.908-.9.908zm4.998 0c-.497 0-.9-.407-.9-.908a.905.905 0 0 1 .9-.91c.498 0 .9.408.9.91 0 .5-.402.908-.9.908z"></path>
</svg>
</div>
<span></span>
</div>
<div
className={style["third-party-item"]}
onClick={handleAppleLogin}
>
<div className={style["apple-icon"]}>
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z" />
</svg>
</div>
<span>Apple</span>
</div>
</div>
</div>
</div>
</div>
</Layout>
);
};

View File

@@ -0,0 +1,8 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const Orders: React.FC = () => {
return <PlaceholderPage title="订单管理" />;
};
export default Orders;

View File

@@ -0,0 +1,8 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const PlanDetail: React.FC = () => {
return <PlaceholderPage title="计划详情" />;
};
export default PlanDetail;

View File

@@ -0,0 +1,10 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const Plans: React.FC = () => {
return (
<PlaceholderPage title="计划管理" showAddButton addButtonText="新建计划" />
);
};
export default Plans;

View File

@@ -0,0 +1,8 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const Profile: React.FC = () => {
return <PlaceholderPage title="个人中心" showBack={false} />;
};
export default Profile;

View File

@@ -0,0 +1,8 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const ScenarioList: React.FC = () => {
return <PlaceholderPage title="场景列表" />;
};
export default ScenarioList;

View File

@@ -0,0 +1,10 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const Scenarios: React.FC = () => {
return (
<PlaceholderPage title="场景管理" showAddButton addButtonText="新建场景" />
);
};
export default Scenarios;

View File

@@ -0,0 +1,8 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const NewPlan: React.FC = () => {
return <PlaceholderPage title="新建场景" />;
};
export default NewPlan;

View File

@@ -1,14 +1,26 @@
import request from '@/api/request';
// 设备统计
export function getDeviceStats() {
return request('/v1/dashboard/device-stats', {}, 'GET');
// 获取场景列表
export function getScenarios(params: any) {
return request('/v1/plan/scenes', params, 'GET');
}
// 微信号统计
export function getWechatStats() {
return request('/v1/dashboard/wechat-stats', {}, 'GET');
// 获取场景详情
export function getScenarioDetail(id: string) {
return request(`/v1/scenarios/${id}`, {}, 'GET');
}
// 你可以根据需要继续添加其他接口
// 例如:场景获客统计、今日数据统计等
// 创建场景
export function createScenario(data: any) {
return request('/v1/scenarios', data, 'POST');
}
// 更新场景
export function updateScenario(id: string, data: any) {
return request(`/v1/scenarios/${id}`, data, 'PUT');
}
// 删除场景
export function deleteScenario(id: string) {
return request(`/v1/scenarios/${id}`, {}, 'DELETE');
}

View File

@@ -1,71 +1,319 @@
.home-page {
padding: 12px;
background: #f5f5f5;
// 导航栏样式
.nav-title {
font-size: 18px;
font-weight: 600;
color: var(--primary-color);
}
.home-cards {
display: flex;
gap: 12px;
margin-bottom: 12px;
> :global(.adm-card) {
flex: 1;
.nav-text {
color: var(--primary-color);
}
.nav-right {
margin-left: 4px;
font-size: 12px;
}
.new-plan-btn {
border-radius: 20px;
padding: 4px 12px;
height: 32px;
font-size: 12px;
background: var(--primary-gradient);
border: none;
box-shadow: 0 2px 8px var(--primary-shadow);
&:active {
transform: translateY(1px);
box-shadow: 0 1px 4px var(--primary-shadow);
}
}
.home-section {
// 页面容器
.scene-page {
padding: 12px;
background: #f5f5f5;
min-height: calc(100vh - 100px);
}
// 错误提示
.error-notice {
margin-bottom: 12px;
padding: 8px 12px;
background: #fff2e8;
border: 1px solid #ffd591;
border-radius: 8px;
box-shadow: 0 1px 4px rgba(255, 213, 145, 0.2);
}
.error-notice-text {
font-size: 12px;
color: #d46b08;
font-weight: 500;
}
// 加载状态
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 200px;
gap: 12px;
}
.loading-text {
font-size: 14px;
color: #666;
font-weight: 500;
}
// 错误状态
.error-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 200px;
gap: 16px;
}
.error-text {
font-size: 14px;
color: #ff4d4f;
text-align: center;
font-weight: 500;
}
.retry-button {
min-width: 100px;
border-radius: 20px;
background: var(--primary-gradient);
border: none;
box-shadow: 0 2px 8px var(--primary-shadow);
}
// 页面头部
.scene-header {
margin-bottom: 16px;
text-align: center;
}
.header-title {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
font-size: 18px;
font-weight: 700;
color: var(--primary-color);
margin-bottom: 6px;
}
.header-icon {
font-size: 20px;
color: var(--primary-color);
}
.header-subtitle {
font-size: 12px;
color: #666;
line-height: 1.4;
}
// 场景列表
.scenarios-list {
display: flex;
flex-direction: column;
gap: 10px;
}
// 场景卡片
.scenario-item {
cursor: pointer;
transition: all 0.2s ease;
&:hover {
transform: translateY(-1px);
}
&:active {
transform: translateY(0);
}
}
.scenario-card {
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.03);
}
.home-section-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 15px;
padding-left: 16px;
}
.home-scene-stats {
padding: 16px 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
display: flex;
justify-content: space-between;
margin: 0 8px 8px 8px;
}
.home-scene-item {
flex: 1;
text-align: center;
}
.home-scene-icon {
margin: 0 auto 4px auto;
}
.home-scene-value {
font-size: 18px;
font-weight: bold;
color: #1677ff;
}
.home-scene-label {
font-size: 12px;
color: #888;
align-items: center;
min-height: 78px;
&:hover {
box-shadow: 0 4px 16px rgba(24, 142, 238, 0.12);
border-color: var(--primary-color-light);
}
}
.home-today-item {
text-align: center;
padding: 8px 0;
background: #f7f8fa;
.scenario-left {
display: flex;
align-items: center;
gap: 16px;
flex: 1;
}
.scenario-icon {
width: 52px;
height: 52px;
background: linear-gradient(135deg, #f0f8ff 0%, #e6f7ff 100%);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid rgba(24, 142, 238, 0.1);
flex-shrink: 0;
}
.scenario-image {
width: 32px;
height: 32px;
object-fit: contain;
border-radius: 8px;
}
.home-today-value {
font-size: 18px;
font-weight: bold;
color: #1677ff;
}
.home-today-label {
font-size: 12px;
color: #888;
.scenario-info {
display: flex;
flex-direction: column;
gap: 6px;
flex: 1;
min-width: 0;
}
.home-chart {
margin-top: 8px;
width: 100%;
min-height: 100px;
}
.scenario-name {
font-size: 16px;
font-weight: 600;
color: #1a1a1a;
line-height: 1.3;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.scenario-stats {
display: flex;
align-items: center;
}
.stat-text {
font-size: 13px;
color: #666;
font-weight: 500;
}
.scenario-right {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 8px;
flex-shrink: 0;
}
.scenario-status {
display: flex;
justify-content: flex-end;
}
.status-tag {
font-size: 11px;
padding: 3px 8px;
border-radius: 10px;
font-weight: 500;
}
.scenario-growth {
display: flex;
align-items: center;
gap: 3px;
font-size: 16px;
font-weight: 700;
}
.growth-icon {
font-size: 14px;
}
// 空状态
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 20px;
text-align: center;
}
.empty-icon {
font-size: 36px;
margin-bottom: 12px;
opacity: 0.6;
}
.empty-text {
font-size: 14px;
color: #666;
margin-bottom: 16px;
font-weight: 500;
}
.empty-action {
border-radius: 20px;
background: var(--primary-gradient);
border: none;
box-shadow: 0 2px 8px var(--primary-shadow);
padding: 6px 16px;
font-weight: 500;
font-size: 12px;
}
// 响应式设计
@media (max-width: 480px) {
.scene-page {
padding: 8px;
}
.scenario-card {
padding: 14px 16px;
min-height: 70px;
}
.scenario-icon {
width: 46px;
height: 46px;
}
.scenario-image {
width: 28px;
height: 28px;
}
.scenario-name {
font-size: 15px;
}
.stat-text {
font-size: 12px;
}
.scenario-growth {
font-size: 15px;
}
.growth-icon {
font-size: 13px;
}
}

View File

@@ -1,132 +1,184 @@
import React, { useState } from "react";
import { Card, NavBar, TabBar, Grid } from "antd-mobile";
import {
AppOutline,
UserOutline,
PieOutline, // 替换 BarChartOutline
ShopbagOutline,
BellOutline,
} from "antd-mobile-icons";
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { NavBar, Button, SpinLoading, Toast, Tag } from "antd-mobile";
import { AddOutline, UserAddOutline } from "antd-mobile-icons";
import MeauMobile from "@/components/MeauMobile/MeauMoible";
import Layout from "@/components/Layout/Layout";
import { getScenarios } from "./api";
import style from "./index.module.scss";
const sceneStats = [
{
label: "公众号获客",
value: 234,
icon: <ShopbagOutline style={{ fontSize: 28, color: "#4caf50" }} />,
},
{
label: "海报获客",
value: 167,
icon: <AppOutline style={{ fontSize: 28, color: "#ff9800" }} />,
},
{
label: "抖音获客",
value: 156,
icon: <PieOutline style={{ fontSize: 28, color: "#2196f3" }} />,
},
{
label: "小红书获客",
value: 89,
icon: <UserOutline style={{ fontSize: 28, color: "#e91e63" }} />,
},
];
const todayStats = [
{ label: "朋友圈同步", value: 12 },
{ label: "群发任务", value: 8 },
{ label: "获客转化", value: "85%" },
{ label: "系统活跃度", value: "98%" },
];
interface Scenario {
id: string;
name: string;
image: string;
count: number;
growth: string;
status: number;
}
const Scene: React.FC = () => {
const navigate = useNavigate();
const [scenarios, setScenarios] = useState<Scenario[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
useEffect(() => {
const fetchScenarios = async () => {
setLoading(true);
setError("");
try {
const response = await getScenarios({ page: 1, limit: 20 });
// 转换API数据为前端需要的格式
const transformedScenarios: Scenario[] = response.map((item: any) => ({
id: item.id.toString(),
name: item.name,
image:
item.image ||
"https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-api.png",
count: item.count, // 模拟今日数据
growth: item.growth, // 模拟增长率
status: item.status,
}));
setScenarios(transformedScenarios);
} catch (error) {
console.error("获取场景数据失败:", error);
setError("获取场景数据失败,请稍后重试");
Toast.show({
content: "获取场景数据失败,请稍后重试",
position: "top",
});
} finally {
setLoading(false);
}
};
fetchScenarios();
}, []);
const handleScenarioClick = (scenarioId: string, scenarioName: string) => {
navigate(
`/scenarios/list/${scenarioId}/${encodeURIComponent(scenarioName)}`
);
};
const handleNewPlan = () => {
navigate("/scenarios/new");
};
const getGrowthColor = (growth: string) => {
const value = parseFloat(growth.replace(/[+%]/g, ""));
if (value > 10) return "#52c41a";
if (value > 5) return "#faad14";
return "#ff4d4f";
};
const Home: React.FC = () => {
return (
<Layout
header={
<NavBar back={null} style={{ background: "#fff" }}>
<span style={{ color: "#1677ff", fontWeight: 700 }}></span>
</NavBar>
<NavBar
back={null}
style={{ background: "#fff" }}
left={<div className={style["nav-title"]}></div>}
right={
<Button
size="small"
color="primary"
onClick={handleNewPlan}
className={style["new-plan-btn"]}
>
<AddOutline />
<span className={style["nav-right"]}></span>
</Button>
}
/>
}
footer={<MeauMobile />}
>
<div className={style["home-page"]}>
{/* 统计卡片 */}
<div className={style["home-cards"]}>
<Card className={style["home-card"]}>
<div className={style["home-card-title"]}></div>
<div className={style["home-card-value"]}>0</div>
</Card>
<Card className={style["home-card"]}>
<div className={style["home-card-title"]}></div>
<div className={style["home-card-value"]}>0</div>
</Card>
<Card className={style["home-card"]}>
<div className={style["home-card-title"]}>线</div>
<div className={style["home-card-value"]}>0</div>
</Card>
<div className={style["scene-page"]}>
<div className={style["scene-header"]}>
<div className={style["header-title"]}>
<UserAddOutline className={style["header-icon"]} />
<span></span>
</div>
<div className={style["header-subtitle"]}>
</div>
</div>
{/* 场景获客统计 */}
<Card className={style["home-section"]}>
<div className={style["home-section-title"]}></div>
<div className={style["home-scene-stats"]}>
{sceneStats.map((item) => (
<div key={item.label} className={style["home-scene-item"]}>
<div className={style["home-scene-icon"]}>{item.icon}</div>
<div className={style["home-scene-value"]}>{item.value}</div>
<div className={style["home-scene-label"]}>{item.label}</div>
</div>
))}
</div>
</Card>
{/* 今日数据 */}
<Card className={style["home-section"]}>
<div className={style["home-section-title"]}></div>
<Grid columns={2} gap={12}>
{todayStats.map((item) => (
<Grid.Item key={item.label}>
<div className={style["home-today-item"]}>
<div className={style["home-today-value"]}>{item.value}</div>
<div className={style["home-today-label"]}>{item.label}</div>
<div className={style["scenarios-list"]}>
{scenarios.map((scenario) => (
<div
key={scenario.id}
className={style["scenario-item"]}
onClick={() => handleScenarioClick(scenario.id, scenario.name)}
>
<div className={style["scenario-card"]}>
<div className={style["scenario-left"]}>
<div className={style["scenario-icon"]}>
<img
src={scenario.image}
alt={scenario.name}
className={style["scenario-image"]}
onError={(e) => {
e.currentTarget.src =
"https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-api.png";
}}
/>
</div>
<div className={style["scenario-info"]}>
<div className={style["scenario-name"]}>
{scenario.name}
</div>
<div className={style["scenario-stats"]}>
<span className={style["stat-text"]}>
: {scenario.count}
</span>
</div>
</div>
</div>
</Grid.Item>
))}
</Grid>
</Card>
{/* 每日获客趋势(静态图表占位) */}
<Card className={style["home-section"]}>
<div className={style["home-section-title"]}></div>
<div className={style["home-chart"]}>
<svg width="100%" height="120">
<polyline
fill="none"
stroke="#1677ff"
strokeWidth="3"
points="10,100 40,80 70,60 100,50 130,40 160,60 190,80"
/>
{/* x轴文字 */}
{["周一", "周二", "周三", "周四", "周五", "周六", "周日"].map(
(d, i) => (
<text
key={d}
x={10 + i * 30}
y={115}
fontSize="12"
textAnchor="middle"
<div className={style["scenario-right"]}>
<div className={style["scenario-status"]}>
<Tag
color={scenario.status === 1 ? "success" : "default"}
fill="outline"
className={style["status-tag"]}
>
{scenario.status === 1 ? "活跃" : "暂停"}
</Tag>
</div>
<div
className={style["scenario-growth"]}
style={{ color: getGrowthColor(scenario.growth) }}
>
{d}
</text>
)
)}
</svg>
{scenario.growth}
</div>
</div>
</div>
</div>
))}
</div>
{scenarios.length === 0 && !loading && (
<div className={style["empty-state"]}>
<div className={style["empty-icon"]}>📊</div>
<div className={style["empty-text"]}></div>
<Button
color="primary"
size="small"
onClick={handleNewPlan}
className={style["empty-action"]}
>
</Button>
</div>
</Card>
)}
</div>
</Layout>
);
};
export default Home;
export default Scene;

View File

@@ -0,0 +1,10 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const TrafficPool: React.FC = () => {
return (
<PlaceholderPage title="流量池" showAddButton addButtonText="新建流量池" />
);
};
export default TrafficPool;

View File

@@ -0,0 +1,8 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const TrafficPoolDetail: React.FC = () => {
return <PlaceholderPage title="流量池详情" />;
};
export default TrafficPoolDetail;

View File

@@ -0,0 +1,30 @@
import React from "react";
import { NavBar } from "antd-mobile";
import Layout from "@/components/Layout/Layout";
import MeauMobile from "@/components/MeauMobile/MeauMoible";
const WechatAccountDetail: React.FC = () => {
return (
<Layout
header={
<NavBar
backArrow
style={{ background: "#fff" }}
onBack={() => window.history.back()}
>
<div style={{ color: "var(--primary-color)", fontWeight: 600 }}>
</div>
</NavBar>
}
footer={<MeauMobile />}
>
<div style={{ padding: 20, textAlign: "center", color: "#666" }}>
<h3></h3>
<p>...</p>
</div>
</Layout>
);
};
export default WechatAccountDetail;

View File

@@ -0,0 +1,37 @@
import React from "react";
import { NavBar, Button } from "antd-mobile";
import { AddOutline } from "antd-mobile-icons";
import Layout from "@/components/Layout/Layout";
import MeauMobile from "@/components/MeauMobile/MeauMoible";
const WechatAccounts: React.FC = () => {
return (
<Layout
header={
<NavBar
back={null}
style={{ background: "#fff" }}
left={
<div style={{ color: "var(--primary-color)", fontWeight: 600 }}>
</div>
}
right={
<Button size="small" color="primary">
<AddOutline />
<span style={{ marginLeft: 4, fontSize: 12 }}></span>
</Button>
}
/>
}
footer={<MeauMobile />}
>
<div style={{ padding: 20, textAlign: "center", color: "#666" }}>
<h3></h3>
<p>...</p>
</div>
</Layout>
);
};
export default WechatAccounts;

View File

@@ -0,0 +1,185 @@
.workspace {
padding: 16px;
background-color: #f5f5f5;
min-height: 100vh;
}
.statsGrid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-bottom: 24px;
}
.statsCard {
background: #fff;
border-radius: 12px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
:global(.adm-card-body) {
padding: 0;
}
}
.statsTitle {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.statsValue {
font-size: 28px;
font-weight: 700;
color: var(--primary-color);
margin-bottom: 12px;
}
.progress {
margin-bottom: 8px;
:global(.adm-progress-bar) {
background-color: #f0f0f0;
}
:global(.adm-progress-bar-fill) {
background-color: var(--primary-color);
}
}
.statsSubtitle {
font-size: 12px;
color: #999;
}
.activityRate {
display: flex;
align-items: center;
font-size: 14px;
color: #52c41a;
margin-top: 8px;
}
.activityIcon {
width: 16px;
height: 16px;
margin-right: 4px;
color: #52c41a;
}
.section {
margin-bottom: 24px;
}
.sectionTitle {
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 16px;
padding-left: 4px;
}
.featuresGrid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.featureLink {
text-decoration: none;
color: inherit;
}
.featureCard {
background: #fff;
border-radius: 12px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
transition: all 0.2s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
:global(.adm-card-body) {
padding: 0;
}
}
.featureIcon {
width: 40px;
height: 40px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 12px;
}
.icon {
font-size: 20px;
}
.featureHeader {
display: flex;
align-items: center;
margin-bottom: 4px;
}
.featureName {
font-size: 16px;
font-weight: 600;
color: #333;
}
.newBadge {
margin-left: 8px;
:global(.adm-badge-content) {
background-color: var(--primary-color);
color: #fff;
font-size: 10px;
padding: 2px 6px;
border-radius: 10px;
}
}
.featureDescription {
font-size: 12px;
color: #666;
line-height: 1.4;
}
// 响应式设计
@media (max-width: 375px) {
.workspace {
padding: 12px;
}
.statsGrid {
gap: 8px;
}
.featuresGrid {
gap: 8px;
}
.statsCard,
.featureCard {
padding: 12px;
}
.statsValue {
font-size: 24px;
}
.featureIcon {
width: 36px;
height: 36px;
}
.icon {
font-size: 18px;
}
}

View File

@@ -0,0 +1,254 @@
import React from "react";
import { Link } from "react-router-dom";
import { Card, NavBar, Badge, Progress } from "antd-mobile";
import {
ThumbsUpOutline,
MessageOutline,
SendOutline,
TeamOutline,
ShareOutline,
AppOutline,
BarChartOutline,
LineChartOutline,
ClockCircleOutline,
} from "antd-mobile-icons";
import Layout from "@/components/Layout/Layout";
import MeauMobile from "@/components/MeauMobile/MeauMoible";
import styles from "./Workspace.module.scss";
const Workspace: React.FC = () => {
// 模拟任务数据
const taskStats = {
total: 42,
inProgress: 12,
completed: 30,
todayTasks: 12,
activityRate: 98,
};
// 常用功能
const commonFeatures = [
{
id: "auto-like",
name: "自动点赞",
description: "智能自动点赞互动",
icon: (
<ThumbsUpOutline className={styles.icon} style={{ color: "#ff4d4f" }} />
),
path: "/workspace/auto-like",
bgColor: "#fff2f0",
isNew: true,
},
{
id: "moments-sync",
name: "朋友圈同步",
description: "自动同步朋友圈内容",
icon: (
<ClockCircleOutline
className={styles.icon}
style={{ color: "#722ed1" }}
/>
),
path: "/workspace/moments-sync",
bgColor: "#f9f0ff",
},
{
id: "group-push",
name: "群消息推送",
description: "智能群发助手",
icon: (
<SendOutline className={styles.icon} style={{ color: "#fa8c16" }} />
),
path: "/workspace/group-push",
bgColor: "#fff7e6",
},
{
id: "auto-group",
name: "自动建群",
description: "智能拉好友建群",
icon: (
<TeamOutline className={styles.icon} style={{ color: "#52c41a" }} />
),
path: "/workspace/auto-group",
bgColor: "#f6ffed",
},
{
id: "traffic-distribution",
name: "流量分发",
description: "管理流量分发和分配",
icon: (
<ShareOutline className={styles.icon} style={{ color: "#1890ff" }} />
),
path: "/workspace/traffic-distribution",
bgColor: "#e6f7ff",
},
{
id: "ai-assistant",
name: "AI对话助手",
description: "智能回复,提高互动质量",
icon: (
<MessageOutline className={styles.icon} style={{ color: "#1890ff" }} />
),
path: "/workspace/ai-assistant",
bgColor: "#e6f7ff",
isNew: true,
},
];
// AI智能助手
const aiFeatures = [
{
id: "ai-analyzer",
name: "AI数据分析",
description: "智能分析客户行为特征",
icon: (
<BarChartOutline className={styles.icon} style={{ color: "#531dab" }} />
),
path: "/workspace/ai-analyzer",
bgColor: "#f0f0ff",
isNew: true,
},
{
id: "ai-strategy",
name: "AI策略优化",
description: "智能优化获客策略",
icon: <AppOutline className={styles.icon} style={{ color: "#13c2c2" }} />,
path: "/workspace/ai-strategy",
bgColor: "#e6fffb",
isNew: true,
},
{
id: "ai-forecast",
name: "AI销售预测",
description: "智能预测销售趋势",
icon: (
<LineChartOutline
className={styles.icon}
style={{ color: "#d48806" }}
/>
),
path: "/workspace/ai-forecast",
bgColor: "#fffbe6",
},
];
return (
<Layout
header={
<NavBar back={null} style={{ background: "#fff" }}>
<div style={{ color: "var(--primary-color)", fontWeight: 600 }}>
</div>
</NavBar>
}
footer={<MeauMobile />}
>
<div className={styles.workspace}>
{/* 任务统计卡片 */}
<div className={styles.statsGrid}>
<Card className={styles.statsCard}>
<div className={styles.statsTitle}></div>
<div className={styles.statsValue}>{taskStats.total}</div>
<Progress
percent={(taskStats.inProgress / taskStats.total) * 100}
className={styles.progress}
/>
<div className={styles.statsSubtitle}>
: {taskStats.inProgress} / : {taskStats.completed}
</div>
</Card>
<Card className={styles.statsCard}>
<div className={styles.statsTitle}></div>
<div className={styles.statsValue} style={{ color: "#52c41a" }}>
{taskStats.todayTasks}
</div>
<div className={styles.activityRate}>
<svg
className={styles.activityIcon}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3 12H7L10 19L14 5L17 12H21"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<span> {taskStats.activityRate}%</span>
</div>
</Card>
</div>
{/* 常用功能 */}
<div className={styles.section}>
<h2 className={styles.sectionTitle}></h2>
<div className={styles.featuresGrid}>
{commonFeatures.map((feature) => (
<Link
to={feature.path}
key={feature.id}
className={styles.featureLink}
>
<Card className={styles.featureCard}>
<div
className={styles.featureIcon}
style={{ backgroundColor: feature.bgColor }}
>
{feature.icon}
</div>
<div className={styles.featureHeader}>
<div className={styles.featureName}>{feature.name}</div>
{feature.isNew && (
<Badge content="New" className={styles.newBadge} />
)}
</div>
<div className={styles.featureDescription}>
{feature.description}
</div>
</Card>
</Link>
))}
</div>
</div>
{/* AI智能助手 */}
<div className={styles.section}>
<h2 className={styles.sectionTitle}>AI </h2>
<div className={styles.featuresGrid}>
{aiFeatures.map((feature) => (
<Link
to={feature.path}
key={feature.id}
className={styles.featureLink}
>
<Card className={styles.featureCard}>
<div
className={styles.featureIcon}
style={{ backgroundColor: feature.bgColor }}
>
{feature.icon}
</div>
<div className={styles.featureHeader}>
<div className={styles.featureName}>{feature.name}</div>
{feature.isNew && (
<Badge content="New" className={styles.newBadge} />
)}
</div>
<div className={styles.featureDescription}>
{feature.description}
</div>
</Card>
</Link>
))}
</div>
</div>
</div>
</Layout>
);
};
export default Workspace;

View File

@@ -0,0 +1,8 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const AIAssistant: React.FC = () => {
return <PlaceholderPage title="AI助手" showBack={false} />;
};
export default AIAssistant;

View File

@@ -0,0 +1,10 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const AutoGroup: React.FC = () => {
return (
<PlaceholderPage title="自动分组" showAddButton addButtonText="新建分组" />
);
};
export default AutoGroup;

View File

@@ -0,0 +1,8 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const AutoGroupDetail: React.FC = () => {
return <PlaceholderPage title="自动分组详情" />;
};
export default AutoGroupDetail;

View File

@@ -0,0 +1,38 @@
import React from "react";
import { NavBar, Button } from "antd-mobile";
import { AddOutline } from "antd-mobile-icons";
import Layout from "@/components/Layout/Layout";
import MeauMobile from "@/components/MeauMobile/MeauMoible";
const AutoLike: React.FC = () => {
return (
<Layout
header={
<NavBar
backArrow
style={{ background: "#fff" }}
onBack={() => window.history.back()}
left={
<div style={{ color: "var(--primary-color)", fontWeight: 600 }}>
</div>
}
right={
<Button size="small" color="primary">
<AddOutline />
<span style={{ marginLeft: 4, fontSize: 12 }}></span>
</Button>
}
/>
}
footer={<MeauMobile />}
>
<div style={{ padding: 20, textAlign: "center", color: "#666" }}>
<h3></h3>
<p>...</p>
</div>
</Layout>
);
};
export default AutoLike;

View File

@@ -0,0 +1,8 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const AutoLikeDetail: React.FC = () => {
return <PlaceholderPage title="自动点赞详情" />;
};
export default AutoLikeDetail;

View File

@@ -0,0 +1,30 @@
import React from "react";
import { NavBar } from "antd-mobile";
import Layout from "@/components/Layout/Layout";
import MeauMobile from "@/components/MeauMobile/MeauMoible";
const NewAutoLike: React.FC = () => {
return (
<Layout
header={
<NavBar
backArrow
style={{ background: "#fff" }}
onBack={() => window.history.back()}
>
<div style={{ color: "var(--primary-color)", fontWeight: 600 }}>
</div>
</NavBar>
}
footer={<MeauMobile />}
>
<div style={{ padding: 20, textAlign: "center", color: "#666" }}>
<h3></h3>
<p>...</p>
</div>
</Layout>
);
};
export default NewAutoLike;

View File

@@ -0,0 +1,10 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const GroupPush: React.FC = () => {
return (
<PlaceholderPage title="群发推送" showAddButton addButtonText="新建推送" />
);
};
export default GroupPush;

View File

@@ -0,0 +1,8 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const NewGroupPush: React.FC = () => {
return <PlaceholderPage title="新建群发推送" />;
};
export default NewGroupPush;

View File

@@ -0,0 +1,8 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const MomentsSyncDetail: React.FC = () => {
return <PlaceholderPage title="朋友圈同步详情" />;
};
export default MomentsSyncDetail;

View File

@@ -0,0 +1,14 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const MomentsSync: React.FC = () => {
return (
<PlaceholderPage
title="朋友圈同步"
showAddButton
addButtonText="新建同步"
/>
);
};
export default MomentsSync;

View File

@@ -0,0 +1,8 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const NewMomentsSync: React.FC = () => {
return <PlaceholderPage title="新建朋友圈同步" />;
};
export default NewMomentsSync;

View File

@@ -0,0 +1,8 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const TrafficDistributionDetail: React.FC = () => {
return <PlaceholderPage title="流量分发详情" />;
};
export default TrafficDistributionDetail;

View File

@@ -0,0 +1,8 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const NewDistribution: React.FC = () => {
return <PlaceholderPage title="新建流量分发" />;
};
export default NewDistribution;

View File

@@ -0,0 +1,10 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const TrafficDistribution: React.FC = () => {
return (
<PlaceholderPage title="流量分发" showAddButton addButtonText="新建分发" />
);
};
export default TrafficDistribution;