fix: 完善个人设置界面 (#1542)

Reviewed-on: daoyoucloud/tachybase#1542
Reviewed-by: sealday <zhanglin@daoyoucloud.com>
Co-authored-by: wjh <wwwjh0710@163.com>
Co-committed-by: wjh <wwwjh0710@163.com>
This commit is contained in:
wjh 2024-09-19 12:02:48 +08:00 committed by sealday
parent d6dfdfd54d
commit e911982408
9 changed files with 254 additions and 19 deletions

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { ApiOutlined, SettingOutlined } from '@ant-design/icons'; import { ApiOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons';
import { Button, Dropdown, Tooltip } from 'antd'; import { Button, Dropdown, Tooltip } from 'antd';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
@ -31,7 +31,30 @@ export const SettingsCenterDropdown = () => {
const compile = useCompile(); const compile = useCompile();
const { token } = useToken(); const { token } = useToken();
const app = useApp(); const app = useApp();
const settings = app.pluginSettingsManager.getList(); const systemSettings = app.pluginSettingsManager.getList();
const userSettings = app.userSettingsManager.getList();
const settingItem = [];
userSettings
.filter((v) => v.isTopLevel !== false)
.forEach((setting) => {
settingItem.push({
key: setting.name,
icon: setting.icon,
label: <Link to={setting.path}>{compile(setting.title)}</Link>,
});
});
settingItem.push({
type: 'divider',
});
systemSettings
.filter((v) => v.isTopLevel !== false)
.forEach((setting) => {
settingItem.push({
key: setting.name,
icon: setting.icon,
label: <Link to={setting.path}>{compile(setting.title)}</Link>,
});
});
return ( return (
<Dropdown <Dropdown
menu={{ menu={{
@ -39,15 +62,7 @@ export const SettingsCenterDropdown = () => {
maxHeight: '70vh', maxHeight: '70vh',
overflow: 'auto', overflow: 'auto',
}, },
items: settings items: settingItem,
.filter((v) => v.isTopLevel !== false)
.map((setting) => {
return {
key: setting.name,
icon: setting.icon,
label: <Link to={setting.path}>{compile(setting.title)}</Link>,
};
}),
}} }}
> >
<Button <Button

View File

@ -957,5 +957,6 @@
"Full Screen":"切换全屏", "Full Screen":"切换全屏",
"Designer Mode":"设计者模式", "Designer Mode":"设计者模式",
"There are no full screen blocks available at the current location":"当前位置没有可全屏区块", "There are no full screen blocks available at the current location":"当前位置没有可全屏区块",
"Exit Full Screen":"退出全屏" "Exit Full Screen":"退出全屏",
"User settings":"个人设置"
} }

View File

@ -149,9 +149,9 @@ export const SettingsMenu: React.FC<{
}, [ }, [
addMenuItem, addMenuItem,
api.auth, api.auth,
changePassword,
controlApp, controlApp,
editProfile, editProfile,
changePassword,
languageSettings, languageSettings,
navigate, navigate,
redirectUrl, redirectUrl,

View File

@ -124,7 +124,6 @@ const schema: ISchema = {
export const useEditProfile = () => { export const useEditProfile = () => {
const ctx = useContext(DropdownVisibleContext); const ctx = useContext(DropdownVisibleContext);
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const navigate = useNavigate();
const { t } = useTranslation(); const { t } = useTranslation();
return useMemo<MenuProps['items'][0]>(() => { return useMemo<MenuProps['items'][0]>(() => {
@ -132,9 +131,22 @@ export const useEditProfile = () => {
key: 'profile', key: 'profile',
eventKey: 'EditProfile', eventKey: 'EditProfile',
onClick: () => { onClick: () => {
navigate('/admin/profilers/user-profile'); setVisible(true);
ctx?.setVisible(false);
}, },
label: <div>{t('Edit profile')}</div>, label: (
<div>
{t('Edit profile')}
<ActionContextProvider value={{ visible, setVisible }}>
<div onClick={(e) => e.stopPropagation()}>
<SchemaComponent
scope={{ useCurrentUserValues, useCloseAction, useSaveCurrentUserValues }}
schema={schema}
/>
</div>
</ActionContextProvider>
</div>
),
}; };
}, [visible]); }, [visible]);
}; };

View File

@ -0,0 +1,100 @@
import React from 'react';
import { SchemaComponent, useAPIClient, useTranslation } from '@tachybase/client';
import { ISchema, useForm } from '@tachybase/schema';
import { uid } from '@tachybase/utils/client';
import { App } from 'antd';
export const ChangePassword = () => {
return <SchemaComponent schema={schema} scope={{ useSaveCurrentUserValues }} />;
};
const useSaveCurrentUserValues = () => {
const form = useForm();
const api = useAPIClient();
const { message } = App.useApp();
const { t } = useTranslation();
return {
async run() {
await form.submit();
const result = await api.resource('auth').changePassword({
values: form.values,
});
if (result.status === 200) {
message.success(t('Edited successfully'));
}
await form.reset();
},
};
};
const schema: ISchema = {
type: 'object',
properties: {
[uid()]: {
type: 'void',
'x-component': 'CardItem',
'x-decorator': 'Form',
title: '{{t("Change password")}}',
properties: {
action: {
type: 'void',
properties: {
submit: {
title: '{{t("Submit")}}',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
style: { left: '96%' },
useAction: '{{ useSaveCurrentUserValues }}',
},
},
},
},
oldPassword: {
type: 'string',
title: '{{t("Old password")}}',
required: true,
'x-component': 'Password',
'x-decorator': 'FormItem',
},
newPassword: {
type: 'string',
title: '{{t("New password")}}',
required: true,
'x-component': 'Password',
'x-decorator': 'FormItem',
'x-component-props': { checkStrength: true, style: {} },
'x-reactions': [
{
dependencies: ['.confirmPassword'],
fulfill: {
state: {
selfErrors: '{{$deps[0] && $self.value && $self.value !== $deps[0] ? t("Password mismatch") : ""}}',
},
},
},
],
},
confirmPassword: {
type: 'string',
required: true,
title: '{{t("Confirm password")}}',
'x-component': 'Password',
'x-decorator': 'FormItem',
'x-component-props': { checkStrength: true, style: {} },
'x-reactions': [
{
dependencies: ['.newPassword'],
fulfill: {
state: {
selfErrors: '{{$deps[0] && $self.value && $self.value !== $deps[0] ? t("Password mismatch") : ""}}',
},
},
},
],
},
},
},
},
};

View File

@ -1,5 +1,101 @@
import React from 'react'; import React from 'react';
import { SchemaComponent, useAPIClient, useCurrentUserContext, useRequest, useTranslation } from '@tachybase/client';
import { ISchema, useForm } from '@tachybase/schema';
import { uid } from '@tachybase/utils/client';
import { App } from 'antd';
export const UserProfile = () => { export const UserProfile = () => {
return <div>user profile</div>; return <SchemaComponent schema={schema} scope={{ useCurrentUserValues, useSaveCurrentUserValues }} />;
};
const useCurrentUserValues = (options) => {
const ctx = useCurrentUserContext();
return useRequest(() => Promise.resolve(ctx.data), options);
};
const useSaveCurrentUserValues = () => {
const ctx = useCurrentUserContext();
const form = useForm();
const api = useAPIClient();
const { message } = App.useApp();
const { t } = useTranslation();
return {
async run() {
const values = await form.submit<any>();
const result = await api.resource('users').updateProfile({
values,
});
if (result.status === 200) {
message.success(t('Edited successfully'));
}
ctx.mutate({
data: {
...ctx?.data?.data,
...values,
},
});
},
};
};
const schema: ISchema = {
type: 'object',
properties: {
[uid()]: {
type: 'void',
'x-component': 'CardItem',
'x-decorator': 'Form',
'x-decorator-props': {
useValues: '{{useCurrentUserValues}}',
},
title: '{{t("Edit profile")}}',
properties: {
action: {
type: 'void',
properties: {
submit: {
title: 'Submit',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
style: {
left: '96%',
},
useAction: '{{ useSaveCurrentUserValues }}',
},
},
},
},
nickname: {
type: 'string',
title: "{{t('Nickname')}}",
'x-decorator': 'FormItem',
'x-component': 'Input',
},
username: {
type: 'string',
title: '{{t("Username")}}',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-validator': { username: true },
required: true,
},
email: {
type: 'string',
title: '{{t("Email")}}',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-validator': 'email',
},
phone: {
type: 'string',
title: '{{t("Phone")}}',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-validator': 'phone',
},
},
},
},
}; };

View File

@ -2,6 +2,7 @@ import { Plugin, tval } from '@tachybase/client';
import ACLPlugin from '@tachybase/plugin-acl/client'; import ACLPlugin from '@tachybase/plugin-acl/client';
import { RoleUsersManager } from './RoleUsersManager'; import { RoleUsersManager } from './RoleUsersManager';
import { ChangePassword } from './UserChangePassword';
import { UserProfile } from './UserProfile'; import { UserProfile } from './UserProfile';
import { UsersManagement } from './UsersManagement'; import { UsersManagement } from './UsersManagement';
@ -18,6 +19,12 @@ class PluginUsersClient extends Plugin {
aclSnippet: 'pm.users', aclSnippet: 'pm.users',
}); });
this.userSettingsManager.add('user-change-password', {
icon: 'LockOutlined',
title: tval('Change password'),
Component: ChangePassword,
});
this.userSettingsManager.add('user-profile', { this.userSettingsManager.add('user-profile', {
icon: 'UserOutlined', icon: 'UserOutlined',
title: tval('Edit profile'), title: tval('Edit profile'),

View File

@ -3,5 +3,7 @@
"Add users": "Add users", "Add users": "Add users",
"Remove user": "Remove user", "Remove user": "Remove user",
"Are you sure you want to remove it?": "Are you sure you want to remove it?", "Are you sure you want to remove it?": "Are you sure you want to remove it?",
"Random password": "Random password" "Random password": "Random password",
"Edited successfully":"Edited successfully",
"Users Settings":"Users Settings"
} }

View File

@ -3,5 +3,7 @@
"Add users": "添加用户", "Add users": "添加用户",
"Remove user": "移除用户", "Remove user": "移除用户",
"Are you sure you want to remove it?": "你确定要移除吗?", "Are you sure you want to remove it?": "你确定要移除吗?",
"Random password": "随机密码" "Random password": "随机密码",
"Edited successfully":"编辑成功",
"Users Settings":"用户设置"
} }