Commit c49ab07e authored by David Yang's avatar David Yang

feat: 应用切换

parent 3489b2a6
import { defineConfig } from '@umijs/max';
export default defineConfig({
base: 'ai',
publicPath: '/ai/',
base: '/ai_chat/',
publicPath: '/ai_chat/',
antd: {},
access: {},
model: {},
......@@ -33,7 +33,7 @@ export default defineConfig({
npmClient: 'pnpm',
proxy: {
'/api': {
target: 'https://awm.rsmchina.com.cn/api',
target: 'https://xy.streams7.com/api',
// target: 'http://localhost:3000',
changeOrigin: true,
pathRewrite: { '^/api': '' },
......
{
"name": "S-Agent",
"version": "1.0.26",
"build": "1"
"name": "ai_chat",
"version": "1.0.1",
"build": "abc",
"description": "merge master",
"date": "2025-07-14 19:27:20",
"author": "yang"
}
This diff is collapsed.
import { login } from '@/services/DataService';
import { LockOutlined, UserOutlined } from '@ant-design/icons';
import { getCaptcha, login } from '@/services/DataService';
import {
LockOutlined,
ReloadOutlined,
SafetyCertificateOutlined,
UserOutlined,
} from '@ant-design/icons';
import { LoginForm, ProFormText } from '@ant-design/pro-components';
import { Alert, Flex, Modal, Typography } from 'antd';
import { Alert, Button, Flex, Modal, Typography } from 'antd';
import { createStyles } from 'antd-style';
import { useState } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
const useStyle = createStyles(({ css }) => {
return {
......@@ -14,10 +19,10 @@ const useStyle = createStyles(({ css }) => {
overflow: hidden;
background: radial-gradient(
circle at top right,
rgba(125, 211, 252, 0.22),
rgba(251, 207, 232, 0.26),
transparent 20%
),
linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
linear-gradient(180deg, #ffffff 0%, #fff7f7 100%);
}
.ant-modal-body {
......@@ -33,7 +38,7 @@ const useStyle = createStyles(({ css }) => {
padding: 38px 34px;
background: radial-gradient(
circle at top left,
rgba(0, 166, 232, 0.22),
rgba(200, 30, 58, 0.22),
transparent 22%
),
linear-gradient(160deg, #0f172a 0%, #1e293b 100%);
......@@ -132,6 +137,8 @@ const useStyle = createStyles(({ css }) => {
height: 46px;
border-radius: 16px;
font-weight: 600;
background: linear-gradient(135deg, #c81e3a 0%, #9f1239 100%);
border: none;
}
`,
formInner: css`
......@@ -142,14 +149,76 @@ const useStyle = createStyles(({ css }) => {
margin-bottom: 20px;
border-radius: 14px;
`,
captchaRow: css`
display: grid;
grid-template-columns: minmax(0, 1fr) 128px;
gap: 12px;
align-items: flex-start;
.ant-form-item {
margin-bottom: 0;
}
`,
captchaButton: css`
height: 48px;
border-radius: 16px;
padding: 0 10px;
overflow: hidden;
border-color: #e2e8f0;
color: #64748b;
.ant-btn-icon {
display: inline-flex;
}
`,
captchaImage: css`
display: flex;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
svg {
width: 100%;
height: 100%;
display: block;
}
`,
};
});
export default (props) => {
const { onError, onSuccess } = props;
const [errorMessage, setErrorMessage] = useState(null);
const [captchaData, setCaptchaData] = useState(null);
const [captchaLoading, setCaptchaLoading] = useState(false);
const formRef = useRef();
const { styles } = useStyle();
const refreshCaptcha = useCallback(async () => {
setCaptchaLoading(true);
try {
const nextCaptcha = await getCaptcha();
setCaptchaData(nextCaptcha || null);
} catch (error) {
const nextErrorMessage =
error.response?.data?.message || error.message || '验证码加载失败';
setCaptchaData(null);
setErrorMessage(nextErrorMessage);
} finally {
setCaptchaLoading(false);
}
}, []);
useEffect(() => {
if (props.open) {
setErrorMessage(null);
formRef.current?.setFieldsValue({ captcha: undefined });
refreshCaptcha();
}
}, [props.open, refreshCaptcha]);
return (
<Modal
{...props}
......@@ -189,12 +258,18 @@ export default (props) => {
<div className={styles.formPane}>
<div className={styles.formInner}>
<LoginForm
formRef={formRef}
logo="https://oss.streams7.com/rsm/BfNsqGPGTKE2.svg"
title="智能助手"
subTitle="登录后继续使用统一 AI 对话工作台"
onFinish={async (form) => {
try {
const userInfo = await login(form.username, form.password);
const userInfo = await login(
form.username,
form.password,
form.captcha,
captchaData?.token,
);
localStorage.setItem(
'current_user',
......@@ -211,6 +286,8 @@ export default (props) => {
error.message ||
'网络错误';
setErrorMessage(nextErrorMessage);
formRef.current?.setFieldsValue({ captcha: undefined });
refreshCaptcha();
onError?.(nextErrorMessage, error);
}
}}
......@@ -244,6 +321,45 @@ export default (props) => {
},
]}
/>
<div className={styles.captchaRow}>
<ProFormText
name="captcha"
fieldProps={{
size: 'large',
prefix: (
<SafetyCertificateOutlined className={'prefixIcon'} />
),
}}
placeholder="验证码"
rules={[
{
required: true,
message: '请输入验证码',
},
]}
/>
<Button
htmlType="button"
className={styles.captchaButton}
loading={captchaLoading}
disabled={captchaLoading}
icon={captchaData?.image ? undefined : <ReloadOutlined />}
onClick={() => {
setErrorMessage(null);
formRef.current?.setFieldsValue({ captcha: undefined });
refreshCaptcha();
}}
>
{captchaData?.image ? (
<span
className={styles.captchaImage}
dangerouslySetInnerHTML={{ __html: captchaData.image }}
/>
) : (
'刷新'
)}
</Button>
</div>
{errorMessage && (
<Alert
className={styles.error}
......
......@@ -12,17 +12,19 @@ const GlobalStyle = createGlobalStyle`
--app-shell-bg:
radial-gradient(
circle at top left,
rgba(0, 156, 217, 0.16),
rgba(200, 30, 58, 0.14),
transparent 26%
),
radial-gradient(
circle at bottom right,
rgba(126, 34, 206, 0.08),
transparent 26%
circle at right center,
rgba(159, 18, 57, 0.08),
transparent 24%
),
linear-gradient(180deg, #f8fbff 0%, #eef4fb 100%);
linear-gradient(180deg, #fff7f7 0%, #fff1f2 100%);
--app-surface: rgba(255, 255, 255, 0.86);
--app-surface-strong: #ffffff;
--app-brand: #c81e3a;
--app-brand-strong: #9f1239;
--app-sidebar: #0f172a;
--app-sidebar-soft: rgba(30, 41, 59, 0.9);
--app-border: rgba(148, 163, 184, 0.2);
......@@ -38,7 +40,10 @@ const GlobalStyle = createGlobalStyle`
html,
body,
#root {
width: 100%;
height: 100%;
min-height: 100%;
overflow: hidden;
}
body {
......@@ -84,12 +89,12 @@ const GlobalStyle = createGlobalStyle`
export default () => {
const themeToken = defaultAlgorithm({
...defaultSeed,
colorPrimary: '#009cd9',
colorInfo: '#009cd9',
colorPrimary: '#c81e3a',
colorInfo: '#c81e3a',
colorSuccess: '#1d9b62',
colorWarning: '#d97706',
colorError: '#dc2626',
colorBgBase: '#f6f9fc',
colorBgBase: '#fff7f7',
colorBgContainer: '#ffffff',
colorTextBase: '#0f172a',
borderRadius: 18,
......@@ -121,7 +126,7 @@ export default () => {
},
Input: {
borderRadius: 14,
activeShadow: '0 0 0 3px rgba(0, 156, 217, 0.12)',
activeShadow: '0 0 0 3px rgba(200, 30, 58, 0.14)',
},
Modal: {
borderRadiusLG: 24,
......@@ -134,7 +139,7 @@ export default () => {
colorBgElevated: '#0f172a',
},
Steps: {
colorPrimary: '#009cd9',
colorPrimary: '#c81e3a',
},
},
}}
......
This diff is collapsed.
This diff is collapsed.
......@@ -12,6 +12,18 @@ export function getApplication(name) {
});
}
export function getSystemInfo() {
return request('/api/info', {
method: 'get',
});
}
export function getApplications() {
return request('/api/application/info', {
method: 'get',
});
}
export function getRecord(name) {
return request(`/api/record/${name}`, {
method: 'get',
......@@ -50,13 +62,21 @@ export function getUserInfo() {
return currentUser && JSON.parse(currentUser);
}
export async function login(username, password) {
export function getCaptcha() {
return request('/api/auth/captcha', {
method: 'get',
});
}
export async function login(username, password, captcha, token) {
await request('/api/auth/login', {
method: 'post',
data: {
type: 'password',
identity: username,
password: password,
captcha,
token,
},
});
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment