Commit 39fc3866 by t0ng7u

💄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.
parent 0cad369f
...@@ -967,7 +967,7 @@ const PersonalSetting = () => { ...@@ -967,7 +967,7 @@ const PersonalSetting = () => {
{systemToken && ( {systemToken && (
<div className="mt-3"> <div className="mt-3">
<Input <Input
readOnly readonly
value={systemToken} value={systemToken}
onClick={handleSystemTokenClick} onClick={handleSystemTokenClick}
size="large" size="large"
......
...@@ -471,10 +471,11 @@ ...@@ -471,10 +471,11 @@
"请输入新的密码": "Please enter a new password", "请输入新的密码": "Please enter a new password",
"显示名称": "Display Name", "显示名称": "Display Name",
"请输入新的显示名称": "Please enter a new display name", "请输入新的显示名称": "Please enter a new display name",
"已绑定的 GitHub 账户": "GitHub Account Bound", "已绑定的 GITHUB 账户": "Bound GitHub 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", "已绑定的 WECHAT 账户": "Bound WeChat Account",
"已绑定的微信账户": "WeChat Account Bound", "已绑定的 EMAIL 账户": "Bound Email Account",
"已绑定的邮箱账户": "Email Account Bound", "已绑定的 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!", "用户信息更新成功!": "User information updated successfully!",
"使用明细(总消耗额度:{renderQuota(stat.quota)})": "Usage Details (Total Consumption Quota: {renderQuota(stat.quota)})", "使用明细(总消耗额度:{renderQuota(stat.quota)})": "Usage Details (Total Consumption Quota: {renderQuota(stat.quota)})",
"用户名称": "User Name", "用户名称": "User Name",
......
...@@ -130,7 +130,7 @@ const Home = () => { ...@@ -130,7 +130,7 @@ const Home = () => {
{/* BASE URL 与端点选择 */} {/* 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"> <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 <Input
readOnly readonly
value={serverAddress} value={serverAddress}
className="flex-1 !rounded-full" className="flex-1 !rounded-full"
size={isMobile() ? 'default' : 'large'} size={isMobile() ? 'default' : 'large'}
......
...@@ -931,7 +931,7 @@ const TopUp = () => { ...@@ -931,7 +931,7 @@ const TopUp = () => {
<Title heading={6}>{t('邀请链接')}</Title> <Title heading={6}>{t('邀请链接')}</Title>
<Input <Input
value={affLink} value={affLink}
readOnly readonly
size='large' size='large'
suffix={ suffix={
<Button <Button
......
import React, { useState } from 'react'; import React, { useState, useRef } from 'react';
import { API, isMobile, showError, showSuccess } from '../../helpers'; import { API, isMobile, showError, showSuccess } from '../../helpers';
import { import {
Button, Button,
Input,
SideSheet, SideSheet,
Space, Space,
Spin, Spin,
Typography, Typography,
Card, Card,
Tag, Tag,
Avatar Avatar,
Form,
Row,
Col,
} from '@douyinfe/semi-ui'; } from '@douyinfe/semi-ui';
import { import {
IconSave, IconSave,
...@@ -22,32 +24,23 @@ const { Text, Title } = Typography; ...@@ -22,32 +24,23 @@ const { Text, Title } = Typography;
const AddUser = (props) => { const AddUser = (props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const originInputs = { const formApiRef = useRef(null);
const [loading, setLoading] = useState(false);
const getInitValues = () => ({
username: '', username: '',
display_name: '', display_name: '',
password: '', password: '',
remark: '', 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); setLoading(true);
if (inputs.username === '' || inputs.password === '') { const res = await API.post(`/api/user/`, values);
setLoading(false);
showError(t('用户名和密码不能为空!'));
return;
}
const res = await API.post(`/api/user/`, inputs);
const { success, message } = res.data; const { success, message } = res.data;
if (success) { if (success) {
showSuccess(t('用户账户创建成功!')); showSuccess(t('用户账户创建成功!'));
setInputs(originInputs); formApiRef.current?.setValues(getInitValues());
props.refresh(); props.refresh();
props.handleClose(); props.handleClose();
} else { } else {
...@@ -85,7 +78,7 @@ const AddUser = (props) => { ...@@ -85,7 +78,7 @@ const AddUser = (props) => {
<Button <Button
theme="solid" theme="solid"
className="!rounded-full" className="!rounded-full"
onClick={submit} onClick={() => formApiRef.current?.submitForm()}
icon={<IconSave />} icon={<IconSave />}
loading={loading} loading={loading}
> >
...@@ -107,7 +100,17 @@ const AddUser = (props) => { ...@@ -107,7 +100,17 @@ const AddUser = (props) => {
onCancel={() => handleCancel()} onCancel={() => handleCancel()}
> >
<Spin spinning={loading}> <Spin spinning={loading}>
<div className="p-6"> <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"> <Card className="!rounded-2xl shadow-sm border-0">
<div className="flex items-center mb-2"> <div className="flex items-center mb-2">
<Avatar size="small" color="blue" className="mr-2 shadow-md"> <Avatar size="small" color="blue" className="mr-2 shadow-md">
...@@ -119,59 +122,38 @@ const AddUser = (props) => { ...@@ -119,59 +122,38 @@ const AddUser = (props) => {
</div> </div>
</div> </div>
<div className="space-y-4"> <Row gutter={12}>
<div> <Col span={24}>
<Text strong className="block mb-2">{t('用户名')}</Text> <Form.Input
<Input field='username'
label={t('用户名')}
placeholder={t('请输入用户名')} placeholder={t('请输入用户名')}
onChange={(value) => handleInputChange('username', value)} rules={[{ required: true, message: t('请输入用户名') }]} />
value={username} </Col>
autoComplete="off" <Col span={24}>
className="!rounded-lg" <Form.Input
showClear field='display_name'
required label={t('显示名称')}
/> placeholder={t('请输入显示名称')} />
</div> </Col>
<Col span={24}>
<div> <Form.Input
<Text strong className="block mb-2">{t('显示名称')}</Text> field='password'
<Input label={t('密码')}
placeholder={t('请输入显示名称')} type='password'
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('请输入密码')} placeholder={t('请输入密码')}
onChange={(value) => handleInputChange('password', value)} rules={[{ required: true, message: t('请输入密码') }]} />
value={password} </Col>
autoComplete="off" <Col span={24}>
className="!rounded-lg" <Form.Input
required field='remark'
/> label={t('备注')}
</div> placeholder={t('请输入备注(仅管理员可见)')} />
</Col>
<div> </Row>
<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> </Card>
</div> </div>
</Form>
</Spin> </Spin>
</SideSheet> </SideSheet>
</> </>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment