فهرست منبع

💄refactor: enhance `EditUser` and `AddUser` form validation & UX

Changes in `web/src/pages/User/EditUser.js`:
• Added `rules` to
  – `Form.Select group`: now required with error “Please select group”.
  – `Form.InputNumber quota`: now required with error “Please enter quota”.
• Added `step={500000}` to quota `InputNumber` for quicker numeric input.
• Replaced invalid `readonly` with React-correct `readOnly`, and added descriptive placeholders for all binding-info fields (GitHub/OIDC/WeChat/Email/Telegram).
• Removed unused `downloadTextAsFile` import.

These updates tighten form validation, improve data entry ergonomics, and restore clear read-only indicators for third-party bindings.
t0ng7u 8 ماه پیش
والد
کامیت
38d3ab5acf

+ 1 - 1
web/src/components/settings/PersonalSetting.js

@@ -967,7 +967,7 @@ const PersonalSetting = () => {
                                 {systemToken && (
                                   <div className="mt-3">
                                     <Input
-                                      readOnly
+                                      readonly
                                       value={systemToken}
                                       onClick={handleSystemTokenClick}
                                       size="large"

+ 5 - 4
web/src/i18n/locales/en.json

@@ -471,10 +471,11 @@
   "请输入新的密码": "Please enter a new password",
   "显示名称": "Display Name",
   "请输入新的显示名称": "Please enter a new display name",
-  "已绑定的 GitHub 账户": "GitHub Account Bound",
-  "此项只读,要用户通过个人设置页面的相关绑��按钮进��绑���,不可直接修改": "This item is read-only. Users need to bind through the relevant binding button on the personal settings page, and cannot be modified directly",
-  "已绑定的微信账户": "WeChat Account Bound",
-  "已绑定的邮箱账户": "Email Account Bound",
+  "已绑定的 GITHUB 账户": "Bound GitHub Account",
+  "已绑定的 WECHAT 账户": "Bound WeChat Account",
+  "已绑定的 EMAIL 账户": "Bound Email Account",
+  "已绑定的 TELEGRAM 账户": "Bound Telegram Account",
+  "此项只读,要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改": "This item is read-only. Users need to bind through the relevant binding button on the personal settings page, and cannot be modified directly",
   "用户信息更新成功!": "User information updated successfully!",
   "使用明细(总消耗额度:{renderQuota(stat.quota)})": "Usage Details (Total Consumption Quota: {renderQuota(stat.quota)})",
   "用户名称": "User Name",

+ 1 - 1
web/src/pages/Home/index.js

@@ -130,7 +130,7 @@ const Home = () => {
                   {/* BASE URL 与端点选择 */}
                   <div className="flex flex-col md:flex-row items-center justify-center gap-4 w-full mt-4 md:mt-6 max-w-md">
                     <Input
-                      readOnly
+                      readonly
                       value={serverAddress}
                       className="flex-1 !rounded-full"
                       size={isMobile() ? 'default' : 'large'}

+ 1 - 1
web/src/pages/TopUp/index.js

@@ -931,7 +931,7 @@ const TopUp = () => {
                 <Title heading={6}>{t('邀请链接')}</Title>
                 <Input
                   value={affLink}
-                  readOnly
+                  readonly
                   size='large'
                   suffix={
                     <Button

+ 66 - 84
web/src/pages/User/AddUser.js

@@ -1,15 +1,17 @@
-import React, { useState } from 'react';
+import React, { useState, useRef } from 'react';
 import { API, isMobile, showError, showSuccess } from '../../helpers';
 import {
   Button,
-  Input,
   SideSheet,
   Space,
   Spin,
   Typography,
   Card,
   Tag,
-  Avatar
+  Avatar,
+  Form,
+  Row,
+  Col,
 } from '@douyinfe/semi-ui';
 import {
   IconSave,
@@ -22,32 +24,23 @@ const { Text, Title } = Typography;
 
 const AddUser = (props) => {
   const { t } = useTranslation();
-  const originInputs = {
+  const formApiRef = useRef(null);
+  const [loading, setLoading] = useState(false);
+
+  const getInitValues = () => ({
     username: '',
     display_name: '',
     password: '',
     remark: '',
-  };
-  const [inputs, setInputs] = useState(originInputs);
-  const [loading, setLoading] = useState(false);
-  const { username, display_name, password, remark } = inputs;
+  });
 
-  const handleInputChange = (name, value) => {
-    setInputs((inputs) => ({ ...inputs, [name]: value }));
-  };
-
-  const submit = async () => {
+  const submit = async (values) => {
     setLoading(true);
-    if (inputs.username === '' || inputs.password === '') {
-      setLoading(false);
-      showError(t('用户名和密码不能为空!'));
-      return;
-    }
-    const res = await API.post(`/api/user/`, inputs);
+    const res = await API.post(`/api/user/`, values);
     const { success, message } = res.data;
     if (success) {
       showSuccess(t('用户账户创建成功!'));
-      setInputs(originInputs);
+      formApiRef.current?.setValues(getInitValues());
       props.refresh();
       props.handleClose();
     } else {
@@ -85,7 +78,7 @@ const AddUser = (props) => {
               <Button
                 theme="solid"
                 className="!rounded-full"
-                onClick={submit}
+                onClick={() => formApiRef.current?.submitForm()}
                 icon={<IconSave />}
                 loading={loading}
               >
@@ -107,71 +100,60 @@ const AddUser = (props) => {
         onCancel={() => handleCancel()}
       >
         <Spin spinning={loading}>
-          <div className="p-6">
-            <Card className="!rounded-2xl shadow-sm border-0">
-              <div className="flex items-center mb-2">
-                <Avatar size="small" color="blue" className="mr-2 shadow-md">
-                  <IconUserAdd size={16} />
-                </Avatar>
-                <div>
-                  <Text className="text-lg font-medium">{t('用户信息')}</Text>
-                  <div className="text-xs text-gray-600">{t('创建新用户账户')}</div>
+          <Form
+            initValues={getInitValues()}
+            getFormApi={(api) => formApiRef.current = api}
+            onSubmit={submit}
+            onSubmitFail={(errs) => {
+              const first = Object.values(errs)[0];
+              if (first) showError(Array.isArray(first) ? first[0] : first);
+              formApiRef.current?.scrollToError();
+            }}
+          >
+            <div className="p-6 space-y-6">
+              <Card className="!rounded-2xl shadow-sm border-0">
+                <div className="flex items-center mb-2">
+                  <Avatar size="small" color="blue" className="mr-2 shadow-md">
+                    <IconUserAdd size={16} />
+                  </Avatar>
+                  <div>
+                    <Text className="text-lg font-medium">{t('用户信息')}</Text>
+                    <div className="text-xs text-gray-600">{t('创建新用户账户')}</div>
+                  </div>
                 </div>
-              </div>
 
-              <div className="space-y-4">
-                <div>
-                  <Text strong className="block mb-2">{t('用户名')}</Text>
-                  <Input
-                    placeholder={t('请输入用户名')}
-                    onChange={(value) => handleInputChange('username', value)}
-                    value={username}
-                    autoComplete="off"
-                    className="!rounded-lg"
-                    showClear
-                    required
-                  />
-                </div>
-
-                <div>
-                  <Text strong className="block mb-2">{t('显示名称')}</Text>
-                  <Input
-                    placeholder={t('请输入显示名称')}
-                    onChange={(value) => handleInputChange('display_name', value)}
-                    value={display_name}
-                    autoComplete="off"
-                    className="!rounded-lg"
-                    showClear
-                  />
-                </div>
-
-                <div>
-                  <Text strong className="block mb-2">{t('密码')}</Text>
-                  <Input
-                    type="password"
-                    placeholder={t('请输入密码')}
-                    onChange={(value) => handleInputChange('password', value)}
-                    value={password}
-                    autoComplete="off"
-                    className="!rounded-lg"
-                    required
-                  />
-                </div>
-
-                <div>
-                  <Text strong className="block mb-2">{t('备注')}</Text>
-                  <Input
-                    placeholder={t('请输入备注(仅管理员可见)')}
-                    onChange={(value) => handleInputChange('remark', value)}
-                    value={remark}
-                    autoComplete="off"
-                    className="!rounded-lg"
-                    showClear
-                  />
-                </div>
-              </div>
-            </Card>
-          </div>
+                <Row gutter={12}>
+                  <Col span={24}>
+                    <Form.Input
+                      field='username'
+                      label={t('用户名')}
+                      placeholder={t('请输入用户名')}
+                      rules={[{ required: true, message: t('请输入用户名') }]} />
+                  </Col>
+                  <Col span={24}>
+                    <Form.Input
+                      field='display_name'
+                      label={t('显示名称')}
+                      placeholder={t('请输入显示名称')} />
+                  </Col>
+                  <Col span={24}>
+                    <Form.Input
+                      field='password'
+                      label={t('密码')}
+                      type='password'
+                      placeholder={t('请输入密码')}
+                      rules={[{ required: true, message: t('请输入密码') }]} />
+                  </Col>
+                  <Col span={24}>
+                    <Form.Input
+                      field='remark'
+                      label={t('备注')}
+                      placeholder={t('请输入备注(仅管理员可见)')} />
+                  </Col>
+                </Row>
+              </Card>
+            </div>
+          </Form>
         </Spin>
       </SideSheet>
     </>

+ 209 - 284
web/src/pages/User/EditUser.js

@@ -1,18 +1,27 @@
-import React, { useEffect, useState } from 'react';
-import { useNavigate } from 'react-router-dom';
-import { API, isMobile, showError, showSuccess, renderQuota, renderQuotaWithPrompt } from '../../helpers';
+import React, { useEffect, useState, useRef } from 'react';
+import { useTranslation } from 'react-i18next';
+import {
+  API,
+  isMobile,
+  showError,
+  showSuccess,
+  renderQuota,
+  renderQuotaWithPrompt,
+} from '../../helpers';
 import {
   Button,
-  Input,
   Modal,
-  Select,
   SideSheet,
   Space,
   Spin,
   Typography,
   Card,
   Tag,
+  Form,
   Avatar,
+  Row,
+  Col,
+  Input,
 } from '@douyinfe/semi-ui';
 import {
   IconUser,
@@ -22,73 +31,55 @@ import {
   IconUserGroup,
   IconPlus,
 } from '@douyinfe/semi-icons';
-import { useTranslation } from 'react-i18next';
 
 const { Text, Title } = Typography;
 
 const EditUser = (props) => {
+  const { t } = useTranslation();
   const userId = props.editingUser.id;
   const [loading, setLoading] = useState(true);
   const [addQuotaModalOpen, setIsModalOpen] = useState(false);
-  const [addQuotaLocal, setAddQuotaLocal] = useState('');
-  const [inputs, setInputs] = useState({
+  const [addQuotaLocal, setAddQuotaLocal] = useState('0');
+  const [groupOptions, setGroupOptions] = useState([]);
+  const formApiRef = useRef(null);
+
+  const isEdit = Boolean(userId);
+
+  const getInitValues = () => ({
     username: '',
     display_name: '',
     password: '',
     github_id: '',
     oidc_id: '',
     wechat_id: '',
+    telegram_id: '',
     email: '',
     quota: 0,
     group: 'default',
     remark: '',
   });
-  const [groupOptions, setGroupOptions] = useState([]);
-  const {
-    username,
-    display_name,
-    password,
-    github_id,
-    oidc_id,
-    wechat_id,
-    telegram_id,
-    email,
-    quota,
-    group,
-    remark,
-  } = inputs;
-  const handleInputChange = (name, value) => {
-    setInputs((inputs) => ({ ...inputs, [name]: value }));
-  };
+
   const fetchGroups = async () => {
     try {
       let res = await API.get(`/api/group/`);
       setGroupOptions(
-        res.data.data.map((group) => ({
-          label: group,
-          value: group,
-        })),
+        res.data.data.map((g) => ({ label: g, value: g }))
       );
-    } catch (error) {
-      showError(error.message);
+    } catch (e) {
+      showError(e.message);
     }
   };
-  const navigate = useNavigate();
-  const handleCancel = () => {
-    props.handleClose();
-  };
+
+  const handleCancel = () => props.handleClose();
+
   const loadUser = async () => {
     setLoading(true);
-    let res = undefined;
-    if (userId) {
-      res = await API.get(`/api/user/${userId}`);
-    } else {
-      res = await API.get(`/api/user/self`);
-    }
+    const url = userId ? `/api/user/${userId}` : `/api/user/self`;
+    const res = await API.get(url);
     const { success, message, data } = res.data;
     if (success) {
       data.password = '';
-      setInputs(data);
+      formApiRef.current?.setValues({ ...getInitValues(), ...data });
     } else {
       showError(message);
     }
@@ -96,27 +87,23 @@ const EditUser = (props) => {
   };
 
   useEffect(() => {
-    loadUser().then();
-    if (userId) {
-      fetchGroups().then();
-    }
+    loadUser();
+    if (userId) fetchGroups();
   }, [props.editingUser.id]);
 
-  const submit = async () => {
+  /* ----------------------- submit ----------------------- */
+  const submit = async (values) => {
     setLoading(true);
-    let res = undefined;
+    let payload = { ...values };
+    if (typeof payload.quota === 'string') payload.quota = parseInt(payload.quota) || 0;
     if (userId) {
-      let data = { ...inputs, id: parseInt(userId) };
-      if (typeof data.quota === 'string') {
-        data.quota = parseInt(data.quota);
-      }
-      res = await API.put(`/api/user/`, data);
-    } else {
-      res = await API.put(`/api/user/self`, inputs);
+      payload.id = parseInt(userId);
     }
+    const url = userId ? `/api/user/` : `/api/user/self`;
+    const res = await API.put(url, payload);
     const { success, message } = res.data;
     if (success) {
-      showSuccess('用户信息更新成功!');
+      showSuccess(t('用户信息更新成功!'));
       props.refresh();
       props.handleClose();
     } else {
@@ -125,53 +112,48 @@ const EditUser = (props) => {
     setLoading(false);
   };
 
+  /* --------------------- quota helper -------------------- */
   const addLocalQuota = () => {
-    let newQuota = parseInt(quota) + parseInt(addQuotaLocal);
-    setInputs((inputs) => ({ ...inputs, quota: newQuota }));
+    const current = parseInt(formApiRef.current?.getValue('quota') || 0);
+    const delta = parseInt(addQuotaLocal) || 0;
+    formApiRef.current?.setValue('quota', current + delta);
   };
 
-  const openAddQuotaModal = () => {
-    setAddQuotaLocal('0');
-    setIsModalOpen(true);
-  };
-
-  const { t } = useTranslation();
-
+  /* --------------------------- UI --------------------------- */
   return (
     <>
       <SideSheet
-        placement={'right'}
+        placement='right'
         title={
           <Space>
-            <Tag color="blue" shape="circle">{t('编辑')}</Tag>
-            <Title heading={4} className="m-0">
-              {t('编辑用户')}
+            <Tag color='blue' shape='circle'>
+              {t(isEdit ? '编辑' : '新建')}
+            </Tag>
+            <Title heading={4} className='m-0'>
+              {isEdit ? t('编辑用户') : t('创建用户')}
             </Title>
           </Space>
         }
-        headerStyle={{
-          borderBottom: '1px solid var(--semi-color-border)',
-          padding: '24px'
-        }}
-        bodyStyle={{ padding: '0' }}
+        headerStyle={{ borderBottom: '1px solid var(--semi-color-border)', padding: '24px' }}
+        bodyStyle={{ padding: 0 }}
         visible={props.visible}
         width={isMobile() ? '100%' : 600}
         footer={
-          <div className="flex justify-end bg-white">
+          <div className='flex justify-end bg-white'>
             <Space>
               <Button
-                theme="solid"
-                className="!rounded-full"
-                onClick={submit}
+                theme='solid'
+                className='!rounded-full'
+                onClick={() => formApiRef.current?.submitForm()}
                 icon={<IconSave />}
                 loading={loading}
               >
                 {t('提交')}
               </Button>
               <Button
-                theme="light"
-                className="!rounded-full"
-                type="primary"
+                theme='light'
+                className='!rounded-full'
+                type='primary'
                 onClick={handleCancel}
                 icon={<IconClose />}
               >
@@ -181,215 +163,154 @@ const EditUser = (props) => {
           </div>
         }
         closeIcon={null}
-        onCancel={() => handleCancel()}
+        onCancel={handleCancel}
       >
         <Spin spinning={loading}>
-          <div className="p-6">
-            <Card className="!rounded-2xl shadow-sm border-0 mb-6">
-              {/* Header: Basic Info */}
-              <div className="flex items-center mb-2">
-                <Avatar size="small" color="blue" className="mr-2 shadow-md">
-                  <IconUser size={16} />
-                </Avatar>
-                <div>
-                  <Text className="text-lg font-medium">{t('基本信息')}</Text>
-                  <div className="text-xs text-gray-600">{t('用户的基本账户信息')}</div>
-                </div>
-              </div>
-
-              <div className="space-y-4">
-                <div>
-                  <Text strong className="block mb-2">{t('用户名')}</Text>
-                  <Input
-                    placeholder={t('请输入新的用户名')}
-                    onChange={(value) => handleInputChange('username', value)}
-                    value={username}
-                    autoComplete="new-password"
-                    className="!rounded-lg"
-                    showClear
-                  />
-                </div>
-
-                <div>
-                  <Text strong className="block mb-2">{t('密码')}</Text>
-                  <Input
-                    type="password"
-                    placeholder={t('请输入新的密码,最短 8 位')}
-                    onChange={(value) => handleInputChange('password', value)}
-                    value={password}
-                    autoComplete="new-password"
-                    className="!rounded-lg"
-                  />
-                </div>
-
-                <div>
-                  <Text strong className="block mb-2">{t('显示名称')}</Text>
-                  <Input
-                    placeholder={t('请输入新的显示名称')}
-                    onChange={(value) => handleInputChange('display_name', value)}
-                    value={display_name}
-                    autoComplete="new-password"
-                    className="!rounded-lg"
-                    showClear
-                  />
-                </div>
+          <Form
+            initValues={getInitValues()}
+            getFormApi={(api) => (formApiRef.current = api)}
+            onSubmit={submit}
+          >
+            {({ values }) => (
+              <div className='p-6 space-y-6'>
+                {/* 基本信息 */}
+                <Card className='!rounded-2xl shadow-sm border-0'>
+                  <div className='flex items-center mb-2'>
+                    <Avatar size='small' color='blue' className='mr-2 shadow-md'>
+                      <IconUser size={16} />
+                    </Avatar>
+                    <div>
+                      <Text className='text-lg font-medium'>{t('基本信息')}</Text>
+                      <div className='text-xs text-gray-600'>{t('用户的基本账户信息')}</div>
+                    </div>
+                  </div>
 
-                <div>
-                  <Text strong className="block mb-2">{t('备注')}</Text>
-                  <Input
-                    placeholder={t('请输入备注(仅管理员可见)')}
-                    onChange={(value) => handleInputChange('remark', value)}
-                    value={remark}
-                    autoComplete="off"
-                    className="!rounded-lg"
-                    showClear
-                  />
-                </div>
-              </div>
-            </Card>
+                  <Row gutter={12}>
+                    <Col span={24}>
+                      <Form.Input
+                        field='username'
+                        label={t('用户名')}
+                        placeholder={t('请输入新的用户名')}
+                        rules={[{ required: true, message: t('请输入用户名') }]}
+                        showClear
+                      />
+                    </Col>
 
-            {userId && (
-              <Card className="!rounded-2xl shadow-sm border-0 mb-6">
-                {/* Header: Permission Settings */}
-                <div className="flex items-center mb-2">
-                  <Avatar size="small" color="green" className="mr-2 shadow-md">
-                    <IconUserGroup size={16} />
-                  </Avatar>
-                  <div>
-                    <Text className="text-lg font-medium">{t('权限设置')}</Text>
-                    <div className="text-xs text-gray-600">{t('用户分组和额度管理')}</div>
-                  </div>
-                </div>
+                    <Col span={24}>
+                      <Form.Input
+                        field='password'
+                        label={t('密码')}
+                        placeholder={t('请输入新的密码,最短 8 位')}
+                        mode='password'
+                        showClear
+                      />
+                    </Col>
 
-                <div className="space-y-4">
-                  <div>
-                    <Text strong className="block mb-2">{t('分组')}</Text>
-                    <Select
-                      placeholder={t('请选择分组')}
-                      search
-                      allowAdditions
-                      additionLabel={t(
-                        '请在系统设置页面编辑分组倍率以添加新的分组:',
-                      )}
-                      onChange={(value) => handleInputChange('group', value)}
-                      value={inputs.group}
-                      autoComplete="new-password"
-                      optionList={groupOptions}
-                      className="w-full !rounded-lg"
-                    />
-                  </div>
+                    <Col span={24}>
+                      <Form.Input
+                        field='display_name'
+                        label={t('显示名称')}
+                        placeholder={t('请输入新的显示名称')}
+                        showClear
+                      />
+                    </Col>
 
-                  <div>
-                    <div className="flex justify-between mb-2">
-                      <Text strong>{t('剩余额度')}</Text>
-                      <Text type="tertiary">{renderQuotaWithPrompt(quota)}</Text>
-                    </div>
-                    <div className="flex gap-2">
-                      <Input
-                        placeholder={t('请输入新的剩余额度')}
-                        onChange={(value) => handleInputChange('quota', value)}
-                        value={quota}
-                        type="number"
-                        autoComplete="new-password"
-                        className="flex-1 !rounded-lg"
+                    <Col span={24}>
+                      <Form.Input
+                        field='remark'
+                        label={t('备注')}
+                        placeholder={t('请输入备注(仅管理员可见)')}
+                        showClear
                       />
-                      <Button
-                        onClick={openAddQuotaModal}
-                        className="!rounded-lg"
-                        icon={<IconPlus />}
-                      >
-                        {t('添加额度')}
-                      </Button>
-                    </div>
-                  </div>
-                </div>
-              </Card>
-            )}
+                    </Col>
+                  </Row>
+                </Card>
 
-            <Card className="!rounded-2xl shadow-sm border-0">
-              {/* Header: Bindings */}
-              <div className="flex items-center mb-2">
-                <Avatar size="small" color="purple" className="mr-2 shadow-md">
-                  <IconLink size={16} />
-                </Avatar>
-                <div>
-                  <Text className="text-lg font-medium">{t('绑定信息')}</Text>
-                  <div className="text-xs text-gray-600">{t('第三方账户绑定状态(只读)')}</div>
-                </div>
-              </div>
+                {/* 权限设置 */}
+                {userId && (
+                  <Card className='!rounded-2xl shadow-sm border-0'>
+                    <div className='flex items-center mb-2'>
+                      <Avatar size='small' color='green' className='mr-2 shadow-md'>
+                        <IconUserGroup size={16} />
+                      </Avatar>
+                      <div>
+                        <Text className='text-lg font-medium'>{t('权限设置')}</Text>
+                        <div className='text-xs text-gray-600'>{t('用户分组和额度管理')}</div>
+                      </div>
+                    </div>
 
-              <div className="space-y-4">
-                <div>
-                  <Text strong className="block mb-2">{t('已绑定的 GitHub 账户')}</Text>
-                  <Input
-                    value={github_id}
-                    autoComplete="new-password"
-                    placeholder={t(
-                      '此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改',
-                    )}
-                    readonly
-                    className="!rounded-lg"
-                  />
-                </div>
+                    <Row gutter={12}>
+                      <Col span={24}>
+                        <Form.Select
+                          field='group'
+                          label={t('分组')}
+                          placeholder={t('请选择分组')}
+                          optionList={groupOptions}
+                          allowAdditions
+                          search
+                          rules={[{ required: true, message: t('请选择分组') }]}
+                        />
+                      </Col>
 
-                <div>
-                  <Text strong className="block mb-2">{t('已绑定的 OIDC 账户')}</Text>
-                  <Input
-                    value={oidc_id}
-                    placeholder={t(
-                      '此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改',
-                    )}
-                    readonly
-                    className="!rounded-lg"
-                  />
-                </div>
+                      <Col span={10}>
+                        <Form.InputNumber
+                          field='quota'
+                          label={t('剩余额度')}
+                          placeholder={t('请输入新的剩余额度')}
+                          min={0}
+                          step={500000}
+                          extraText={renderQuotaWithPrompt(values.quota || 0)}
+                          rules={[{ required: true, message: t('请输入额度') }]}
+                          style={{ width: '100%' }}
+                        />
+                      </Col>
 
-                <div>
-                  <Text strong className="block mb-2">{t('已绑定的微信账户')}</Text>
-                  <Input
-                    value={wechat_id}
-                    autoComplete="new-password"
-                    placeholder={t(
-                      '此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改',
-                    )}
-                    readonly
-                    className="!rounded-lg"
-                  />
-                </div>
+                      <Col span={14}>
+                        <Form.Slot label={t('添加额度')}>
+                          <Button
+                            icon={<IconPlus />}
+                            onClick={() => setIsModalOpen(true)}
+                          />
+                        </Form.Slot>
+                      </Col>
+                    </Row>
+                  </Card>
+                )}
 
-                <div>
-                  <Text strong className="block mb-2">{t('已绑定的邮箱账户')}</Text>
-                  <Input
-                    value={email}
-                    autoComplete="new-password"
-                    placeholder={t(
-                      '此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改',
-                    )}
-                    readonly
-                    className="!rounded-lg"
-                  />
-                </div>
+                {/* 绑定信息 */}
+                <Card className='!rounded-2xl shadow-sm border-0'>
+                  <div className='flex items-center mb-2'>
+                    <Avatar size='small' color='purple' className='mr-2 shadow-md'>
+                      <IconLink size={16} />
+                    </Avatar>
+                    <div>
+                      <Text className='text-lg font-medium'>{t('绑定信息')}</Text>
+                      <div className='text-xs text-gray-600'>{t('第三方账户绑定状态(只读)')}</div>
+                    </div>
+                  </div>
 
-                <div>
-                  <Text strong className="block mb-2">{t('已绑定的 Telegram 账户')}</Text>
-                  <Input
-                    value={telegram_id}
-                    autoComplete="new-password"
-                    placeholder={t(
-                      '此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改',
-                    )}
-                    readonly
-                    className="!rounded-lg"
-                  />
-                </div>
+                  <Row gutter={12}>
+                    {['github_id', 'oidc_id', 'wechat_id', 'email', 'telegram_id'].map((field) => (
+                      <Col span={24} key={field}>
+                        <Form.Input
+                          field={field}
+                          label={t(`已绑定的 ${field.replace('_id', '').toUpperCase()} 账户`)}
+                          readonly
+                          placeholder={t('此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改')}
+                        />
+                      </Col>
+                    ))}
+                  </Row>
+                </Card>
               </div>
-            </Card>
-          </div>
+            )}
+          </Form>
         </Spin>
       </SideSheet>
 
+      {/* 添加额度模态框 */}
       <Modal
-        centered={true}
+        centered
         visible={addQuotaModalOpen}
         onOk={() => {
           addLocalQuota();
@@ -398,26 +319,30 @@ const EditUser = (props) => {
         onCancel={() => setIsModalOpen(false)}
         closable={null}
         title={
-          <div className="flex items-center">
-            <IconPlus className="mr-2" />
+          <div className='flex items-center'>
+            <IconPlus className='mr-2' />
             {t('添加额度')}
           </div>
         }
       >
-        <div className="mb-4">
-          <Text type="secondary" className="block mb-2">
-            {`${t('新额度')}${renderQuota(quota)} + ${renderQuota(addQuotaLocal)} = ${renderQuota(quota + parseInt(addQuotaLocal || 0))}`}
-          </Text>
+        <div className='mb-4'>
+          {
+            (() => {
+              const current = formApiRef.current?.getValue('quota') || 0;
+              return (
+                <Text type='secondary' className='block mb-2'>
+                  {`${t('新额度')}${renderQuota(current)} + ${renderQuota(addQuotaLocal)} = ${renderQuota(current + parseInt(addQuotaLocal || 0))}`}
+                </Text>
+              );
+            })()
+          }
         </div>
         <Input
           placeholder={t('需要添加的额度(支持负数)')}
-          onChange={(value) => {
-            setAddQuotaLocal(value);
-          }}
+          type='number'
           value={addQuotaLocal}
-          type="number"
-          autoComplete="new-password"
-          className="!rounded-lg"
+          onChange={setAddQuotaLocal}
+          showClear
         />
       </Modal>
     </>