feat(web): 实现移动页面登录功能

- 新增登录弹窗和相关逻辑
- 添加验证码获取和验证功能
- 优化应用信息和聊天历史的获取流程
- 调整页面布局和样式
main
Tuzki 4 months ago
parent 7e65a15c86
commit ed2a4a023b
  1. 1
      web/components/knowledge/space-form.tsx
  2. 8
      web/hooks/use-user.ts
  3. 14
      web/pages/_app.tsx
  4. 2
      web/pages/construct/app/components/create-app-modal/index.tsx
  5. 6
      web/pages/construct/app/index.tsx
  6. 1
      web/pages/login.tsx
  7. 4
      web/pages/mobile/chat/components/Header.tsx
  8. 202
      web/pages/mobile/chat/index.tsx

@ -35,7 +35,6 @@ export default function SpaceForm(props: IProps) {
};
const handleFinish = async (fieldsValue: FieldType) => {
debugger;
const { spaceName, owner, description, storage, field } = fieldsValue;
setSpinning(true);
const vector_type = storage;

@ -1,7 +1,13 @@
import { STORAGE_USERINFO_KEY } from '@/utils/constants/index';
const useUser = () => {
return JSON.parse(localStorage.getItem(STORAGE_USERINFO_KEY) ?? '');
const users = localStorage.getItem(STORAGE_USERINFO_KEY);
if(users){
return JSON.parse(users);
}else{
return null;
}
// return JSON.parse(localStorage.getItem(STORAGE_USERINFO_KEY) ?? '');
};
export default useUser;

@ -82,13 +82,15 @@ function LayoutWrapper({ children }: { children: React.ReactNode }) {
}
// 检查是否在登录页面
const isLoginPage = router.pathname === '/login';
const isSharePage = router.pathname === '/mobile/chat';
const isLoggedIn = !!localStorage.getItem(STORAGE_USERINFO_KEY);
if (!isLoggedIn && !isLoginPage) {
router.push('/login');
} else if (isLoggedIn && isLoginPage) {
// 已登录访问登录页自动跳转
router.push('/playground/appGround');
if (!isSharePage) {
if (!isLoggedIn && !isLoginPage) {
router.push('/login');
} else if (isLoggedIn && isLoginPage) {
// 已登录访问登录页自动跳转
router.push('/playground/appGround');
}
}
setAuthChecked(true); // 必须设置检查完成标记
// 将isLogin状态设置为false

@ -195,7 +195,6 @@ const CreateAppModal: React.FC<{
const { run: generateAiImage, loading: aiImageLoading } = useRequest(
async (params) => {
const [err, res,response] = await apiInterceptors(getAiImage(params));
debugger;
if (!err && response?.success) {
return response;
}
@ -205,7 +204,6 @@ const CreateAppModal: React.FC<{
{
manual: true,
onSuccess: async (response) => {
debugger;
if (response?.url) {
const [err, blob] = await apiInterceptors(getImage(response.url));
if (!err && blob instanceof Blob) {

@ -317,7 +317,7 @@ export default function AppContent() {
}
const timeoutId = setTimeout(() => {
const mobileUrl = `${location.origin}/mobile/chat/?chat_scene=${item?.team_context?.chat_scene || 'chat_agent'}&app_code=${item.app_code}`;
const dingDingUrl = `dingtalk://dingtalkclient/page/link?url=${encodeURIComponent(mobileUrl)}&pc_slide=true`;
const dingDingUrl = `${mobileUrl}&pc_slide=true`;
const result = copy(dingDingUrl);
if (result) {
message.success('复制成功');
@ -336,7 +336,7 @@ export default function AppContent() {
setClickTimeout(null);
}
const mobileUrl = `${location.origin}/mobile/chat/?chat_scene=${item?.team_context?.chat_scene || 'chat_agent'}&app_code=${item.app_code}`;
const dingDingUrl = `dingtalk://dingtalkclient/page/link?url=${encodeURIComponent(mobileUrl)}&pc_slide=true`;
const dingDingUrl = `${mobileUrl}&pc_slide=true`;
window.open(dingDingUrl);
};
@ -423,13 +423,11 @@ export default function AppContent() {
};
const [err, data] = await apiInterceptors(updateShareApp(updateParams));
res = { err, data };
debugger;
} else {
// 新增模式
const [err, data] = await apiInterceptors(shareApp(params));
res = { err, data };
debugger;
}
if (!res.err && res.data || !res.err && res) {

@ -11,6 +11,7 @@ const imgSrc =
const LoginPage: FC = () => {
const router = useRouter();
console.log('router:', router); // ✅ 打印路由信息
const [form] = Form.useForm(); // ✅ 新增 form 实例
const [verifyCodeImage, setVerifyCodeImage] = useState<string>(imgSrc);
const [catcha_id, setCaptcha_id] = useState<string>('');

@ -35,12 +35,12 @@ const Header: React.FC = () => {
<Typography.Text className='text-sm line-clamp-2'>{appInfo?.app_describe}</Typography.Text>
</div>
</div>
<div
{/* <div
onClick={shareApp}
className='flex items-center justify-center w-10 h-10 bg-[#ffffff99] dark:bg-[rgba(255,255,255,0.2)] border border-white dark:border-[rgba(255,255,255,0.2)] rounded-[50%] cursor-pointer'
>
<ExportOutlined className='text-lg' />
</div>
</div> */}
</header>
);
};

@ -1,5 +1,5 @@
import { ChatContext } from '@/app/chat-context';
import { apiInterceptors, getAppInfo, getChatHistory, getDialogueList, postChatModeParamsList } from '@/client/api';
import { apiInterceptors, getAppInfo, getChatHistory, getDialogueList, postChatModeParamsList, doLogin, getVerifyCode } from '@/client/api';
import useUser from '@/hooks/use-user';
import { IApp } from '@/types/app';
import { ChatHistoryResponse } from '@/types/chat';
@ -7,13 +7,15 @@ import { getUserId } from '@/utils';
import { HEADER_USER_ID_KEY } from '@/utils/constants/index';
import { EventStreamContentType, fetchEventSource } from '@microsoft/fetch-event-source';
import { useRequest } from 'ahooks';
import { Spin } from 'antd';
import { Spin, Modal, Input, message } from 'antd';
import dynamic from 'next/dynamic';
import { useSearchParams } from 'next/navigation';
import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
import Header from './components/Header';
import InputContainer from './components/InputContainer';
import { STORAGE_USERINFO_KEY, STORAGE_USERINFO_VALID_TIME_KEY } from '@/utils/constants/index';
const Content = dynamic(() => import('@/pages/mobile/chat/components/Content'), { ssr: false });
interface MobileChatProps {
@ -46,12 +48,12 @@ export const MobileChatContext = createContext<MobileChatProps>({
model: '',
temperature: 0.5,
resource: null,
setModel: () => {},
setTemperature: () => {},
setResource: () => {},
setModel: () => { },
setTemperature: () => { },
setResource: () => { },
scene: '',
history: [],
setHistory: () => {},
setHistory: () => { },
scrollViewRef: { current: null },
appInfo: {} as IApp,
conv_uid: '',
@ -59,16 +61,84 @@ export const MobileChatContext = createContext<MobileChatProps>({
order: { current: 1 },
handleChat: () => Promise.resolve(),
canAbort: false,
setCarAbort: () => {},
setCarAbort: () => { },
canNewChat: false,
setCanNewChat: () => {},
setCanNewChat: () => { },
ctrl: { current: undefined },
userInput: '',
setUserInput: () => {},
getChatHistoryRun: () => {},
setUserInput: () => { },
getChatHistoryRun: () => { },
});
const MobileChat: React.FC = () => {
const [isLoginReady, setIsLoginReady] = useState(false);
const [loginVisible, setLoginVisible] = useState<boolean>(false);
const [captchaImage, setCaptchaImage] = useState<string>('');
const [captchaId, setCaptchaId] = useState<string>('');
const [captchaInput, setCaptchaInput] = useState<string>('');
// 初始检测是否有token,没有就弹窗登录
useEffect(() => {
const token = localStorage.getItem(STORAGE_USERINFO_KEY);
if (!token) {
fetchCaptcha(); // 拉验证码
setLoginVisible(true);
} else {
getAppInfoRun({ chat_scene: chatScene, app_code: appCode });
}
}, []);
const fetchCaptcha = async () => {
try {
const [, res] = await apiInterceptors(getVerifyCode());
setCaptchaImage(res?.captcha_image);
setCaptchaId(res?.captcha_id);
} catch {
message.error('验证码获取失败');
}
};
const handleLogin = async () => {
try {
const [, res] = await apiInterceptors(
doLogin({
username: 'cjyadmin',
password: 'Cjy@1303!',
catcha_id: captchaId,
verificationCode: captchaInput,
})
);
;
if (res) {
const user = {
token: res,
user_channel: `CJY`,
user_no: `001`,
nick_name: 'cjyadmin',
};
localStorage.setItem(STORAGE_USERINFO_KEY, JSON.stringify(user));
localStorage.setItem(STORAGE_USERINFO_VALID_TIME_KEY, Date.now().toString());
message.success('验证码已通过');
setLoginVisible(false);
setIsLoginReady(true); // 登录成功之后设置为true
// 登录成功后再执行初始化接口
getAppInfoRun({ chat_scene: chatScene, app_code: appCode });
getChatHistoryRun();
}
else {
fetchCaptcha(); // 登录失败刷新验证码
}
} catch {
message.error('登录失败,请重试');
fetchCaptcha(); // 登录失败刷新验证码
}
};
// 从url上获取基本参数
const searchParams = useSearchParams();
const chatScene = searchParams?.get('chat_scene') ?? '';
@ -161,16 +231,17 @@ const MobileChat: React.FC = () => {
// 获取应用信息
useEffect(() => {
if (chatScene && appCode && modelList.length) {
if (isLoginReady && chatScene && appCode && modelList.length) {
getAppInfoRun({ chat_scene: chatScene, app_code: appCode });
}
}, [appCode, chatScene, getAppInfoRun, modelList]);
}, [isLoginReady, chatScene, appCode, modelList]);
// 设置历史会话记录
useEffect(() => {
appCode && getChatHistoryRun();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [appCode]);
if (isLoginReady && appCode) {
getChatHistoryRun();
}
}, [isLoginReady, appCode]);
// 设置默认模型
useEffect(() => {
@ -294,53 +365,74 @@ const MobileChat: React.FC = () => {
// 如果是原生应用,拉取会话列表获取资源参数
useEffect(() => {
if (chatScene && chatScene !== 'chat_agent') {
if (isLoginReady && chatScene && chatScene !== 'chat_agent') {
getDialogueListRun();
}
}, [chatScene, getDialogueListRun]);
}, [isLoginReady, chatScene]);
return (
<MobileChatContext.Provider
value={{
model,
resource,
setModel,
setTemperature,
setResource,
temperature,
appInfo: appInfo as IApp,
conv_uid,
scene: chatScene,
history,
scrollViewRef,
setHistory,
resourceList: data,
order,
handleChat,
setCanNewChat,
ctrl,
canAbort,
setCarAbort,
canNewChat,
userInput,
setUserInput,
getChatHistoryRun,
}}
>
<Spin
size='large'
className='flex h-screen w-screen justify-center items-center max-h-screen'
spinning={historyLoading || appInfoLoading || resourceLoading || dialogueListLoading}
<>
<MobileChatContext.Provider
value={{
model,
resource,
setModel,
setTemperature,
setResource,
temperature,
appInfo: appInfo as IApp,
conv_uid,
scene: chatScene,
history,
scrollViewRef,
setHistory,
resourceList: data,
order,
handleChat,
setCanNewChat,
ctrl,
canAbort,
setCarAbort,
canNewChat,
userInput,
setUserInput,
getChatHistoryRun,
}}
>
<div className='flex flex-col h-screen bg-gradient-light dark:bg-gradient-dark p-4 pt-0'>
<div ref={scrollViewRef} className='flex flex-col flex-1 overflow-y-auto mb-3'>
<Header />
<Content />
<Spin
size='large'
className='flex h-screen w-screen justify-center items-center max-h-screen'
spinning={historyLoading || appInfoLoading || resourceLoading || dialogueListLoading}
>
<div className='flex flex-col h-screen bg-gradient-light dark:bg-gradient-dark p-4 pt-0'>
<div ref={scrollViewRef} className='flex flex-col flex-1 overflow-y-auto mb-3'>
<Header />
<Content />
</div>
{appInfo?.app_code && <InputContainer />}
</div>
{appInfo?.app_code && <InputContainer />}
</Spin>
</MobileChatContext.Provider>
<Modal
open={loginVisible}
title="请输入验证码"
onOk={handleLogin}
onCancel={() => { }}
okText="确定"
cancelButtonProps={{ style: { display: 'none' } }}
>
<div style={{ display: 'flex', alignItems: 'center' }}>
<img src={captchaImage} alt="验证码" style={{ height: 40, marginRight: 10 }} />
<Input
placeholder="输入验证码"
value={captchaInput}
onChange={(e) => setCaptchaInput(e.target.value)}
maxLength={6}
/>
</div>
</Spin>
</MobileChatContext.Provider>
</Modal>
</>
);
};

Loading…
Cancel
Save