Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
A
ai-box
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
青山
ai-box
Commits
22fbf6c1
Commit
22fbf6c1
authored
Jul 09, 2025
by
青山
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
接入飞书
parent
0ae3f35b
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
222 additions
and
161 deletions
+222
-161
index.html
index.html
+12
-0
DocumentTranslatorContent.jsx
src/components/DocumentTranslatorContent.jsx
+1
-1
Header.jsx
src/components/Header.jsx
+100
-37
PrivateRoute.jsx
src/components/PrivateRoute.jsx
+6
-6
Login.jsx
src/pages/Login.jsx
+102
-116
vite.config.js
vite.config.js
+1
-1
No files found.
index.html
View file @
22fbf6c1
...
@@ -12,6 +12,13 @@
...
@@ -12,6 +12,13 @@
<script
src=
"https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.9.359/pdf.min.js"
></script>
<script
src=
"https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.9.359/pdf.min.js"
></script>
<!-- Google tag (gtag.js) -->
<!-- Google tag (gtag.js) -->
<script
async
src=
"https://www.googletagmanager.com/gtag/js?id=G-3PSXKB099C"
></script>
<script
async
src=
"https://www.googletagmanager.com/gtag/js?id=G-3PSXKB099C"
></script>
<!-- 飞书SDK -->
<script
src=
"https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"
></script>
<!-- 引入 JSSDK -->
<!-- JS 文件版本在升级功能时地址会变化,如有需要(比如使用新增的 API),请重新引用「网页应用开发指南」中的JSSDK链接,确保你当前使用的JSSDK版本是最新的。-->
<script
type=
"text/javascript"
src=
"https://lf1-cdn-tos.bytegoofy.com/goofy/lark/op/h5-js-sdk-1.5.16.js"
></script>
<script>
<script>
window
.
dataLayer
=
window
.
dataLayer
||
[];
window
.
dataLayer
=
window
.
dataLayer
||
[];
function
gtag
(){
dataLayer
.
push
(
arguments
);}
function
gtag
(){
dataLayer
.
push
(
arguments
);}
...
@@ -19,6 +26,11 @@
...
@@ -19,6 +26,11 @@
gtag
(
'config'
,
'G-3PSXKB099C'
);
gtag
(
'config'
,
'G-3PSXKB099C'
);
</script>
</script>
<script
src=
"https://unpkg.com/vconsole/dist/vconsole.min.js"
></script>
<script>
var
vConsole
=
new
window
.
VConsole
();
</script>
</head>
</head>
<body>
<body>
<div
id=
"root"
></div>
<div
id=
"root"
></div>
...
...
src/components/DocumentTranslatorContent.jsx
View file @
22fbf6c1
...
@@ -372,7 +372,7 @@ const DocumentTranslatorContent = ({ currentLanguage, onLanguageChange }) => {
...
@@ -372,7 +372,7 @@ const DocumentTranslatorContent = ({ currentLanguage, onLanguageChange }) => {
const
handlePaste
=
(
e
)
=>
{
const
handlePaste
=
(
e
)
=>
{
if
(
!
checkAuthentication
())
{
if
(
!
checkAuthentication
())
{
// For clipboard pastes, just redirect to login without saving
// For clipboard pastes, just redirect to login without saving
window
.
location
.
href
=
'/login'
;
//
window.location.href = '/login';
return
;
return
;
}
}
...
...
src/components/Header.jsx
View file @
22fbf6c1
...
@@ -8,16 +8,96 @@ import logo from '/assets/logo.png';
...
@@ -8,16 +8,96 @@ import logo from '/assets/logo.png';
function
Header
()
{
function
Header
()
{
const
{
t
}
=
useTranslation
();
const
{
t
}
=
useTranslation
();
const
navigate
=
useNavigate
();
const
navigate
=
useNavigate
();
const
[
user
,
setUser
]
=
useState
(
null
);
const
[
user
,
setUser
]
=
useState
({});
const
[
avatarUrl
,
setAvatarUrl
]
=
useState
(
""
);
const
[
menuOpen
,
setMenuOpen
]
=
useState
(
false
);
const
[
menuOpen
,
setMenuOpen
]
=
useState
(
false
);
const
[
mobileMenuOpen
,
setMobileMenuOpen
]
=
useState
(
false
);
const
[
mobileMenuOpen
,
setMobileMenuOpen
]
=
useState
(
false
);
const
menuRef
=
useRef
(
null
);
const
menuRef
=
useRef
(
null
);
useEffect
(()
=>
{
const
lang
=
window
.
navigator
.
language
;
const
apiAuth
=
async
()
=>
{
console
.
log
(
"start apiAuth"
);
if
(
!
window
.
h5sdk
)
{
console
.
log
(
"invalid h5sdk"
);
alert
(
"please open in feishu"
);
return
;
}
const
url
=
encodeURIComponent
(
location
.
href
.
split
(
"#"
)[
0
]);
console
.
log
(
"接入方前端将需要鉴权的url发给接入方服务端,url为:"
,
url
);
try
{
const
response
=
await
fetch
(
`/get_config_parameters?url=
${
url
}
`
);
const
res
=
await
response
.
json
();
console
.
log
(
"接入方服务端返回给接入方前端的结果(前端调用config接口的所需参数):"
,
res
);
window
.
h5sdk
.
error
((
err
)
=>
{
console
.
error
(
"h5sdk error:"
,
JSON
.
stringify
(
err
));
});
window
.
h5sdk
.
config
({
appId
:
res
.
appid
,
timestamp
:
res
.
timestamp
,
nonceStr
:
res
.
noncestr
,
signature
:
res
.
signature
,
jsApiList
:
[],
onSuccess
:
(
res
)
=>
{
console
.
log
(
`config success:
${
JSON
.
stringify
(
res
)}
`
);
},
onFail
:
(
err
)
=>
{
console
.
error
(
`config failed:
${
JSON
.
stringify
(
err
)}
`
);
},
});
window
.
h5sdk
.
ready
(()
=>
{
// 获取用户信息
window
.
tt
.
getUserInfo
({
success
:
(
res
)
=>
{
console
.
log
(
`getUserInfo success:
${
JSON
.
stringify
(
res
)}
`
);
setUser
(
res
.
userInfo
);
localStorage
.
setItem
(
'userInfo'
,
JSON
.
stringify
(
res
.
userInfo
));
setAvatarUrl
(
res
.
userInfo
.
avatarUrl
)
},
fail
:
(
err
)
=>
{
console
.
log
(
`getUserInfo failed:`
,
JSON
.
stringify
(
err
));
},
});
// 显示Toast
window
.
tt
.
showToast
({
title
:
"鉴权成功"
,
icon
:
"success"
,
duration
:
3000
,
success
(
res
)
{
console
.
log
(
"showToast 调用成功"
,
res
.
errMsg
);
},
fail
(
res
)
{
// console.log("showToast 调用失败", res.errMsg);
},
complete
(
res
)
{
// console.log("showToast 调用结束", res.errMsg);
},
});
});
}
catch
(
e
)
{
console
.
error
(
"fetch error:"
,
e
);
}
};
apiAuth
();
},
[]);
// Check for user on component mount and localStorage changes
// Check for user on component mount and localStorage changes
useEffect
(()
=>
{
useEffect
(()
=>
{
const
checkUser
=
()
=>
{
const
checkUser
=
()
=>
{
try
{
try
{
const
userData
=
localStorage
.
getItem
(
'user'
);
const
userData
=
localStorage
.
getItem
(
'user
Info
'
);
if
(
userData
)
{
if
(
userData
)
{
setUser
(
JSON
.
parse
(
userData
));
setUser
(
JSON
.
parse
(
userData
));
}
else
{
}
else
{
...
@@ -54,10 +134,10 @@ function Header() {
...
@@ -54,10 +134,10 @@ function Header() {
},
[
menuRef
]);
},
[
menuRef
]);
const
handleLogout
=
()
=>
{
const
handleLogout
=
()
=>
{
localStorage
.
removeItem
(
'user'
);
//
localStorage.removeItem('user');
setUser
(
null
);
//
setUser(null);
navigate
(
'/login'
);
//
navigate('/login');
setMobileMenuOpen
(
false
);
//
setMobileMenuOpen(false);
};
};
const
toggleMobileMenu
=
()
=>
{
const
toggleMobileMenu
=
()
=>
{
...
@@ -121,6 +201,11 @@ function Header() {
...
@@ -121,6 +201,11 @@ function Header() {
};
};
},
[
mobileMenuOpen
]);
},
[
mobileMenuOpen
]);
// const avatarUrl = JSON.parse(localStorage.getItem('userInfo') || '{}').avatarUrl || '';
// useEffect(()=>{
// const avatarUrl = JSON.parse(localStorage.getItem('userInfo') || '{}').avatarUrl || '';
// },[user])
return
(
return
(
<
header
className=
"fixed top-0 left-0 right-0 z-50"
>
<
header
className=
"fixed top-0 left-0 right-0 z-50"
>
<
div
className=
"absolute inset-0 bg-white/70 backdrop-blur-lg border-b border-gray-100"
></
div
>
<
div
className=
"absolute inset-0 bg-white/70 backdrop-blur-lg border-b border-gray-100"
></
div
>
...
@@ -229,7 +314,7 @@ function Header() {
...
@@ -229,7 +314,7 @@ function Header() {
className=
"flex items-center space-x-3 focus:outline-none"
className=
"flex items-center space-x-3 focus:outline-none"
>
>
<
img
<
img
src=
"https://th.bing.com/th/id/OIP.nl9qIl6NYdJmv6jeMd8H7gAAAA?rs=1&pid=ImgDetMain&cb=idpwebpc2"
src=
{
avatarUrl
}
alt=
"User Avatar"
alt=
"User Avatar"
className=
"w-8 h-8 sm:w-10 sm:h-10 rounded-full ring-2 ring-offset-2 ring-indigo-500 transition transform hover:scale-105"
className=
"w-8 h-8 sm:w-10 sm:h-10 rounded-full ring-2 ring-offset-2 ring-indigo-500 transition transform hover:scale-105"
/>
/>
...
@@ -247,12 +332,12 @@ function Header() {
...
@@ -247,12 +332,12 @@ function Header() {
)
}
)
}
</
div
>
</
div
>
)
:
(
)
:
(
<
NavLink
to=
"/login"
<
img
className=
"hidden sm:inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-lg text-white bg-indigo-600 hover:bg-indigo-700 transition-colors duration-200 shadow-sm hover:shadow-md"
src=
{
avatarUrl
}
>
alt=
"User Avatar"
{
t
(
'login'
)
}
className=
"w-8 h-8 sm:w-10 sm:h-10 rounded-full ring-2 ring-offset-2 ring-indigo-500 transition transform hover:scale-105"
</
NavLink
>
/
>
)
}
)
}
</
div
>
</
div
>
...
@@ -341,19 +426,7 @@ function Header() {
...
@@ -341,19 +426,7 @@ function Header() {
>
>
{
t
(
'image-tools.title'
)
}
{
t
(
'image-tools.title'
)
}
</
NavLink
>
</
NavLink
>
<
NavLink
to=
"/translator"
className=
{
({
isActive
})
=>
`block px-4 py-3 rounded-lg text-base font-medium transition-colors duration-200 ${
isActive
? 'bg-indigo-50 text-indigo-600'
: 'text-gray-700 hover:bg-gray-50 hover:text-indigo-600'
}`
}
onClick=
{
()
=>
setMobileMenuOpen
(
false
)
}
>
翻译工具
</
NavLink
>
<
NavLink
<
NavLink
to=
"/document-translator"
to=
"/document-translator"
className=
{
({
isActive
})
=>
className=
{
({
isActive
})
=>
...
@@ -396,17 +469,7 @@ function Header() {
...
@@ -396,17 +469,7 @@ function Header() {
</
div
>
</
div
>
</
div
>
</
div
>
<
div
className=
"border-t border-gray-200 p-4"
>
{
!
user
&&
(
<
NavLink
to=
"/login"
className=
"flex items-center justify-center px-4 py-2 border border-transparent text-base font-medium rounded-lg text-white bg-indigo-600 hover:bg-indigo-700 transition-colors duration-200 shadow-sm hover:shadow-md w-full"
onClick=
{
()
=>
setMobileMenuOpen
(
false
)
}
>
{
t
(
'login'
)
}
</
NavLink
>
)
}
</
div
>
</
nav
>
</
nav
>
</
div
>
</
div
>
</
div
>
</
div
>
...
...
src/components/PrivateRoute.jsx
View file @
22fbf6c1
...
@@ -7,12 +7,12 @@ import { Navigate, useLocation } from 'react-router-dom';
...
@@ -7,12 +7,12 @@ import { Navigate, useLocation } from 'react-router-dom';
*/
*/
const
PrivateRoute
=
({
children
})
=>
{
const
PrivateRoute
=
({
children
})
=>
{
const
location
=
useLocation
();
const
location
=
useLocation
();
const
user
=
localStorage
.
getItem
(
'user'
);
const
user
=
localStorage
.
getItem
(
'user
Info
'
);
if
(
!
user
)
{
//
if (!user) {
// 记录跳转前的路径,登录后可重定向回来
//
// 记录跳转前的路径,登录后可重定向回来
sessionStorage
.
setItem
(
'redirectAfterLogin'
,
location
.
pathname
+
location
.
search
);
//
sessionStorage.setItem('redirectAfterLogin', location.pathname + location.search);
return
<
Navigate
to=
"/login"
replace
/>;
//
return <Navigate to="/login" replace />;
}
//
}
return
children
;
return
children
;
};
};
...
...
src/pages/Login.jsx
View file @
22fbf6c1
// src/pages/Login.jsx
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
useNavigate
}
from
'react-router-dom'
;
import
{
useTranslation
}
from
'../js/i18n'
;
import
'../styles/Login.css'
;
const
Login
=
()
=>
{
const
Login
=
()
=>
{
const
navigate
=
useNavigate
();
const
[
userInfo
,
setUserInfo
]
=
useState
(
null
);
const
{
t
}
=
useTranslation
();
const
lang
=
window
.
navigator
.
language
;
// 新增:用户名密码登录相关状态
useEffect
(()
=>
{
const
[
username
,
setUsername
]
=
useState
(
''
);
const
apiAuth
=
async
()
=>
{
const
[
password
,
setPassword
]
=
useState
(
''
);
console
.
log
(
"start apiAuth"
);
const
[
loginLoading
,
setLoginLoading
]
=
useState
(
false
);
const
[
loginError
,
setLoginError
]
=
useState
(
''
);
if
(
!
window
.
h5sdk
)
{
const
[
rememberMe
,
setRememberMe
]
=
useState
(
false
);
console
.
log
(
"invalid h5sdk"
);
alert
(
"please open in feishu"
);
// 新增:腾讯云开发用户名密码登录
const
handleCloudbaseLogin
=
async
(
e
)
=>
{
e
.
preventDefault
();
setLoginError
(
''
);
setLoginLoading
(
true
);
// 记住密码逻辑
if
(
rememberMe
)
{
localStorage
.
setItem
(
'rememberedLogin'
,
JSON
.
stringify
({
username
,
password
}));
}
else
{
localStorage
.
removeItem
(
'rememberedLogin'
);
}
const
app
=
cloudbase
.
init
({
env
:
'xingzhi-authing-5gkb8pggc4cf2edf'
,
});
try
{
if
(
!
app
)
{
setLoginError
(
'CloudBase SDK 未初始化'
);
setLoginLoading
(
false
);
return
;
return
;
}
}
// 0. 先进行匿名登录,避免 "you can't request without auth" 报错
const
auth
=
app
.
auth
();
await
auth
.
signInAnonymously
();
// 1. 调用云函数 login 校验用户名密码,返回 ticket
const
res
=
await
app
.
callFunction
({
name
:
'login'
,
data
:
{
username
,
password
,
},
});
const
{
code
}
=
res
.
result
||
{};
if
(
code
==
200
)
{
localStorage
.
setItem
(
'user'
,
JSON
.
stringify
({
username
}));
window
.
location
.
href
=
'/'
;
}
else
{
setLoginError
(
'用户名或密码错误'
);
}
}
catch
(
err
)
{
setLoginError
(
err
.
message
||
'用户名或密码错误'
);
setLoginLoading
(
false
);
}
};
// 记住密码功能:初始化时自动填充
const
url
=
encodeURIComponent
(
location
.
href
.
split
(
"#"
)[
0
]);
useEffect
(()
=>
{
console
.
log
(
"接入方前端将需要鉴权的url发给接入方服务端,url为:"
,
url
);
const
saved
=
localStorage
.
getItem
(
'rememberedLogin'
);
if
(
saved
)
{
try
{
try
{
const
{
username
:
savedUser
,
password
:
savedPass
}
=
JSON
.
parse
(
saved
);
const
response
=
await
fetch
(
`/get_config_parameters?url=
${
url
}
`
);
setUsername
(
savedUser
||
''
);
const
res
=
await
response
.
json
();
setPassword
(
savedPass
||
''
);
setRememberMe
(
true
);
console
.
log
(
}
catch
{}
"接入方服务端返回给接入方前端的结果(前端调用config接口的所需参数):"
,
res
}
);
window
.
h5sdk
.
error
((
err
)
=>
{
console
.
error
(
"h5sdk error:"
,
JSON
.
stringify
(
err
));
});
window
.
h5sdk
.
config
({
appId
:
res
.
appid
,
timestamp
:
res
.
timestamp
,
nonceStr
:
res
.
noncestr
,
signature
:
res
.
signature
,
jsApiList
:
[],
onSuccess
:
(
res
)
=>
{
console
.
log
(
`config success:
${
JSON
.
stringify
(
res
)}
`
);
},
onFail
:
(
err
)
=>
{
console
.
error
(
`config failed:
${
JSON
.
stringify
(
err
)}
`
);
},
});
window
.
h5sdk
.
ready
(()
=>
{
// 获取用户信息
window
.
tt
.
getUserInfo
({
success
:
(
res
)
=>
{
console
.
log
(
`getUserInfo success:
${
JSON
.
stringify
(
res
)}
`
);
setUserInfo
(
res
.
userInfo
);
localStorage
.
setItem
(
'userInfo'
,
JSON
.
stringify
(
res
.
userInfo
));
},
fail
:
(
err
)
=>
{
console
.
log
(
`getUserInfo failed:`
,
JSON
.
stringify
(
err
));
},
});
// 显示Toast
window
.
tt
.
showToast
({
title
:
"鉴权成功"
,
icon
:
"success"
,
duration
:
3000
,
success
(
res
)
{
console
.
log
(
"showToast 调用成功"
,
res
.
errMsg
);
},
fail
(
res
)
{
// console.log("showToast 调用失败", res.errMsg);
},
complete
(
res
)
{
// console.log("showToast 调用结束", res.errMsg);
},
});
});
}
catch
(
e
)
{
console
.
error
(
"fetch error:"
,
e
);
}
};
apiAuth
();
},
[]);
},
[]);
const
renderUserInfo
=
()
=>
{
if
(
!
userInfo
)
return
null
;
const
name
=
lang
===
'zh_CN'
||
lang
===
'zh-CN'
?
userInfo
.
nickName
:
userInfo
.
i18nName
?.
en_us
;
const
welcomeText
=
lang
===
'zh_CN'
||
lang
===
'zh-CN'
?
'欢迎使用飞书'
:
'Welcome to Feishu'
;
return
(
// <div>
// <div id="img_div">
// <img src={userInfo.avatarUrl} alt="avatar" style={{ width: '100%' }} />
// </div>
// <h2 id="hello_text_name">{name}</h2>
// <p id="hello_text_welcome">{welcomeText}</p>
// </div>
<></>
);
};
return
(
return
(
<
div
className=
"login-container"
>
<
div
>
<
div
className=
"login-card"
>
{
/* <h1>Feishu 鉴权 Demo</h1>
<
h1
className=
"login-title"
>
{
t
(
'login'
)
}
</
h1
>
{renderUserInfo()} */
}
<
p
className=
"login-subtitle"
>
{
t
(
'loginSubtitle'
,
'欢迎使用 AI 工具箱,请登录以获得完整体验'
)
}
</
p
>
<
div
className=
"login-options"
>
{
/* 腾讯云开发用户名密码登录表单 */
}
<
form
className=
"cloudbase-login-form"
onSubmit=
{
handleCloudbaseLogin
}
>
<
input
type=
"text"
placeholder=
"用户名"
value=
{
username
}
onChange=
{
(
e
)
=>
setUsername
(
e
.
target
.
value
)
}
required
className=
"login-input enhanced-input"
autoComplete=
"username"
/>
<
input
type=
"password"
placeholder=
"密码"
value=
{
password
}
onChange=
{
(
e
)
=>
setPassword
(
e
.
target
.
value
)
}
required
className=
"login-input enhanced-input"
autoComplete=
"current-password"
/>
<
label
style=
{
{
display
:
'flex'
,
alignItems
:
'center'
,
marginBottom
:
'0.5rem'
,
fontSize
:
'0.98rem'
,
color
:
'#6B7280'
,
userSelect
:
'none'
}
}
>
<
input
type=
"checkbox"
checked=
{
rememberMe
}
onChange=
{
e
=>
setRememberMe
(
e
.
target
.
checked
)
}
style=
{
{
marginRight
:
'8px'
,
accentColor
:
'#6366f1'
}
}
/>
记住密码
</
label
>
<
button
type=
"submit"
className=
"login-btn enhanced-btn"
disabled=
{
loginLoading
}
>
{
loginLoading
?
'登录中...'
:
'用户名密码登录'
}
</
button
>
{
loginError
&&
<
div
className=
"login-error"
>
{
loginError
}
</
div
>
}
</
form
>
</
div
>
</
div
>
</
div
>
</
div
>
);
);
};
};
...
...
vite.config.js
View file @
22fbf6c1
...
@@ -5,5 +5,5 @@ import sitemap from 'vite-plugin-sitemap';
...
@@ -5,5 +5,5 @@ import sitemap from 'vite-plugin-sitemap';
export
default
defineConfig
({
export
default
defineConfig
({
server
:
{
server
:
{
port
:
3000
port
:
3000
}
}
,
})
})
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment