Просмотр исходного кода

feat: improve frontend (#387)

* fork

* fork

* chore: update style

---------

Co-authored-by: JustSong <songquanpeng@foxmail.com>
Yolo° 2 лет назад
Родитель
Сommit
c58f710227

+ 9 - 2
controller/token.go

@@ -109,10 +109,10 @@ func AddToken(c *gin.Context) {
 		})
 		return
 	}
-	if len(token.Name) == 0 || len(token.Name) > 20 {
+	if len(token.Name) == 0 || len(token.Name) > 30 {
 		c.JSON(http.StatusOK, gin.H{
 			"success": false,
-			"message": "令牌名称长度必须在1-20之间",
+			"message": "令牌名称长",
 		})
 		return
 	}
@@ -171,6 +171,13 @@ func UpdateToken(c *gin.Context) {
 		})
 		return
 	}
+	if len(token.Name) == 0 || len(token.Name) > 30 {
+		c.JSON(http.StatusOK, gin.H{
+			"success": false,
+			"message": "令牌名称过长",
+		})
+		return
+	}
 	cleanToken, err := model.GetTokenByIds(token.Id, userId)
 	if err != nil {
 		c.JSON(http.StatusOK, gin.H{

+ 4 - 3
i18n/en.json

@@ -40,7 +40,7 @@
   "一次兑换码批量生成的个数不能大于 100": "The number of redemption codes generated in a batch cannot be greater than 100",
   "通过令牌「%s」使用模型 %s 消耗 %s(模型倍率 %.2f,分组倍率 %.2f)": "Using model %s with token %s consumes %s (model rate %.2f, group rate %.2f)",
   "当前分组负载已饱和,请稍后再试,或升级账户以提升服务质量。": "The current group load is saturated, please try again later, or upgrade your account to improve service quality.",
-  "令牌名称长度必须在1-20之间": "The length of the token name must be between 1-20",
+  "令牌名称过长": "Token name is too long",
   "令牌已过期,无法启用,请先修改令牌过期时间,或者设置为永不过期": "The token has expired and cannot be enabled. Please modify the expiration time of the token, or set it to never expire.",
   "令牌可用额度已用尽,无法启用,请先修改令牌剩余额度,或者设置为无限额度": "The available quota of the token has been used up and cannot be enabled. Please modify the remaining quota of the token, or set it to unlimited quota",
   "管理员关闭了密码登录": "The administrator has turned off password login",
@@ -229,7 +229,7 @@
   "已是最新版本": "Is the latest version",
   "检查更新": "Check for updates",
   "公告": "Announcement",
-  "在此输入新的公告内容": "Enter new announcement content here",
+  "在此输入新的公告内容,支持 Markdown & HTML 代码": "Enter the new announcement content here, supports Markdown & HTML code",
   "保存公告": "Save Announcement",
   "个性化设置": "Personalization Settings",
   "系统名称": "System Name",
@@ -518,5 +518,6 @@
   ",图片演示。": "related image demo.",
   "令牌创建成功,请在列表页面点击复制获取令牌!": "Token created successfully, please click copy on the list page to get the token!",
   "代理": "Proxy",
-  "此项可选,用于通过代理站来进行 API 调用,请输入代理站地址,格式为:https://domain.com": "This is optional, used to make API calls through the proxy site, please enter the proxy site address, the format is: https://domain.com"
+  "此项可选,用于通过代理站来进行 API 调用,请输入代理站地址,格式为:https://domain.com": "This is optional, used to make API calls through the proxy site, please enter the proxy site address, the format is: https://domain.com",
+  "取消密码登录将导致所有未绑定其他登录方式的用户(包括管理员)无法通过密码登录,确认取消?": "Canceling password login will cause all users (including administrators) who have not bound other login methods to be unable to log in via password, confirm cancel?"
 }

+ 2 - 2
web/src/components/ChannelsTable.js

@@ -447,8 +447,8 @@ const ChannelsTable = () => {
               <Button size='small' loading={loading} onClick={testAllChannels}>
                 测试所有已启用通道
               </Button>
-              <Button size='small' onClick={updateAllChannelsBalance}
-                      loading={loading || updatingBalance}>更新所有已启用通道余额</Button>
+              {/* <Button size='small' onClick={updateAllChannelsBalance}
+                      loading={loading || updatingBalance}>更新所有已启用通道余额</Button> */}
               <Pagination
                 floated='right'
                 activePage={activePage}

+ 1 - 1
web/src/components/OtherSetting.js

@@ -112,7 +112,7 @@ const OtherSetting = () => {
           <Form.Group widths='equal'>
             <Form.TextArea
               label='公告'
-              placeholder='在此输入新的公告内容'
+              placeholder='在此输入新的公告内容,支持 Markdown & HTML 代码'
               value={inputs.Notice}
               name='Notice'
               onChange={handleInputChange}

+ 19 - 9
web/src/components/RedemptionsTable.js

@@ -1,5 +1,5 @@
 import React, { useEffect, useState } from 'react';
-import { Button, Form, Label, Message, Pagination, Table } from 'semantic-ui-react';
+import { Button, Form, Label, Popup, Pagination, Table } from 'semantic-ui-react';
 import { Link } from 'react-router-dom';
 import { API, copy, showError, showInfo, showSuccess, showWarning, timestamp2string } from '../helpers';
 
@@ -240,15 +240,25 @@ const RedemptionsTable = () => {
                       >
                         复制
                       </Button>
-                      <Button
-                        size={'small'}
-                        negative
-                        onClick={() => {
-                          manageRedemption(redemption.id, 'delete', idx);
-                        }}
+                      <Popup
+                        trigger={
+                          <Button size='small' negative>
+                            删除
+                          </Button>
+                        }
+                        on='click'
+                        flowing
+                        hoverable
                       >
-                        删除
-                      </Button>
+                        <Button
+                          negative
+                          onClick={() => {
+                            manageRedemption(redemption.id, 'delete', idx);
+                          }}
+                        >
+                          确认删除
+                        </Button>
+                      </Popup>
                       <Button
                         size={'small'}
                         disabled={redemption.status === 3}  // used

+ 33 - 1
web/src/components/SystemSetting.js

@@ -1,5 +1,5 @@
 import React, { useEffect, useState } from 'react';
-import { Button, Divider, Form, Grid, Header, Input, Message } from 'semantic-ui-react';
+import { Button, Divider, Form, Grid, Header, Modal, Message } from 'semantic-ui-react';
 import { API, removeTrailingSlash, showError } from '../helpers';
 
 const SystemSetting = () => {
@@ -33,6 +33,7 @@ const SystemSetting = () => {
   let [loading, setLoading] = useState(false);
   const [EmailDomainWhitelist, setEmailDomainWhitelist] = useState([]);
   const [restrictedDomainInput, setRestrictedDomainInput] = useState('');
+  const [showPasswordWarningModal, setShowPasswordWarningModal] = useState(false);
 
   const getOptions = async () => {
     const res = await API.get('/api/option/');
@@ -95,6 +96,11 @@ const SystemSetting = () => {
   };
 
   const handleInputChange = async (e, { name, value }) => {
+    if (name === 'PasswordLoginEnabled' && inputs[name] === 'true') {
+      // block disabling password login
+      setShowPasswordWarningModal(true);
+      return;
+    }
     if (
       name === 'Notice' ||
       name.startsWith('SMTP') ||
@@ -243,6 +249,32 @@ const SystemSetting = () => {
               name='PasswordLoginEnabled'
               onChange={handleInputChange}
             />
+            {
+              showPasswordWarningModal &&
+              <Modal
+                open={showPasswordWarningModal}
+                onClose={() => setShowPasswordWarningModal(false)}
+                size={'tiny'}
+                style={{ maxWidth: '450px' }}
+              >
+                <Modal.Header>警告</Modal.Header>
+                <Modal.Content>
+                  <p>取消密码登录将导致所有未绑定其他登录方式的用户(包括管理员)无法通过密码登录,确认取消?</p>
+                </Modal.Content>
+                <Modal.Actions>
+                  <Button onClick={() => setShowPasswordWarningModal(false)}>取消</Button>
+                  <Button
+                    color='yellow'
+                    onClick={async () => {
+                      setShowPasswordWarningModal(false);
+                      await updateOption('PasswordLoginEnabled', 'false');
+                    }}
+                  >
+                    确定
+                  </Button>
+                </Modal.Actions>
+              </Modal>
+            }
             <Form.Checkbox
               checked={inputs.PasswordRegisterEnabled === 'true'}
               label='允许通过密码进行注册'

+ 11 - 2
web/src/helpers/utils.js

@@ -1,6 +1,11 @@
 import { toast } from 'react-toastify';
 import { toastConstants } from '../constants';
+import React from 'react';
 
+const HTMLToastContent = ({ htmlContent }) => {
+  return <div dangerouslySetInnerHTML={{ __html: htmlContent }} />;
+};
+export default HTMLToastContent;
 export function isAdmin() {
   let user = localStorage.getItem('user');
   if (!user) return false;
@@ -107,8 +112,12 @@ export function showInfo(message) {
   toast.info(message, showInfoOptions);
 }
 
-export function showNotice(message) {
-  toast.info(message, showNoticeOptions);
+export function showNotice(message, isHTML = false) {
+  if (isHTML) {
+    toast(<HTMLToastContent htmlContent={message} />, showNoticeOptions);
+  } else {
+    toast.info(message, showNoticeOptions);
+  }
 }
 
 export function openPage(url) {

+ 7 - 1
web/src/pages/Channel/EditChannel.js

@@ -1,6 +1,6 @@
 import React, { useEffect, useState } from 'react';
 import { Button, Form, Header, Input, Message, Segment } from 'semantic-ui-react';
-import { useParams } from 'react-router-dom';
+import { useParams, useNavigate } from 'react-router-dom';
 import { API, showError, showInfo, showSuccess, verifyJSON } from '../../helpers';
 import { CHANNEL_OPTIONS } from '../../constants';
 
@@ -12,9 +12,14 @@ const MODEL_MAPPING_EXAMPLE = {
 
 const EditChannel = () => {
   const params = useParams();
+  const navigate = useNavigate();
   const channelId = params.id;
   const isEdit = channelId !== undefined;
   const [loading, setLoading] = useState(isEdit);
+  const handleCancel = () => {
+    navigate('/channel');
+  };
+  
   const originInputs = {
     name: '',
     type: 1,
@@ -381,6 +386,7 @@ const EditChannel = () => {
               </Form.Field>
             )
           }
+          <Button onClick={handleCancel}>取消</Button>
           <Button type={isEdit ? 'button' : 'submit'} positive onClick={submit}>提交</Button>
         </Form>
       </Segment>

+ 5 - 4
web/src/pages/Home/index.js

@@ -14,10 +14,11 @@ const Home = () => {
     const { success, message, data } = res.data;
     if (success) {
       let oldNotice = localStorage.getItem('notice');
-      if (data !== oldNotice && data !== '') {
-        showNotice(data);
-        localStorage.setItem('notice', data);
-      }
+        if (data !== oldNotice && data !== '') {
+            const htmlNotice = marked(data);
+            showNotice(htmlNotice, true);
+            localStorage.setItem('notice', data);
+        }
     } else {
       showError(message);
     }

+ 7 - 1
web/src/pages/Redemption/EditRedemption.js

@@ -1,11 +1,12 @@
 import React, { useEffect, useState } from 'react';
 import { Button, Form, Header, Segment } from 'semantic-ui-react';
-import { useParams } from 'react-router-dom';
+import { useParams, useNavigate } from 'react-router-dom';
 import { API, downloadTextAsFile, showError, showSuccess } from '../../helpers';
 import { renderQuota, renderQuotaWithPrompt } from '../../helpers/render';
 
 const EditRedemption = () => {
   const params = useParams();
+  const navigate = useNavigate();
   const redemptionId = params.id;
   const isEdit = redemptionId !== undefined;
   const [loading, setLoading] = useState(isEdit);
@@ -17,6 +18,10 @@ const EditRedemption = () => {
   const [inputs, setInputs] = useState(originInputs);
   const { name, quota, count } = inputs;
 
+  const handleCancel = () => {
+    navigate('/redemption');
+  };
+  
   const handleInputChange = (e, { name, value }) => {
     setInputs((inputs) => ({ ...inputs, [name]: value }));
   };
@@ -113,6 +118,7 @@ const EditRedemption = () => {
             </>
           }
           <Button positive onClick={submit}>提交</Button>
+          <Button onClick={handleCancel}>取消</Button>
         </Form>
       </Segment>
     </>