Skip to content

Commit 63866e4

Browse files
authored
Merge pull request #29 from guanweiwang/main
feat: 添加钉钉登录
2 parents 8087536 + 0da806d commit 63866e4

File tree

16 files changed

+469
-130
lines changed

16 files changed

+469
-130
lines changed

ui/scripts/downLoadIcon.cjs

Lines changed: 17 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,23 @@
1-
const fs = require('fs')
2-
const path = require('path')
3-
4-
const Axios = require('axios')
1+
const fs = require("fs");
2+
const path = require("path");
53

64
async function downloadFile(url) {
7-
try {
8-
const iconPath = path.resolve(__dirname, '../src/assets/fonts/iconfont.js')
9-
const iconDir = path.dirname(iconPath)
10-
11-
// 检查目录是否存在,如果不存在则创建
12-
if (!fs.existsSync(iconDir)) {
13-
console.log(`目录 ${iconDir} 不存在,正在创建...`)
14-
fs.mkdirSync(iconDir, { recursive: true })
15-
console.log('目录创建成功')
16-
}
17-
18-
console.log(`开始下载图标文件到: ${iconPath}`)
19-
20-
const writer = fs.createWriteStream(iconPath)
21-
const response = await Axios({
22-
url: `https:${url}`,
23-
method: 'GET',
24-
responseType: 'stream',
25-
timeout: 30000, // 30秒超时
26-
})
27-
28-
response.data.pipe(writer)
29-
30-
return new Promise((resolve, reject) => {
31-
writer.on('finish', () => {
32-
console.log('图标文件下载成功!')
33-
resolve()
34-
})
35-
writer.on('error', (err) => {
36-
console.error('写入文件时出错:', err.message)
37-
reject(err)
38-
})
39-
})
40-
} catch (error) {
41-
console.error('下载过程中出错:', error.message)
42-
throw error
43-
}
44-
}
5+
const iconPath = path.resolve(__dirname, "../src/assets/fonts/iconfont.js");
6+
const iconDir = path.dirname(iconPath);
457

46-
async function main() {
47-
const argument = process.argv.splice(2)
48-
49-
if (!argument[0]) {
50-
console.error('错误: 请提供下载URL作为参数')
51-
console.log('使用方法: node downLoadIcon.cjs <url>')
52-
process.exit(1)
53-
}
54-
55-
try {
56-
await downloadFile(argument[0])
57-
console.log('所有操作完成!')
58-
} catch (error) {
59-
console.error('脚本执行失败:', error.message)
60-
process.exit(1)
8+
// 检查目录是否存在,不存在则创建
9+
if (!fs.existsSync(iconDir)) {
10+
fs.mkdirSync(iconDir, { recursive: true });
11+
console.log(`目录 ${iconDir} 已创建`);
6112
}
13+
14+
const response = await fetch(`https:${url}`, {
15+
method: "GET",
16+
// responseType: "stream", // fetch 不支持此参数
17+
}).then((res) => res.text());
18+
fs.writeFileSync(iconPath, response);
19+
console.log("Download Icon Success");
6220
}
21+
let argument = process.argv.splice(2);
22+
downloadFile(argument[0]);
6323

64-
main()

ui/src/api/User.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
DomainListUserResp,
2424
DomainLoginReq,
2525
DomainLoginResp,
26+
DomainOAuthURLResp,
2627
DomainRegisterReq,
2728
DomainSetting,
2829
DomainUpdateSettingReq,
@@ -32,6 +33,8 @@ import {
3233
GetListAdminUserParams,
3334
GetListUserParams,
3435
GetLoginHistoryParams,
36+
GetUserOauthCallbackParams,
37+
GetUserOauthSignupOrInParams,
3538
WebResp,
3639
} from "./types";
3740

@@ -401,6 +404,66 @@ export const getLoginHistory = (
401404
...params,
402405
});
403406

407+
/**
408+
* @description 用户 OAuth 回调
409+
*
410+
* @tags User
411+
* @name GetUserOauthCallback
412+
* @summary 用户 OAuth 回调
413+
* @request GET:/api/v1/user/oauth/callback
414+
* @response `200` `(WebResp & {
415+
data?: string,
416+
417+
})` OK
418+
*/
419+
420+
export const getUserOauthCallback = (
421+
query: GetUserOauthCallbackParams,
422+
params: RequestParams = {},
423+
) =>
424+
request<
425+
WebResp & {
426+
data?: string;
427+
}
428+
>({
429+
path: `/api/v1/user/oauth/callback`,
430+
method: "GET",
431+
query: query,
432+
type: ContentType.Json,
433+
format: "json",
434+
...params,
435+
});
436+
437+
/**
438+
* @description 用户 OAuth 登录或注册
439+
*
440+
* @tags User
441+
* @name GetUserOauthSignupOrIn
442+
* @summary 用户 OAuth 登录或注册
443+
* @request GET:/api/v1/user/oauth/signup-or-in
444+
* @response `200` `(WebResp & {
445+
data?: DomainOAuthURLResp,
446+
447+
})` OK
448+
*/
449+
450+
export const getUserOauthSignupOrIn = (
451+
query: GetUserOauthSignupOrInParams,
452+
params: RequestParams = {},
453+
) =>
454+
request<
455+
WebResp & {
456+
data?: DomainOAuthURLResp;
457+
}
458+
>({
459+
path: `/api/v1/user/oauth/signup-or-in`,
460+
method: "GET",
461+
query: query,
462+
type: ContentType.Json,
463+
format: "json",
464+
...params,
465+
});
466+
404467
/**
405468
* @description 注册用户
406469
*

ui/src/api/types.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ export enum ConstsUserStatus {
1616
UserStatusLocked = "locked",
1717
}
1818

19+
export enum ConstsUserPlatform {
20+
UserPlatformEmail = "email",
21+
UserPlatformDingTalk = "dingtalk",
22+
}
23+
1924
export enum ConstsModelType {
2025
ModelTypeLLM = "llm",
2126
ModelTypeCoder = "coder",
@@ -312,6 +317,10 @@ export interface DomainModelTokenUsageResp {
312317
total_output?: number;
313318
}
314319

320+
export interface DomainOAuthURLResp {
321+
url?: string;
322+
}
323+
315324
export interface DomainProviderModel {
316325
/** 模型列表 */
317326
models?: DomainModelBasic[];
@@ -333,6 +342,8 @@ export interface DomainSetting {
333342
created_at?: number;
334343
/** 是否禁用密码登录 */
335344
disable_password_login?: boolean;
345+
/** 是否开启钉钉OAuth */
346+
enable_dingtalk_oauth?: boolean;
336347
/** 是否开启SSO */
337348
enable_sso?: boolean;
338349
/** 是否强制两步验证 */
@@ -419,8 +430,14 @@ export interface DomainUpdateModelReq {
419430
}
420431

421432
export interface DomainUpdateSettingReq {
433+
/** 钉钉客户端ID */
434+
dingtalk_client_id?: string;
435+
/** 钉钉客户端密钥 */
436+
dingtalk_client_secret?: string;
422437
/** 是否禁用密码登录 */
423438
disable_password_login?: boolean;
439+
/** 是否开启钉钉OAuth */
440+
enable_dingtalk_oauth?: boolean;
424441
/** 是否开启SSO */
425442
enable_sso?: boolean;
426443
/** 是否强制两步验证 */
@@ -637,3 +654,17 @@ export interface GetLoginHistoryParams {
637654
/** 每页多少条记录 */
638655
size?: number;
639656
}
657+
658+
export interface GetUserOauthCallbackParams {
659+
code: string;
660+
state: string;
661+
}
662+
663+
export interface GetUserOauthSignupOrInParams {
664+
/** 第三方平台 dingtalk */
665+
platform: "email" | "dingtalk";
666+
/** 登录成功后跳转的 URL */
667+
redirect_url?: string;
668+
/** 会话ID */
669+
session_id?: string;
670+
}

ui/src/assets/fonts/iconfont.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ui/src/components/form/index.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
'use client';
21
import { styled, FormLabel } from '@mui/material';
32

43
export const StyledFormLabel = styled(FormLabel)(({ theme }) => ({
54
display: 'block',
65
color: theme.vars.palette.text.primary,
7-
fontSize: 16,
8-
fontWeight: 500,
6+
fontSize: 14,
7+
fontWeight: 400,
98
marginBottom: theme.spacing(1),
109
[theme.breakpoints.down('sm')]: {
1110
fontSize: 14,

ui/src/components/markDown/index.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ const MarkDown = ({
9797

9898
const answer = processContent(content);
9999

100+
console.log(answer);
101+
102+
console.log(content);
103+
100104
if (content.length === 0) return null;
101105

102106
return (
@@ -446,12 +450,13 @@ const MarkDown = ({
446450
...rest
447451
}: React.HTMLAttributes<HTMLElement>) {
448452
const match = /language-(\w+)/.exec(className || '');
453+
console.log(children, rest);
449454
return match ? (
450455
<SyntaxHighlighter
451456
showLineNumbers
452457
{...rest}
453458
language={match[1] || 'bash'}
454-
style={github}
459+
style={anOldHope}
455460
onClick={() => {
456461
if (navigator.clipboard) {
457462
navigator.clipboard.writeText(

ui/src/pages/auth/index.tsx

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,20 @@ import {
1212
Grid2 as Grid,
1313
InputAdornment,
1414
IconButton,
15+
Divider,
16+
Stack,
1517
} from '@mui/material';
16-
import { Icon } from '@c-x/ui';
18+
import { Icon, message } from '@c-x/ui';
1719

1820
// @ts-ignore
1921
import { AestheticFluidBg } from '@/assets/jsm/AestheticFluidBg.module.js';
2022

2123
import { useSearchParams } from 'react-router-dom';
22-
import { postLogin } from '@/api/User';
24+
import { postLogin, getUserOauthSignupOrIn, getGetSetting } from '@/api/User';
2325

2426
import { useForm, Controller } from 'react-hook-form';
2527
import { styled } from '@mui/material/styles';
28+
import { useRequest } from 'ahooks';
2629

2730
// 样式化组件
2831
const StyledContainer = styled(Container)(({ theme }) => ({
@@ -111,6 +114,7 @@ const AuthPage = () => {
111114
const [showPassword, setShowPassword] = useState(false);
112115

113116
const [searchParams] = useSearchParams();
117+
const { data: loginSetting = {} } = useRequest(getGetSetting);
114118

115119
const {
116120
control,
@@ -132,7 +136,8 @@ const AuthPage = () => {
132136
try {
133137
const sessionId = searchParams.get('session_id');
134138
if (!sessionId) {
135-
throw new Error('缺少会话ID参数');
139+
message.error('缺少会话ID参数');
140+
return;
136141
}
137142

138143
// 用户登录
@@ -245,17 +250,6 @@ const AuthPage = () => {
245250
/>
246251
);
247252

248-
// 渲染错误提示
249-
const renderErrorAlert = () => {
250-
if (!error) return null;
251-
252-
return (
253-
<Grid size={12}>
254-
<Alert severity='error'>{error}</Alert>
255-
</Grid>
256-
);
257-
};
258-
259253
// 渲染登录按钮
260254
const renderLoginButton = () => (
261255
<Grid size={12}>
@@ -271,6 +265,32 @@ const AuthPage = () => {
271265
</Grid>
272266
);
273267

268+
const onDingdingLogin = () => {
269+
getUserOauthSignupOrIn({
270+
platform: 'dingtalk',
271+
redirect_url: window.location.origin + window.location.pathname,
272+
// @ts-ignore
273+
session_id: searchParams.get('session_id') || null,
274+
}).then((res) => {
275+
if (res.url) {
276+
window.location.href = res.url;
277+
}
278+
});
279+
};
280+
281+
const dingdingLogin = () => {
282+
return (
283+
<Stack justifyContent='center'>
284+
<Divider sx={{ my: 3, fontSize: 12, borderColor: 'divider' }}>
285+
使用其他方式登录
286+
</Divider>
287+
<IconButton sx={{ alignSelf: 'center' }} onClick={onDingdingLogin}>
288+
<Icon type='icon-dingding' sx={{ fontSize: 30 }} />
289+
</IconButton>
290+
</Stack>
291+
);
292+
};
293+
274294
// 渲染登录表单
275295
const renderLoginForm = () => (
276296
<>
@@ -284,19 +304,27 @@ const AuthPage = () => {
284304
<Box component='form' onSubmit={handleSubmit(onSubmit)}>
285305
<Grid container spacing={4}>
286306
<Grid size={12}>{renderUsernameField()}</Grid>
287-
288307
<Grid size={12}>{renderPasswordField()}</Grid>
289308

290-
{renderErrorAlert()}
291309
{renderLoginButton()}
292310
</Grid>
293311
</Box>
294312
</>
295313
);
296314

315+
useEffect(() => {
316+
const redirect_url = searchParams.get('redirect_url');
317+
if (redirect_url) {
318+
window.location.href = redirect_url;
319+
}
320+
}, []);
321+
297322
return (
298323
<StyledContainer id='box'>
299-
<StyledPaper elevation={3}>{renderLoginForm()}</StyledPaper>
324+
<StyledPaper elevation={3}>
325+
{!loginSetting.disable_password_login && renderLoginForm()}
326+
{loginSetting.enable_dingtalk_oauth && dingdingLogin()}
327+
</StyledPaper>
300328
</StyledContainer>
301329
);
302330
};

0 commit comments

Comments
 (0)