diff --git a/src/components/form-buttons/index.tsx b/src/components/form-buttons/index.tsx index c0268e97..d12880d1 100644 --- a/src/components/form-buttons/index.tsx +++ b/src/components/form-buttons/index.tsx @@ -1,3 +1,4 @@ +import { useIntl } from '@umijs/max'; import { Button, Space } from 'antd'; type FormButtonsProps = { @@ -18,6 +19,7 @@ const FormButtons: React.FC = ({ showOk = true, htmlType = 'button' }) => { + const intl = useIntl(); return ( {showOk && ( @@ -27,12 +29,12 @@ const FormButtons: React.FC = ({ style={{ width: '120px' }} htmlType={htmlType} > - {okText || '保存'} + {okText || intl.formatMessage({ id: 'common.button.save' })} )} {showCancel && ( )} diff --git a/src/components/password-validate/index.tsx b/src/components/password-validate/index.tsx new file mode 100644 index 00000000..edbbcb64 --- /dev/null +++ b/src/components/password-validate/index.tsx @@ -0,0 +1,68 @@ +import { + digitReg, + lowercaseReg, + specialCharacterReg, + uppercaseReg +} from '@/config'; +import { CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons'; +import { useIntl } from '@umijs/max'; +import { Space } from 'antd'; + +const PasswordValidate: React.FC<{ value: string }> = ({ value = '' }) => { + const intl = useIntl(); + + const renderIcon = ({ valid, text }: { valid: boolean; text: string }) => { + return ( + <> + {valid ? ( + + ) : ( + + )} + + {text} + + + ); + }; + return ( + + + {renderIcon({ + valid: uppercaseReg.test(value), + text: intl.formatMessage({ id: 'users.password.uppcase' }) + })} + + + {renderIcon({ + valid: lowercaseReg.test(value), + text: intl.formatMessage({ id: 'users.password.lowercase' }) + })} + + + + {renderIcon({ + valid: digitReg.test(value), + text: intl.formatMessage({ id: 'users.password.number' }) + })} + + + {renderIcon({ + valid: value.length >= 6 && value.length <= 12, + text: intl.formatMessage({ id: 'users.password.length' }) + })} + + + {renderIcon({ + valid: specialCharacterReg.test(value), + text: intl.formatMessage({ id: 'users.password.special' }) + })} + + + ); +}; + +export default PasswordValidate; diff --git a/src/config/index.ts b/src/config/index.ts index 38033fbb..3a0c7372 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -43,3 +43,18 @@ export const WatchEventType = { UPDATE: 'MODIFIED', DELETE: 'DELETED' }; + +export const PasswordReg = + /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_])(?=\S+$).{6,12}$/; + +export const uppercaseReg = /(?=.*[A-Z])/; + +export const lowercaseReg = /(?=.*[a-z])/; + +export const digitReg = /(?=.*\d)/; + +export const specialCharacterReg = /(?=.*[\W_])/; + +export const noSpaceReg = /(?=\S+$)/; + +export const lengthReg = /^.{6,12}$/; diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts index f18d58f8..b611c962 100644 --- a/src/locales/en-US.ts +++ b/src/locales/en-US.ts @@ -4,6 +4,7 @@ import menu from './en-US/menu'; import models from './en-US/models'; import playground from './en-US/playground'; import resources from './en-US/resources'; +import users from './en-US/users'; export default { ...common, @@ -11,5 +12,6 @@ export default { ...models, ...playground, ...resources, - ...apikeys + ...apikeys, + ...users }; diff --git a/src/locales/en-US/apikeys.ts b/src/locales/en-US/apikeys.ts index 17c95051..cdac3425 100644 --- a/src/locales/en-US/apikeys.ts +++ b/src/locales/en-US/apikeys.ts @@ -1,6 +1,9 @@ export default { 'apikeys.title': 'API Keys', + 'apikeys.table.apikeys': 'keys', 'apikeys.button.create': 'New API Key', 'apikeys.form.expiretime': 'Expiration', - 'apikeys.table.name': 'Key Name' + 'apikeys.table.name': 'Key Name', + 'apikeys.table.save.tips': + 'Make sure to copy your key immediately. You will not be able to see it again.' }; diff --git a/src/locales/en-US/common.ts b/src/locales/en-US/common.ts index f52759b7..59008519 100644 --- a/src/locales/en-US/common.ts +++ b/src/locales/en-US/common.ts @@ -179,5 +179,8 @@ export default { 'common.settings.language': 'Language', 'common.delete.confirm': 'Are you sure you want to delete the selected {type}?', - 'common.filter.name': 'Filter by name' + 'common.filter.name': 'Filter by name', + 'common.form.password': 'Password', + 'common.form.username': 'Username', + 'common.login.rember': 'Remember me' }; diff --git a/src/locales/en-US/users.ts b/src/locales/en-US/users.ts new file mode 100644 index 00000000..4f9959fe --- /dev/null +++ b/src/locales/en-US/users.ts @@ -0,0 +1,22 @@ +export default { + 'users.title': 'Users', + 'users.button.create': 'Create User', + 'users.form.edit': 'Edit User', + 'users.form.create': 'Create User', + 'users.table.username': 'User Name', + 'users.table.role': 'Role', + 'users.form.fullname': 'Full Name', + 'users.table.user': 'users', + 'users.form.admin': 'Admin', + 'users.form.user': 'User', + 'users.form.newpassword': 'New Password', + 'users.form.currentpassword': 'Current Password', + 'users.form.updatepassword': 'Modify Password', + 'users.form.rule.password': + 'Contains uppercase and lowercase letters, numbers, and special characters, 6 to 12 characters in length, no spaces allowed.', + 'users.password.uppcase': 'At least one uppercase letter', + 'users.password.lowercase': 'At least one lowercase letter', + 'users.password.number': 'At least one number', + 'users.password.special': 'At least one special character', + 'users.password.length': 'Length between 6 and 12 characters' +}; diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index ac4bee7b..f37c36b4 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -1,9 +1,10 @@ -import menu from './en-US/menu'; import apikeys from './zh-CN/apikeys'; import common from './zh-CN/common'; +import menu from './zh-CN/menu'; import models from './zh-CN/models'; import playground from './zh-CN/playground'; import resources from './zh-CN/resources'; +import users from './zh-CN/users'; export default { ...common, @@ -11,5 +12,6 @@ export default { ...models, ...playground, ...resources, - ...apikeys + ...apikeys, + ...users }; diff --git a/src/locales/zh-CN/apikeys.ts b/src/locales/zh-CN/apikeys.ts index 80e38867..2f8f0632 100644 --- a/src/locales/zh-CN/apikeys.ts +++ b/src/locales/zh-CN/apikeys.ts @@ -1,6 +1,8 @@ export default { 'apikeys.title': 'API 密钥', + 'apikeys.table.apikeys': '密钥', 'apikeys.button.create': '新建 API 密钥', 'apikeys.form.expiretime': '过期时间', - 'apikeys.table.name': '密钥名称' + 'apikeys.table.name': '密钥名称', + 'apikeys.table.save.tips': '确保立即复制您的密钥。您将无法再次看到它!' }; diff --git a/src/locales/zh-CN/common.ts b/src/locales/zh-CN/common.ts index 501abefa..71341771 100644 --- a/src/locales/zh-CN/common.ts +++ b/src/locales/zh-CN/common.ts @@ -174,5 +174,8 @@ export default { 'common.settings.instructions': '操作指引', 'common.settings.language': '语言', 'common.delete.confirm': '确定删除选中的{type}吗?', - 'common.filter.name': '名称查询' + 'common.filter.name': '名称查询', + 'common.form.password': '密码', + 'common.form.username': '用户名', + 'common.login.rember': '记住我' }; diff --git a/src/locales/zh-CN/users.ts b/src/locales/zh-CN/users.ts new file mode 100644 index 00000000..4ddc62a5 --- /dev/null +++ b/src/locales/zh-CN/users.ts @@ -0,0 +1,22 @@ +export default { + 'users.title': '用户', + 'users.button.create': '新建用户', + 'users.form.edit': '编辑用户', + 'users.form.create': '新建用户', + 'users.table.username': '用户名', + 'users.table.role': '角色', + 'users.form.fullname': '全名', + 'users.table.user': '用户', + 'users.form.admin': '管理员', + 'users.form.user': '普通用户', + 'users.form.newpassword': '新密码', + 'users.form.currentpassword': '当前密码', + 'users.form.updatepassword': '修改密码', + 'users.form.rule.password': + '包含大小写字母、数字和特殊字符,6至12个字符,不允许有空格', + 'users.password.uppcase': '至少包含一个大写字母', + 'users.password.lowercase': '至少包含一个小写字母', + 'users.password.number': '至少包含一个数字', + 'users.password.special': '至少包含一个特殊字符', + 'users.password.length': '长度在6至12个字符之间' +}; diff --git a/src/pages/api-keys/components/add-apikey.tsx b/src/pages/api-keys/components/add-apikey.tsx index 7e025149..77b6aa82 100644 --- a/src/pages/api-keys/components/add-apikey.tsx +++ b/src/pages/api-keys/components/add-apikey.tsx @@ -3,6 +3,7 @@ import SealInput from '@/components/seal-form/seal-input'; import SealSelect from '@/components/seal-form/seal-select'; import { PageActionType } from '@/config/types'; import { SyncOutlined } from '@ant-design/icons'; +import { useIntl } from '@umijs/max'; import { Form, Modal } from 'antd'; import { expirationOptions } from '../config'; import { FormData } from '../config/types'; @@ -23,6 +24,7 @@ const AddModal: React.FC = ({ onCancel }) => { const [form] = Form.useForm(); + const intl = useIntl(); const Suffix = ( = ({ } >
- name="name" rules={[{ required: true }]}> - + + name="name" + rules={[ + { + required: true, + message: intl.formatMessage( + { id: 'common.form.rule.input' }, + { + name: intl.formatMessage({ id: 'common.table.name' }) + } + ) + } + ]} + > + - name="expires_in" rules={[{ required: true }]}> + + name="expires_in" + rules={[ + { + required: true, + message: intl.formatMessage( + { id: 'common.form.rule.select' }, + { + name: intl.formatMessage({ id: 'apikeys.form.expiretime' }) + } + ) + } + ]} + > name="description" rules={[{ required: false }]}> - + diff --git a/src/pages/api-keys/index.tsx b/src/pages/api-keys/index.tsx index 8ce642e5..1dd20660 100644 --- a/src/pages/api-keys/index.tsx +++ b/src/pages/api-keys/index.tsx @@ -128,7 +128,7 @@ const Models: React.FC = () => { }; const res = await createApisKey({ data: params }); setOpenAddModal(false); - message.success('successfully!'); + message.success(intl.formatMessage({ id: 'common.message.success' })); setDataSource([res, ...dataSource]); setTotal(total + 1); } catch (error) { @@ -144,11 +144,14 @@ const Models: React.FC = () => { const handleDelete = (row: ListItem) => { Modal.confirm({ title: '', - content: 'Are you sure you want to delete the selected keys?', + content: intl.formatMessage( + { id: 'common.delete.confirm' }, + { type: intl.formatMessage({ id: 'apikeys.table.apikeys' }) } + ), async onOk() { console.log('OK'); await deleteApisKey(row.id); - message.success('successfully!'); + message.success(intl.formatMessage({ id: 'common.message.success' })); fetchData(); }, onCancel() { @@ -160,10 +163,13 @@ const Models: React.FC = () => { const handleDeleteBatch = () => { Modal.confirm({ title: '', - content: 'Are you sure you want to delete the selected keys?', + content: intl.formatMessage( + { id: 'common.delete.confirm' }, + { type: intl.formatMessage({ id: 'apikeys.table.apikeys' }) } + ), async onOk() { await handleBatchRequest(rowSelection.selectedRowKeys, deleteApisKey); - message.success('successfully!'); + message.success(intl.formatMessage({ id: 'common.message.success' })); fetchData(); }, onCancel() { @@ -172,12 +178,6 @@ const Models: React.FC = () => { }); }; - const handleEditUser = () => { - setOpenAddModal(true); - setAction(PageAction.EDIT); - setTitle('Edit User'); - }; - const renderSecrectKey = (text: string, record: ListItem) => { const { value } = record; @@ -187,7 +187,7 @@ const Models: React.FC = () => { {value && ( - 确保立即复制您的个人访问密钥。您将无法再次看到它! + {intl.formatMessage({ id: 'apikeys.table.save.tips' })} { }} /> { return ( diff --git a/src/pages/login/apis/index.ts b/src/pages/login/apis/index.ts index 15814cb4..d86da576 100644 --- a/src/pages/login/apis/index.ts +++ b/src/pages/login/apis/index.ts @@ -27,3 +27,10 @@ export const accessToken = async () => { method: 'POST' }); }; + +export const updatePassword = async (params: any) => { + return request(`${AUTH_API}/update-password`, { + method: 'POST', + data: params + }); +}; diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx index 65aa3827..9b150c8f 100644 --- a/src/pages/login/index.tsx +++ b/src/pages/login/index.tsx @@ -1,7 +1,7 @@ import LogoIcon from '@/assets/images/logo.png'; import SealInput from '@/components/seal-form/seal-input'; -import { LockOutlined, UserOutlined } from '@ant-design/icons'; -import { history, useModel } from '@umijs/max'; +import { GlobalOutlined, LockOutlined, UserOutlined } from '@ant-design/icons'; +import { SelectLang, history, useIntl, useModel } from '@umijs/max'; import { Button, Checkbox, Form } from 'antd'; import { flushSync } from 'react-dom'; import { login } from './apis'; @@ -24,7 +24,7 @@ const renderLogo = () => { }; const Login = () => { const { initialState, setInitialState } = useModel('@@initialState'); - + const intl = useIntl(); const [form] = Form.useForm(); const gotoDefaultPage = (userInfo: any) => { @@ -61,44 +61,63 @@ const Login = () => { }; return ( -
-
{renderLogo()}
- +
+ } reload={false} /> +
+ - } /> -
+
{renderLogo()}
+ + } + /> + - - } label="Password" /> - - -
- Remember me -
-
- -
+ + } + label={intl.formatMessage({ id: 'common.form.password' })} + /> + + +
+ + {intl.formatMessage({ id: 'common.login.rember' })} + +
+
+ + + ); }; diff --git a/src/pages/playground/apis/index.ts b/src/pages/playground/apis/index.ts index 5a0a313a..66d1b4be 100644 --- a/src/pages/playground/apis/index.ts +++ b/src/pages/playground/apis/index.ts @@ -41,8 +41,8 @@ export const receiveChatStream = async ( } let chunk = decoder.decode(value, { stream: true }); - if (chunk.startsWith('data: ')) { - chunk = chunk.substring('data: '.length); + if (chunk.startsWith('data:')) { + chunk = chunk.substring('data:'.length); } const item = JSON.parse(chunk?.trim()); callback(item); diff --git a/src/pages/playground/components/ground-left.tsx b/src/pages/playground/components/ground-left.tsx index 23190fc3..c1c49a33 100644 --- a/src/pages/playground/components/ground-left.tsx +++ b/src/pages/playground/components/ground-left.tsx @@ -43,9 +43,6 @@ const MessageList: React.FC = (props) => { const systemRef = useRef(null); const contentRef = useRef(''); - const handleReceiveMessage = (data: any) => { - console.log('event source message: ', { data }); - }; const handleSystemMessageChange = (e: any) => { setSystemMessage(e.target.value); }; diff --git a/src/pages/profile/index.tsx b/src/pages/profile/index.tsx index 8e50fc46..2b0b50a9 100644 --- a/src/pages/profile/index.tsx +++ b/src/pages/profile/index.tsx @@ -1,21 +1,29 @@ import FormButtons from '@/components/form-buttons'; import SealInput from '@/components/seal-form/seal-input'; +import { PasswordReg } from '@/config'; import { INPUT_WIDTH } from '@/constants'; +import { updatePassword } from '@/pages/login/apis'; import { PageContainer } from '@ant-design/pro-components'; -import { Form } from 'antd'; +import { useIntl } from '@umijs/max'; +import { Form, message } from 'antd'; import { StrictMode } from 'react'; interface ProfileProps { - name: string; - password: string; - originalPassword: string; - email: string; + username?: string; + new_password: string; + current_password: string; } const Profile: React.FC = () => { const [form] = Form.useForm(); + const intl = useIntl(); - const handleOnFinish = (values: any) => { - console.log('handleOnFinish', values); + const newpasswordValue = Form.useWatch('new_password', form); + + const handleOnFinish = async (values: any) => { + try { + await updatePassword(values); + message.success(intl.formatMessage({ id: 'common.message.success' })); + } catch (error) {} }; const handleOnFinishFailed = (errorInfo: any) => { @@ -30,7 +38,7 @@ const Profile: React.FC = () => { @@ -41,33 +49,60 @@ const Profile: React.FC = () => { onFinish={handleOnFinish} onFinishFailed={handleOnFinishFailed} > - name="name" rules={[{ required: true }]}> - - - name="email" rules={[{ required: true }]}> + {/* + name="username" + rules={[ + { + required: true, + message: intl.formatMessage( + { id: 'common.form.rule.input' }, + { + name: intl.formatMessage({ id: 'common.form.username' }) + } + ) + } + ]} + > - + */} - name="originalPassword" - rules={[{ required: true }]} + name="current_password" + rules={[ + { + required: true, + message: intl.formatMessage( + { id: 'common.form.rule.input' }, + { + name: intl.formatMessage({ + id: 'users.form.currentpassword' + }) + } + ) + } + ]} > - name="password" rules={[{ required: true }]}> + + name="new_password" + rules={[ + { + required: true, + pattern: PasswordReg, + message: intl.formatMessage({ id: 'users.form.rule.password' }) + } + ]} + > diff --git a/src/pages/users/components/add-modal.tsx b/src/pages/users/components/add-modal.tsx index 88f97282..3e31ef20 100644 --- a/src/pages/users/components/add-modal.tsx +++ b/src/pages/users/components/add-modal.tsx @@ -3,8 +3,13 @@ import SealInput from '@/components/seal-form/seal-input'; import SealSelect from '@/components/seal-form/seal-select'; import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; -import { SyncOutlined } from '@ant-design/icons'; -import { Form, Modal } from 'antd'; +import { + SyncOutlined, + UserOutlined, + UserSwitchOutlined +} from '@ant-design/icons'; +import { useIntl } from '@umijs/max'; +import { Form, Modal, Select } from 'antd'; import { useEffect } from 'react'; import { UserRoles, UserRolesOptions } from '../config'; import { FormData, ListItem } from '../config/types'; @@ -26,6 +31,7 @@ const AddModal: React.FC = ({ onCancel }) => { const [form] = Form.useForm(); + const intl = useIntl(); const suffix = ( = ({ } >
- name="username" rules={[{ required: true }]}> - + + name="username" + rules={[ + { + required: true, + message: intl.formatMessage( + { id: 'common.form.rule.input' }, + { + name: intl.formatMessage({ id: 'common.table.name' }) + } + ) + } + ]} + > + name="full_name" rules={[{ required: false }]}> - + name="is_admin" rules={[{ required: false }]}> - + + {UserRolesOptions.map((item) => { + return ( + + {item.value === UserRoles.ADMIN ? ( + + ) : ( + + )} + + {intl.formatMessage({ id: item.label })} + + + ); + })} + - name="password" rules={[{ required: true }]}> - + + name="password" + rules={[ + { + required: true, + message: intl.formatMessage( + { id: 'common.form.rule.input' }, + { + name: intl.formatMessage({ id: 'common.form.password' }) + } + ) + } + ]} + > + diff --git a/src/pages/users/config/index.ts b/src/pages/users/config/index.ts index 6155181b..9a40a5e1 100644 --- a/src/pages/users/config/index.ts +++ b/src/pages/users/config/index.ts @@ -4,6 +4,6 @@ export const UserRoles = { }; export const UserRolesOptions = [ - { label: '管理员', value: UserRoles.ADMIN }, - { label: '普通用户', value: UserRoles.USER } + { label: 'users.form.admin', value: UserRoles.ADMIN }, + { label: 'users.form.user', value: UserRoles.USER } ]; diff --git a/src/pages/users/index.tsx b/src/pages/users/index.tsx index 1b8ddb52..0855666f 100644 --- a/src/pages/users/index.tsx +++ b/src/pages/users/index.tsx @@ -13,6 +13,7 @@ import { UserSwitchOutlined } from '@ant-design/icons'; import { PageContainer } from '@ant-design/pro-components'; +import { useIntl } from '@umijs/max'; import { Button, Input, Modal, Space, Table, Tooltip, message } from 'antd'; import dayjs from 'dayjs'; import _ from 'lodash'; @@ -27,6 +28,7 @@ const Models: React.FC = () => { const { sortOrder, setSortOrder } = useTableSort({ defaultSortOrder: 'descend' }); + const intl = useIntl(); const [total, setTotal] = useState(0); const [openAddModal, setOpenAddModal] = useState(false); const [loading, setLoading] = useState(false); @@ -93,11 +95,7 @@ const Models: React.FC = () => { const handleAddUser = () => { setOpenAddModal(true); setAction(PageAction.CREATE); - setTitle('Add User'); - }; - - const handleClickMenu = (e: any) => { - console.log('click', e); + setTitle(intl.formatMessage({ id: 'users.form.create' })); }; const handleModalOk = async (data: FormData) => { @@ -119,7 +117,7 @@ const Models: React.FC = () => { } fetchData(); setOpenAddModal(false); - message.success('successfully!'); + message.success(''); } catch (error) { setOpenAddModal(false); } @@ -133,11 +131,14 @@ const Models: React.FC = () => { const handleDelete = (row: ListItem) => { Modal.confirm({ title: '', - content: 'Are you sure you want to delete the selected users?', + content: intl.formatMessage( + { id: 'common.delete.confirm' }, + { type: intl.formatMessage({ id: 'users.table.user' }) } + ), async onOk() { console.log('OK'); await deleteUser(row.id); - message.success('successfully!'); + message.success(intl.formatMessage({ id: 'common.message.success' })); fetchData(); }, onCancel() { @@ -149,10 +150,13 @@ const Models: React.FC = () => { const handleDeleteBatch = () => { Modal.confirm({ title: '', - content: 'Are you sure you want to delete the selected users?', + content: intl.formatMessage( + { id: 'common.delete.confirm' }, + { type: intl.formatMessage({ id: 'users.table.user' }) } + ), async onOk() { await handleBatchRequest(rowSelection.selectedRowKeys, deleteUser); - message.success('successfully!'); + message.success(intl.formatMessage({ id: 'common.message.success' })); fetchData(); }, onCancel() { @@ -165,7 +169,7 @@ const Models: React.FC = () => { setCurrentData(row); setOpenAddModal(true); setAction(PageAction.EDIT); - setTitle('Edit User'); + setTitle(intl.formatMessage({ id: 'users.form.edit' })); }; useEffect(() => { @@ -177,7 +181,7 @@ const Models: React.FC = () => { @@ -186,7 +190,7 @@ const Models: React.FC = () => { left={ @@ -205,7 +209,7 @@ const Models: React.FC = () => { type="primary" onClick={handleAddUser} > - Add User + {intl.formatMessage({ id: 'users.button.create' })} } @@ -234,9 +238,14 @@ const Models: React.FC = () => { onChange: handlePageChange }} > - + { }} /> { return record.is_admin ? ( <> - 管理员 + + {intl.formatMessage({ id: 'users.form.admin' })} + ) : ( <> - 普通用户 + + {intl.formatMessage({ id: 'users.form.user' })} + ); }} /> { }} /> { return ( - + - +