feat: 重构登录模块并添加验证码功能
- 将登录组件文件名从login.tsx改为Login.tsx - 添加验证码图片显示和验证功能 - 修改登录后默认跳转路径为/ckbox/weChat - 移除第三方登录功能 - 更新项目部署路径为tkb-wechat - 清理无用路由模块
This commit is contained in:
@@ -9,7 +9,12 @@ import {
|
||||
import { useUserStore } from "@/store/module/user";
|
||||
import { useWebSocketStore } from "@/store/module/websocket/websocket";
|
||||
|
||||
import { loginWithPassword, loginWithCode, sendVerificationCode } from "./api";
|
||||
import {
|
||||
loginWithPassword,
|
||||
loginWithCode,
|
||||
sendVerificationCode,
|
||||
getVerifyCode,
|
||||
} from "./api";
|
||||
import style from "./login.module.scss";
|
||||
|
||||
const Login: React.FC = () => {
|
||||
@@ -21,7 +26,13 @@ const Login: React.FC = () => {
|
||||
const [agreeToTerms, setAgreeToTerms] = useState(false);
|
||||
const { setUserInfo } = useCkChatStore.getState();
|
||||
const { login, login2 } = useUserStore();
|
||||
|
||||
const [verify, setVerify] = useState<{
|
||||
verifyCodeImage: string;
|
||||
verifyCode: string;
|
||||
}>({
|
||||
verifyCodeImage: "",
|
||||
verifyCode: "",
|
||||
});
|
||||
// 倒计时效果
|
||||
useEffect(() => {
|
||||
if (countdown > 0) {
|
||||
@@ -30,7 +41,18 @@ const Login: React.FC = () => {
|
||||
}
|
||||
}, [countdown]);
|
||||
|
||||
// 发送验证码
|
||||
const getVerifyCodeFunction = async () => {
|
||||
const res = await getVerifyCode();
|
||||
setVerify({
|
||||
verifyCodeImage: res.verifyCodeImage,
|
||||
verifyCode: res.verifyCode,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getVerifyCodeFunction();
|
||||
}, []);
|
||||
|
||||
const handleSendVerificationCode = async () => {
|
||||
const account = form.getFieldValue("account");
|
||||
|
||||
@@ -74,15 +96,19 @@ const Login: React.FC = () => {
|
||||
? loginWithPassword(loginParams)
|
||||
: loginWithCode(loginParams);
|
||||
|
||||
response.then(res => {
|
||||
const { member, kefuData, deviceTotal } = res;
|
||||
// 清空WebSocket连接状态
|
||||
useWebSocketStore.getState().clearConnectionState();
|
||||
login(res.token, member, deviceTotal);
|
||||
const { self, token } = kefuData;
|
||||
login2(token.access_token);
|
||||
setUserInfo(self);
|
||||
});
|
||||
response
|
||||
.then(res => {
|
||||
const { member, kefuData, deviceTotal } = res;
|
||||
// 清空WebSocket连接状态
|
||||
useWebSocketStore.getState().clearConnectionState();
|
||||
login(res.token, member, deviceTotal);
|
||||
const { self, token } = kefuData;
|
||||
login2(token.access_token);
|
||||
setUserInfo(self);
|
||||
})
|
||||
.catch(() => {
|
||||
getVerifyCodeFunction();
|
||||
});
|
||||
};
|
||||
|
||||
// 登录处理
|
||||
@@ -91,18 +117,10 @@ const Login: React.FC = () => {
|
||||
Toast.show({ content: "请同意用户协议和隐私政策", position: "top" });
|
||||
return;
|
||||
}
|
||||
//获取存客宝
|
||||
//获取触客宝
|
||||
getToken(values);
|
||||
};
|
||||
|
||||
// 第三方登录处理
|
||||
const handleWechatLogin = () => {
|
||||
Toast.show({ content: "微信登录功能开发中", position: "top" });
|
||||
};
|
||||
|
||||
const handleAppleLogin = () => {
|
||||
Toast.show({ content: "Apple登录功能开发中", position: "top" });
|
||||
};
|
||||
const paddingTop = localStorage.getItem("paddingTop") || "44px";
|
||||
return (
|
||||
<div className={style["login-page"]}>
|
||||
@@ -122,7 +140,7 @@ const Login: React.FC = () => {
|
||||
<div className={style["logo-icon"]}>
|
||||
<UserOutline />
|
||||
</div>
|
||||
<h1 className={style["app-name"]}>存客宝</h1>
|
||||
<h1 className={style["app-name"]}>触客宝</h1>
|
||||
</div>
|
||||
<p className={style["subtitle"]}>登录您的账户继续使用</p>
|
||||
</div>
|
||||
@@ -163,19 +181,12 @@ const Login: React.FC = () => {
|
||||
{/* 手机号输入 */}
|
||||
<Form.Item
|
||||
name="account"
|
||||
label="手机号"
|
||||
rules={[
|
||||
{ required: true, message: "请输入手机号" },
|
||||
{
|
||||
pattern: /^1[3-9]\d{9}$/,
|
||||
message: "请输入正确的11位手机号",
|
||||
},
|
||||
]}
|
||||
label="账号"
|
||||
rules={[{ required: true, message: "请输入账号" }]}
|
||||
>
|
||||
<div className={style["input-wrapper"]}>
|
||||
<span className={style["input-prefix"]}>+86</span>
|
||||
<Input
|
||||
placeholder="请输入手机号"
|
||||
placeholder="请输入账号"
|
||||
clearable
|
||||
className={style["phone-input"]}
|
||||
/>
|
||||
@@ -205,8 +216,28 @@ const Login: React.FC = () => {
|
||||
</div>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
{/* 验证码输入 */}
|
||||
{activeTab == 1 && verify.verifyCodeImage && (
|
||||
<Form.Item
|
||||
name="verificationCode"
|
||||
label="验证码"
|
||||
rules={[{ required: true, message: "请输入验证码" }]}
|
||||
>
|
||||
<div className={style["input-wrapper"]}>
|
||||
<Input
|
||||
placeholder="请输入验证码"
|
||||
clearable
|
||||
className={style["code-input"]}
|
||||
/>
|
||||
<img
|
||||
className={style["verify-code-img"]}
|
||||
src={verify.verifyCodeImage}
|
||||
alt="验证码"
|
||||
onClick={getVerifyCodeFunction}
|
||||
/>
|
||||
</div>
|
||||
</Form.Item>
|
||||
)}
|
||||
{/* 验证码输入2 */}
|
||||
{activeTab === 2 && (
|
||||
<Form.Item
|
||||
name="verificationCode"
|
||||
@@ -243,7 +274,7 @@ const Login: React.FC = () => {
|
||||
<span className={style["agreement-text"]}>
|
||||
我已阅读并同意
|
||||
<span className={style["agreement-link"]}>
|
||||
《存客宝用户协议》
|
||||
《触客宝用户协议》
|
||||
</span>
|
||||
和
|
||||
<span className={style["agreement-link"]}>《隐私政策》</span>
|
||||
@@ -263,44 +294,6 @@ const Login: React.FC = () => {
|
||||
{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>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import request from "@/api/request";
|
||||
import request2 from "@/api/request2";
|
||||
|
||||
// 密码登录
|
||||
export function loginWithPassword(params: any) {
|
||||
return request("/v1/auth/login", params, "POST");
|
||||
@@ -49,3 +50,8 @@ export function loginWithToken(params: any) {
|
||||
export function getChuKeBaoUserInfo() {
|
||||
return request2("/api/account/self", {}, "GET");
|
||||
}
|
||||
|
||||
//验证码
|
||||
export function getVerifyCode() {
|
||||
return request("/v1/api/user/verify-code", {}, "GET");
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
.verify-code-img {
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
background: #ccc;
|
||||
}
|
||||
.login-page {
|
||||
min-height: 100vh;
|
||||
background: var(--primary-gradient);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Login from "@/pages/login/login";
|
||||
import Login from "@/pages/login/Login";
|
||||
import Guide from "@/pages/guide";
|
||||
|
||||
const authRoutes = [
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import ContentLibraryList from "@/pages/mobile/mine/content/list/index";
|
||||
import ContentLibraryForm from "@/pages/mobile/mine/content/form/index";
|
||||
import MaterialsList from "@/pages/mobile/mine/content/materials/list/index";
|
||||
import MaterialForm from "@/pages/mobile/mine/content/materials/form/index";
|
||||
|
||||
const contentRoutes = [
|
||||
{
|
||||
path: "/mine/content",
|
||||
element: <ContentLibraryList />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/mine/content/new",
|
||||
element: <ContentLibraryForm />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/mine/content/edit/:id",
|
||||
element: <ContentLibraryForm />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/mine/content/materials/:id",
|
||||
element: <MaterialsList />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/mine/content/materials/new/:id",
|
||||
element: <MaterialForm />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/mine/content/materials/edit/:id/:materialId",
|
||||
element: <MaterialForm />,
|
||||
auth: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default contentRoutes;
|
||||
0
Touchkebao/src/router/module/mobile.tsx
Normal file
0
Touchkebao/src/router/module/mobile.tsx
Normal file
@@ -1,33 +0,0 @@
|
||||
import ScenariosList from "@/pages/mobile/scenarios/list";
|
||||
import NewPlan from "@/pages/mobile/scenarios/plan/new";
|
||||
import ListPlan from "@/pages/mobile/scenarios/plan/list";
|
||||
|
||||
const scenarioRoutes = [
|
||||
{
|
||||
path: "/scenarios",
|
||||
element: <ScenariosList />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/scenarios/new",
|
||||
element: <NewPlan />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/scenarios/new/:scenarioId",
|
||||
element: <NewPlan />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/scenarios/edit/:planId",
|
||||
element: <NewPlan />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/scenarios/list/:scenarioId/:scenarioName",
|
||||
element: <ListPlan />,
|
||||
auth: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default scenarioRoutes;
|
||||
@@ -1,17 +0,0 @@
|
||||
import WechatAccounts from "@/pages/mobile/mine/wechat-accounts/list";
|
||||
import WechatAccountDetail from "@/pages/mobile/mine/wechat-accounts/detail";
|
||||
|
||||
const wechatAccountRoutes = [
|
||||
{
|
||||
path: "/wechat-accounts",
|
||||
element: <WechatAccounts />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/wechat-accounts/detail/:id",
|
||||
element: <WechatAccountDetail />,
|
||||
auth: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default wechatAccountRoutes;
|
||||
@@ -1,183 +0,0 @@
|
||||
import Workspace from "@/pages/mobile/workspace/main";
|
||||
import ListAutoLike from "@/pages/mobile/workspace/auto-like/list";
|
||||
import NewAutoLike from "@/pages/mobile/workspace/auto-like/new";
|
||||
import RecordAutoLike from "@/pages/mobile/workspace/auto-like/record";
|
||||
import AutoGroupList from "@/pages/mobile/workspace/auto-group/list";
|
||||
import AutoGroupDetail from "@/pages/mobile/workspace/auto-group/detail";
|
||||
import AutoGroupForm from "@/pages/mobile/workspace/auto-group/form";
|
||||
import GroupPush from "@/pages/mobile/workspace/group-push/list";
|
||||
import FormGroupPush from "@/pages/mobile/workspace/group-push/form";
|
||||
import DetailGroupPush from "@/pages/mobile/workspace/group-push/detail";
|
||||
import MomentsSync from "@/pages/mobile/workspace/moments-sync/list";
|
||||
import NewMomentsSync from "@/pages/mobile/workspace/moments-sync/new/index";
|
||||
import MomentsSyncRecord from "@/pages/mobile/workspace/moments-sync/record";
|
||||
import AIAssistant from "@/pages/mobile/workspace/ai-assistant/AIAssistant";
|
||||
import TrafficDistribution from "@/pages/mobile/workspace/traffic-distribution/list/index";
|
||||
import TrafficDistributionDetail from "@/pages/mobile/workspace/traffic-distribution/detail/index";
|
||||
import NewDistribution from "@/pages/mobile/workspace/traffic-distribution/form/index";
|
||||
import ContactImportList from "@/pages/mobile/workspace/contact-import/list";
|
||||
import ContactImportForm from "@/pages/mobile/workspace/contact-import/form";
|
||||
import ContactImportDetail from "@/pages/mobile/workspace/contact-import/detail";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
import AiAnalyzer from "@/pages/mobile/workspace/ai-analyzer";
|
||||
|
||||
const workspaceRoutes = [
|
||||
{
|
||||
path: "/workspace",
|
||||
element: <Workspace />,
|
||||
auth: true,
|
||||
},
|
||||
// 自动点赞
|
||||
{
|
||||
path: "/workspace/auto-like",
|
||||
element: <ListAutoLike />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/auto-like/new",
|
||||
element: <NewAutoLike />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/auto-like/record/:id",
|
||||
element: <RecordAutoLike />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/auto-like/edit/:id",
|
||||
element: <NewAutoLike />,
|
||||
auth: true,
|
||||
},
|
||||
// 自动建群
|
||||
{
|
||||
path: "/workspace/auto-group",
|
||||
element: <AutoGroupList />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/auto-group/new",
|
||||
element: <AutoGroupForm />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/auto-group/:id",
|
||||
element: <AutoGroupDetail />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/auto-group/:id/edit",
|
||||
element: <AutoGroupForm />,
|
||||
auth: true,
|
||||
},
|
||||
// 群发推送
|
||||
{
|
||||
path: "/workspace/group-push",
|
||||
element: <GroupPush />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/group-push/:id",
|
||||
element: <DetailGroupPush />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/group-push/new",
|
||||
element: <FormGroupPush />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/group-push/edit/:id",
|
||||
element: <FormGroupPush />,
|
||||
auth: true,
|
||||
},
|
||||
// 朋友圈同步
|
||||
{
|
||||
path: "/workspace/moments-sync",
|
||||
element: <MomentsSync />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/moments-sync/new",
|
||||
element: <NewMomentsSync />,
|
||||
auth: true,
|
||||
},
|
||||
|
||||
{
|
||||
path: "/workspace/moments-sync/record/:id",
|
||||
element: <MomentsSyncRecord />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/moments-sync/edit/:id",
|
||||
element: <NewMomentsSync />,
|
||||
auth: true,
|
||||
},
|
||||
// AI助手
|
||||
{
|
||||
path: "/workspace/ai-assistant",
|
||||
element: <AIAssistant />,
|
||||
auth: true,
|
||||
},
|
||||
// AI数据分析
|
||||
{
|
||||
path: "/workspace/ai-analyzer",
|
||||
element: <AiAnalyzer />,
|
||||
auth: true,
|
||||
},
|
||||
// AI策略优化
|
||||
{
|
||||
path: "/workspace/ai-strategy",
|
||||
element: <PlaceholderPage title="AI策略优化" />,
|
||||
auth: true,
|
||||
},
|
||||
// AI销售预测
|
||||
{
|
||||
path: "/workspace/ai-forecast",
|
||||
element: <PlaceholderPage title="AI销售预测" />,
|
||||
auth: true,
|
||||
},
|
||||
// 流量分发
|
||||
{
|
||||
path: "/workspace/traffic-distribution",
|
||||
element: <TrafficDistribution />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/traffic-distribution/new",
|
||||
element: <NewDistribution />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/traffic-distribution/edit/:id",
|
||||
element: <NewDistribution />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/traffic-distribution/:id",
|
||||
element: <TrafficDistributionDetail />,
|
||||
auth: true,
|
||||
},
|
||||
// 通讯录导入
|
||||
{
|
||||
path: "/workspace/contact-import/list",
|
||||
element: <ContactImportList />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/contact-import/form",
|
||||
element: <ContactImportForm />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/contact-import/form/:id",
|
||||
element: <ContactImportForm />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/contact-import/detail/:id",
|
||||
element: <ContactImportDetail />,
|
||||
auth: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default workspaceRoutes;
|
||||
@@ -69,14 +69,7 @@ export const useUserStore = createPersistStore<UserState>(
|
||||
set({ user, token, isLoggedIn: true });
|
||||
|
||||
Toast.show({ content: "登录成功", position: "top" });
|
||||
|
||||
// 根据设备数量判断跳转
|
||||
if (deviceTotal > 0) {
|
||||
window.location.href = "/";
|
||||
} else {
|
||||
// 没有设备,跳转到引导页面
|
||||
window.location.href = "/guide";
|
||||
}
|
||||
window.location.href = "/ckbox/weChat";
|
||||
},
|
||||
login2: token2 => {
|
||||
localStorage.setItem("token2", token2);
|
||||
|
||||
Reference in New Issue
Block a user