feat: able to customize home page now (#24)

This commit is contained in:
JustSong 2023-05-13 21:27:49 +08:00
parent 46e77389a4
commit d84c2f5c70
6 changed files with 112 additions and 61 deletions

View File

@ -56,7 +56,7 @@ _✨ All in one 的 OpenAI 接口,整合各种 API 访问方式,开箱即用
5. 支持设置令牌的过期时间和使用次数。 5. 支持设置令牌的过期时间和使用次数。
6. 支持批量生成和导出兑换码,可使用兑换码为令牌进行充值。 6. 支持批量生成和导出兑换码,可使用兑换码为令牌进行充值。
7. 支持为新用户设置初始配额。 7. 支持为新用户设置初始配额。
8. 支持发布公告,在线修改关于页面,设置充值链接,自定义页脚。 8. 支持自定义首页,发布公告,自定义关于页面,设置充值链接,自定义页脚。
9. 支持通过系统访问令牌访问管理 API。 9. 支持通过系统访问令牌访问管理 API。
10. 多种用户登录注册方式: 10. 多种用户登录注册方式:
+ 邮箱登录注册以及通过邮箱进行密码重置。 + 邮箱登录注册以及通过邮箱进行密码重置。

View File

@ -54,6 +54,17 @@ func GetAbout(c *gin.Context) {
return return
} }
func GetHomePageContent(c *gin.Context) {
common.OptionMapRWMutex.RLock()
defer common.OptionMapRWMutex.RUnlock()
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
"data": common.OptionMap["HomePageContent"],
})
return
}
func SendEmailVerification(c *gin.Context) { func SendEmailVerification(c *gin.Context) {
email := c.Query("email") email := c.Query("email")
if err := common.Validate.Var(email, "required,email"); err != nil { if err := common.Validate.Var(email, "required,email"); err != nil {

View File

@ -39,6 +39,7 @@ func InitOptionMap() {
common.OptionMap["SMTPToken"] = "" common.OptionMap["SMTPToken"] = ""
common.OptionMap["Notice"] = "" common.OptionMap["Notice"] = ""
common.OptionMap["About"] = "" common.OptionMap["About"] = ""
common.OptionMap["HomePageContent"] = ""
common.OptionMap["Footer"] = common.Footer common.OptionMap["Footer"] = common.Footer
common.OptionMap["ServerAddress"] = "" common.OptionMap["ServerAddress"] = ""
common.OptionMap["GitHubClientId"] = "" common.OptionMap["GitHubClientId"] = ""

View File

@ -15,6 +15,7 @@ func SetApiRouter(router *gin.Engine) {
apiRouter.GET("/status", controller.GetStatus) apiRouter.GET("/status", controller.GetStatus)
apiRouter.GET("/notice", controller.GetNotice) apiRouter.GET("/notice", controller.GetNotice)
apiRouter.GET("/about", controller.GetAbout) apiRouter.GET("/about", controller.GetAbout)
apiRouter.GET("/home_page_content", controller.GetHomePageContent)
apiRouter.GET("/verification", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendEmailVerification) apiRouter.GET("/verification", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendEmailVerification)
apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail) apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail)
apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword) apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword)

View File

@ -8,6 +8,7 @@ const OtherSetting = () => {
Footer: '', Footer: '',
Notice: '', Notice: '',
About: '', About: '',
HomePageContent: '',
}); });
let originInputs = {}; let originInputs = {};
let [loading, setLoading] = useState(false); let [loading, setLoading] = useState(false);
@ -69,6 +70,10 @@ const OtherSetting = () => {
await updateOption('About', inputs.About); await updateOption('About', inputs.About);
}; };
const submitOption = async (key) => {
await updateOption(key, inputs[key]);
};
const openGitHubRelease = () => { const openGitHubRelease = () => {
window.location = window.location =
'https://github.com/songquanpeng/one-api/releases/latest'; 'https://github.com/songquanpeng/one-api/releases/latest';
@ -109,6 +114,17 @@ const OtherSetting = () => {
<Form.Button onClick={submitNotice}>保存公告</Form.Button> <Form.Button onClick={submitNotice}>保存公告</Form.Button>
<Divider /> <Divider />
<Header as='h3'>个性化设置</Header> <Header as='h3'>个性化设置</Header>
<Form.Group widths='equal'>
<Form.TextArea
label='首页内容'
placeholder='在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示'
value={inputs.HomePageContent}
name='HomePageContent'
onChange={handleInputChange}
style={{ minHeight: 300, fontFamily: 'JetBrains Mono, Consolas' }}
/>
</Form.Group>
<Form.Button onClick={()=>submitOption('HomePageContent')}>保存首页内容</Form.Button>
<Form.Group widths='equal'> <Form.Group widths='equal'>
<Form.TextArea <Form.TextArea
label='关于' label='关于'

View File

@ -1,10 +1,12 @@
import React, { useContext, useEffect } from 'react'; import React, { useContext, useEffect, useState } from 'react';
import { Card, Grid, Header, Segment } from 'semantic-ui-react'; import { Card, Grid, Header, Segment } from 'semantic-ui-react';
import { API, showError, showNotice, timestamp2string } from '../../helpers'; import { API, showError, showNotice, timestamp2string } from '../../helpers';
import { StatusContext } from '../../context/Status'; import { StatusContext } from '../../context/Status';
import { marked } from 'marked';
const Home = () => { const Home = () => {
const [statusState, statusDispatch] = useContext(StatusContext); const [statusState, statusDispatch] = useContext(StatusContext);
const [homePageContent, setHomePageContent] = useState('');
const displayNotice = async () => { const displayNotice = async () => {
const res = await API.get('/api/notice'); const res = await API.get('/api/notice');
@ -20,6 +22,19 @@ const Home = () => {
} }
}; };
const displayHomePageContent = async () => {
const res = await API.get('/api/home_page_content');
const { success, message, data } = res.data;
if (success) {
let HTMLContent = marked.parse(data);
localStorage.setItem('home_page_content', HTMLContent);
setHomePageContent(HTMLContent);
} else {
showError(message);
setHomePageContent('加载首页内容失败...');
}
};
const getStartTimeString = () => { const getStartTimeString = () => {
const timestamp = statusState?.status?.start_time; const timestamp = statusState?.status?.start_time;
return timestamp2string(timestamp); return timestamp2string(timestamp);
@ -27,69 +42,76 @@ const Home = () => {
useEffect(() => { useEffect(() => {
displayNotice().then(); displayNotice().then();
displayHomePageContent().then();
}, []); }, []);
return ( return (
<> <>
<Segment> <Segment>
<Header as='h3'>系统状况</Header> {
<Grid columns={2} stackable> homePageContent === '' ? <>
<Grid.Column> <Header as='h3'>系统状况</Header>
<Card fluid> <Grid columns={2} stackable>
<Card.Content> <Grid.Column>
<Card.Header>系统信息</Card.Header> <Card fluid>
<Card.Meta>系统信息总览</Card.Meta> <Card.Content>
<Card.Description> <Card.Header>系统信息</Card.Header>
<p>名称{statusState?.status?.system_name}</p> <Card.Meta>系统信息总览</Card.Meta>
<p>版本{statusState?.status?.version}</p> <Card.Description>
<p> <p>名称{statusState?.status?.system_name}</p>
源码 <p>版本{statusState?.status?.version}</p>
<a <p>
href='https://github.com/songquanpeng/one-api' 源码
target='_blank' <a
> href='https://github.com/songquanpeng/one-api'
https://github.com/songquanpeng/one-api target='_blank'
</a> >
</p> https://github.com/songquanpeng/one-api
<p>启动时间{getStartTimeString()}</p> </a>
</Card.Description> </p>
</Card.Content> <p>启动时间{getStartTimeString()}</p>
</Card> </Card.Description>
</Grid.Column> </Card.Content>
<Grid.Column> </Card>
<Card fluid> </Grid.Column>
<Card.Content> <Grid.Column>
<Card.Header>系统配置</Card.Header> <Card fluid>
<Card.Meta>系统配置总览</Card.Meta> <Card.Content>
<Card.Description> <Card.Header>系统配置</Card.Header>
<p> <Card.Meta>系统配置总览</Card.Meta>
邮箱验证 <Card.Description>
{statusState?.status?.email_verification === true <p>
? '已启用' 邮箱验证
: '未启用'} {statusState?.status?.email_verification === true
</p> ? '已启用'
<p> : '未启用'}
GitHub 身份验证 </p>
{statusState?.status?.github_oauth === true <p>
? '已启用' GitHub 身份验证
: '未启用'} {statusState?.status?.github_oauth === true
</p> ? '已启用'
<p> : '未启用'}
微信身份验证 </p>
{statusState?.status?.wechat_login === true <p>
? '已启用' 微信身份验证
: '未启用'} {statusState?.status?.wechat_login === true
</p> ? '已启用'
<p> : '未启用'}
Turnstile 用户校验 </p>
{statusState?.status?.turnstile_check === true <p>
? '已启用' Turnstile 用户校验
: '未启用'} {statusState?.status?.turnstile_check === true
</p> ? '已启用'
</Card.Description> : '未启用'}
</Card.Content> </p>
</Card> </Card.Description>
</Grid.Column> </Card.Content>
</Grid> </Card>
</Grid.Column>
</Grid>
</> : <>
<div dangerouslySetInnerHTML={{ __html: homePageContent}}></div>
</>
}
</Segment> </Segment>
</> </>
); );