mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-09 15:39:24 +08:00
feat(users): allows to custom profile editing form (#5698)
* feat(users): allows to custom profile editing form * chore: update * fix: build * fix: build * fix: build * fix: migration * chore: update * feat: parse schema * feat: trigger workflows * fix: test * fix: test * fix: issues * fix: user menu * fix: user menu * fix: e2e * fix: z-index * fix: bug * fix: designable * fix: required validation * fix: test * fix: forms * fix: version * fix: designable
This commit is contained in:
parent
b015e69cfb
commit
a15a2a9540
@ -84,7 +84,7 @@ test.describe('z-index of dialog', () => {
|
||||
await expect(page.getByTestId('drawer-Action.Drawer-Edit profile')).toBeVisible();
|
||||
|
||||
// click the Cancel button to close the drawer
|
||||
await page.getByLabel('action-Action-Cancel').click();
|
||||
await page.getByLabel('drawer-Action.Drawer-Edit profile-mask').click();
|
||||
await expect(page.getByTestId('drawer-Action.Drawer-Edit profile')).not.toBeVisible();
|
||||
});
|
||||
|
||||
|
@ -175,4 +175,27 @@ ActionDrawer.Footer = observer(
|
||||
{ displayName: 'ActionDrawer.Footer' },
|
||||
);
|
||||
|
||||
ActionDrawer.FootBar = observer(
|
||||
() => {
|
||||
const field = useField();
|
||||
const schema = useFieldSchema();
|
||||
return (
|
||||
<div
|
||||
className="ant-drawer-footer"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
}}
|
||||
>
|
||||
<div className="footer">
|
||||
<MemoizeRecursionField basePath={field.address} schema={schema} onlyRenderProperties />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
{ displayName: 'ActionDrawer.FootBar' },
|
||||
);
|
||||
|
||||
export default ActionDrawer;
|
||||
|
@ -96,4 +96,5 @@ export type ActionDrawerProps<T = DrawerProps> = T & {
|
||||
|
||||
export type ComposedActionDrawer<T = DrawerProps> = React.FC<ActionDrawerProps<T>> & {
|
||||
Footer?: React.FC;
|
||||
FootBar?: React.FC;
|
||||
};
|
||||
|
@ -125,10 +125,11 @@ export const SettingsMenu: React.FC<{
|
||||
},
|
||||
editProfile,
|
||||
changePassword,
|
||||
(editProfile || changePassword) && {
|
||||
key: 'divider_2',
|
||||
type: 'divider',
|
||||
},
|
||||
editProfile ||
|
||||
(changePassword && {
|
||||
key: 'divider_2',
|
||||
type: 'divider',
|
||||
}),
|
||||
switchRole,
|
||||
{
|
||||
key: 'divider_3',
|
||||
@ -160,9 +161,9 @@ export const SettingsMenu: React.FC<{
|
||||
}, [
|
||||
addMenuItem,
|
||||
api.auth,
|
||||
editProfile,
|
||||
changePassword,
|
||||
controlApp,
|
||||
editProfile,
|
||||
languageSettings,
|
||||
navigate,
|
||||
redirectUrl,
|
||||
|
@ -7,133 +7,113 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { ISchema, useForm } from '@formily/react';
|
||||
import { useField, useFieldSchema, useForm } from '@formily/react';
|
||||
import { uid } from '@formily/shared';
|
||||
import { MenuProps } from 'antd';
|
||||
import React, { useContext, useMemo, useState } from 'react';
|
||||
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
ActionContextProvider,
|
||||
DropdownVisibleContext,
|
||||
ExtendCollectionsProvider,
|
||||
RemoteSchemaComponent,
|
||||
SchemaComponent,
|
||||
useActionContext,
|
||||
useCollectValuesToSubmit,
|
||||
useCollectionManager,
|
||||
useCurrentUserContext,
|
||||
useRequest,
|
||||
useSystemSettings,
|
||||
} from '../';
|
||||
import { useAPIClient } from '../api-client';
|
||||
|
||||
const useCloseAction = () => {
|
||||
const { setVisible } = useActionContext();
|
||||
const form = useForm();
|
||||
return {
|
||||
async run() {
|
||||
setVisible(false);
|
||||
form.submit((values) => {
|
||||
console.log(values);
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const useCurrentUserValues = (options) => {
|
||||
const ctx = useCurrentUserContext();
|
||||
return useRequest(() => Promise.resolve(ctx.data), options);
|
||||
};
|
||||
|
||||
const useSaveCurrentUserValues = () => {
|
||||
const useUpdateProfileActionProps = () => {
|
||||
const ctx = useCurrentUserContext();
|
||||
const { setVisible } = useActionContext();
|
||||
const form = useForm();
|
||||
const api = useAPIClient();
|
||||
const actionSchema = useFieldSchema();
|
||||
const actionField = useField();
|
||||
const collectValues = useCollectValuesToSubmit();
|
||||
|
||||
return {
|
||||
async run() {
|
||||
const values = await form.submit<any>();
|
||||
setVisible(false);
|
||||
await api.resource('users').updateProfile({
|
||||
values,
|
||||
});
|
||||
ctx.mutate({
|
||||
data: {
|
||||
...ctx?.data?.data,
|
||||
...values,
|
||||
},
|
||||
});
|
||||
type: 'primary',
|
||||
htmlType: 'submit',
|
||||
async onClick() {
|
||||
const { triggerWorkflows, skipValidator } = actionSchema?.['x-action-settings'] ?? {};
|
||||
if (!skipValidator) {
|
||||
await form.submit();
|
||||
}
|
||||
const values = await collectValues();
|
||||
actionField.data = actionField.data || {};
|
||||
actionField.data.loading = true;
|
||||
try {
|
||||
await api.resource('users').updateProfile({
|
||||
values,
|
||||
triggerWorkflows: triggerWorkflows?.length
|
||||
? triggerWorkflows.map((row) => [row.workflowKey, row.context].filter(Boolean).join('!')).join(',')
|
||||
: undefined,
|
||||
});
|
||||
ctx.mutate({
|
||||
data: {
|
||||
...ctx?.data?.data,
|
||||
...values,
|
||||
},
|
||||
});
|
||||
await form.reset();
|
||||
actionField.data.loading = false;
|
||||
setVisible(false);
|
||||
} catch (error) {
|
||||
actionField.data.loading = false;
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const schema: ISchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[uid()]: {
|
||||
'x-decorator': 'Form',
|
||||
'x-decorator-props': {
|
||||
useValues: '{{ useCurrentUserValues }}',
|
||||
},
|
||||
'x-component': 'Action.Drawer',
|
||||
'x-component-props': {
|
||||
zIndex: 10000,
|
||||
},
|
||||
type: 'void',
|
||||
title: '{{t("Edit profile")}}',
|
||||
properties: {
|
||||
nickname: {
|
||||
type: 'string',
|
||||
title: "{{t('Nickname')}}",
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-disabled': '{{ enableEditProfile === false }}',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
title: '{{t("Username")}}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-validator': { username: true },
|
||||
required: true,
|
||||
'x-disabled': '{{ enableEditProfile === false }}',
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
title: '{{t("Email")}}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-validator': 'email',
|
||||
'x-disabled': '{{ enableEditProfile === false }}',
|
||||
},
|
||||
phone: {
|
||||
type: 'string',
|
||||
title: '{{t("Phone")}}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-disabled': '{{ enableEditProfile === false }}',
|
||||
},
|
||||
footer: {
|
||||
'x-component': 'Action.Drawer.Footer',
|
||||
type: 'void',
|
||||
properties: {
|
||||
cancel: {
|
||||
title: 'Cancel',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
useAction: '{{ useCloseAction }}',
|
||||
},
|
||||
},
|
||||
submit: {
|
||||
title: 'Submit',
|
||||
'x-component': 'Action',
|
||||
'x-disabled': '{{ enableEditProfile === false }}',
|
||||
'x-component-props': {
|
||||
type: 'primary',
|
||||
useAction: '{{ useSaveCurrentUserValues }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
const useEditProfileFormBlockDecoratorProps = () => {
|
||||
const { data } = useCurrentUserContext();
|
||||
return {
|
||||
filterByTk: data.data?.id,
|
||||
};
|
||||
};
|
||||
|
||||
const useCancelActionProps = () => {
|
||||
const { setVisible } = useActionContext();
|
||||
return {
|
||||
type: 'default',
|
||||
onClick() {
|
||||
setVisible(false);
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const ProfileEditForm = () => {
|
||||
const ctx = useContext(DropdownVisibleContext);
|
||||
const cm = useCollectionManager();
|
||||
const userCollection = cm.getCollection('users');
|
||||
const collection = useMemo(
|
||||
() => ({
|
||||
...userCollection,
|
||||
name: 'users',
|
||||
fields: userCollection.fields.filter((field) => !['password', 'roles'].includes(field.name)),
|
||||
}),
|
||||
[userCollection],
|
||||
);
|
||||
useEffect(() => {
|
||||
ctx?.setVisible(false);
|
||||
}, [ctx]);
|
||||
return (
|
||||
<ExtendCollectionsProvider collections={[collection]}>
|
||||
<RemoteSchemaComponent
|
||||
uid="nocobase-user-profile-edit-form"
|
||||
noForm={true}
|
||||
scope={{
|
||||
useUpdateProfileActionProps,
|
||||
useEditFormBlockDecoratorProps: useEditProfileFormBlockDecoratorProps,
|
||||
useCancelActionProps,
|
||||
}}
|
||||
/>
|
||||
</ExtendCollectionsProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useEditProfile = () => {
|
||||
@ -147,8 +127,8 @@ export const useEditProfile = () => {
|
||||
key: 'profile',
|
||||
eventKey: 'EditProfile',
|
||||
onClick: () => {
|
||||
setVisible(true);
|
||||
ctx?.setVisible(false);
|
||||
setVisible(true);
|
||||
},
|
||||
label: (
|
||||
<div>
|
||||
@ -156,8 +136,26 @@ export const useEditProfile = () => {
|
||||
<ActionContextProvider value={{ visible, setVisible }}>
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<SchemaComponent
|
||||
scope={{ useCurrentUserValues, useCloseAction, useSaveCurrentUserValues, enableEditProfile }}
|
||||
schema={schema}
|
||||
components={{ ProfileEditForm }}
|
||||
schema={{
|
||||
type: 'object',
|
||||
properties: {
|
||||
[uid()]: {
|
||||
'x-component': 'Action.Drawer',
|
||||
'x-component-props': {
|
||||
zIndex: 2000,
|
||||
},
|
||||
type: 'void',
|
||||
title: '{{t("Edit profile")}}',
|
||||
properties: {
|
||||
form: {
|
||||
type: 'void',
|
||||
'x-component': 'ProfileEditForm',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</ActionContextProvider>
|
||||
|
@ -18,7 +18,6 @@
|
||||
"@nocobase/client": "1.x",
|
||||
"@nocobase/database": "1.x",
|
||||
"@nocobase/plugin-error-handler": "1.x",
|
||||
"@nocobase/plugin-users": "1.x",
|
||||
"@nocobase/resourcer": "1.x",
|
||||
"@nocobase/server": "1.x",
|
||||
"@nocobase/test": "1.x",
|
||||
|
@ -20,6 +20,7 @@
|
||||
"@nocobase/plugin-acl": "1.x",
|
||||
"@nocobase/plugin-auth": "1.x",
|
||||
"@nocobase/plugin-user-data-sync": "1.x",
|
||||
"@nocobase/plugin-ui-schema-storage": "1.x",
|
||||
"@nocobase/resourcer": "1.x",
|
||||
"@nocobase/server": "1.x",
|
||||
"@nocobase/test": "1.x",
|
||||
|
@ -20,6 +20,9 @@ import {
|
||||
useDataBlockRequest,
|
||||
useDataBlockResource,
|
||||
useRequest,
|
||||
RemoteSchemaComponent,
|
||||
useCollectionManager,
|
||||
ExtendCollectionsProvider,
|
||||
useSchemaComponentContext,
|
||||
} from '@nocobase/client';
|
||||
import { App, Tabs, message } from 'antd';
|
||||
@ -50,6 +53,7 @@ const useSubmitActionProps = () => {
|
||||
const collection = useCollection();
|
||||
|
||||
return {
|
||||
htmlType: 'submit',
|
||||
type: 'primary',
|
||||
async onClick() {
|
||||
await form.submit();
|
||||
@ -84,19 +88,60 @@ const useEditFormProps = () => {
|
||||
};
|
||||
};
|
||||
|
||||
const UsersManagementTab: React.FC = () => {
|
||||
const { t } = useUsersTranslation();
|
||||
const ProfileCreateForm = () => {
|
||||
return <RemoteSchemaComponent uid="nocobase-admin-profile-create-form" noForm={true} />;
|
||||
};
|
||||
|
||||
const ProfileEditForm = () => {
|
||||
const cm = useCollectionManager();
|
||||
const userCollection = cm.getCollection('users');
|
||||
const collection = {
|
||||
...userCollection,
|
||||
name: 'users',
|
||||
fields: userCollection.fields.filter((field) => field.name !== 'password'),
|
||||
};
|
||||
return (
|
||||
<ExtendCollectionsProvider collections={[collection]}>
|
||||
<RemoteSchemaComponent uid="nocobase-admin-profile-edit-form" noForm={true} scope={{ useCancelActionProps }} />
|
||||
</ExtendCollectionsProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const FilterAction = () => {
|
||||
const scCtx = useSchemaComponentContext();
|
||||
return (
|
||||
<SchemaComponentContext.Provider value={{ ...scCtx, designable: false }}>
|
||||
<SchemaComponent
|
||||
schema={usersSchema}
|
||||
scope={{ t, useCancelActionProps, useSubmitActionProps, useEditFormProps }}
|
||||
components={{ PasswordField }}
|
||||
schema={{
|
||||
type: 'void',
|
||||
properties: {
|
||||
filter: {
|
||||
type: 'void',
|
||||
title: '{{ t("Filter") }}',
|
||||
'x-action': 'filter',
|
||||
'x-component': 'Filter.Action',
|
||||
'x-use-component-props': 'useFilterActionProps',
|
||||
'x-component-props': {
|
||||
icon: 'FilterOutlined',
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</SchemaComponentContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const UsersManagementTab: React.FC = () => {
|
||||
const { t } = useUsersTranslation();
|
||||
return (
|
||||
<SchemaComponent
|
||||
schema={usersSchema}
|
||||
scope={{ t, useCancelActionProps, useSubmitActionProps, useEditFormProps }}
|
||||
components={{ PasswordField, ProfileEditForm, ProfileCreateForm, FilterAction }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const UsersSettingsContext = createContext<any>({});
|
||||
|
||||
const UsersSettingsProvider = (props) => {
|
||||
@ -108,7 +153,6 @@ const UsersSettingsProvider = (props) => {
|
||||
|
||||
const UsersSettingsTab: React.FC = () => {
|
||||
const { t } = useUsersTranslation();
|
||||
const scCtx = useSchemaComponentContext();
|
||||
const form = useForm();
|
||||
const useFormBlockProps = () => {
|
||||
const result = useContext(UsersSettingsContext);
|
||||
@ -139,13 +183,11 @@ const UsersSettingsTab: React.FC = () => {
|
||||
};
|
||||
};
|
||||
return (
|
||||
<SchemaComponentContext.Provider value={{ ...scCtx, designable: false }}>
|
||||
<SchemaComponent
|
||||
schema={usersSettingsSchema}
|
||||
scope={{ t, useFormBlockProps, useSubmitActionProps }}
|
||||
components={{ UsersSettingsProvider }}
|
||||
/>
|
||||
</SchemaComponentContext.Provider>
|
||||
<SchemaComponent
|
||||
schema={usersSettingsSchema}
|
||||
scope={{ t, useFormBlockProps, useSubmitActionProps }}
|
||||
components={{ UsersSettingsProvider }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,198 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import {
|
||||
ActionContextProvider,
|
||||
DropdownVisibleContext,
|
||||
ExtendCollectionsProvider,
|
||||
RemoteSchemaComponent,
|
||||
SchemaComponent,
|
||||
useAPIClient,
|
||||
useActionContext,
|
||||
useCollectValuesToSubmit,
|
||||
useCollectionManager,
|
||||
useCurrentUserContext,
|
||||
useCurrentUserSettingsMenu,
|
||||
useSystemSettings,
|
||||
} from '@nocobase/client';
|
||||
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { MenuProps } from 'antd';
|
||||
import { useUsersTranslation } from './locale';
|
||||
import { uid } from '@formily/shared';
|
||||
import { useForm, useFieldSchema, useField } from '@formily/react';
|
||||
|
||||
const useUpdateProfileActionProps = () => {
|
||||
const ctx = useCurrentUserContext();
|
||||
const { setVisible } = useActionContext();
|
||||
const form = useForm();
|
||||
const api = useAPIClient();
|
||||
const actionSchema = useFieldSchema();
|
||||
const actionField = useField();
|
||||
const collectValues = useCollectValuesToSubmit();
|
||||
|
||||
return {
|
||||
type: 'primary',
|
||||
htmlType: 'submit',
|
||||
async onClick() {
|
||||
const { triggerWorkflows, skipValidator } = actionSchema?.['x-action-settings'] ?? {};
|
||||
if (!skipValidator) {
|
||||
await form.submit();
|
||||
}
|
||||
const values = await collectValues();
|
||||
actionField.data = actionField.data || {};
|
||||
actionField.data.loading = true;
|
||||
try {
|
||||
await api.resource('users').updateProfile({
|
||||
values,
|
||||
triggerWorkflows: triggerWorkflows?.length
|
||||
? triggerWorkflows.map((row) => [row.workflowKey, row.context].filter(Boolean).join('!')).join(',')
|
||||
: undefined,
|
||||
});
|
||||
ctx.mutate({
|
||||
data: {
|
||||
...ctx?.data?.data,
|
||||
...values,
|
||||
},
|
||||
});
|
||||
await form.reset();
|
||||
actionField.data.loading = false;
|
||||
setVisible(false);
|
||||
} catch (error) {
|
||||
actionField.data.loading = false;
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const ProfileEditForm = () => {
|
||||
const cm = useCollectionManager();
|
||||
const userCollection = cm.getCollection('users');
|
||||
const { data } = useCurrentUserContext();
|
||||
const collection = useMemo(
|
||||
() => ({
|
||||
...userCollection,
|
||||
name: 'users',
|
||||
fields: userCollection.fields.filter((field) => !['password', 'roles'].includes(field.name)),
|
||||
}),
|
||||
[userCollection],
|
||||
);
|
||||
return (
|
||||
<ExtendCollectionsProvider collections={[collection]}>
|
||||
<RemoteSchemaComponent
|
||||
uid="nocobase-user-profile-edit-form"
|
||||
noForm={true}
|
||||
scope={{
|
||||
useUpdateProfileActionProps,
|
||||
currentUserId: data.data?.id,
|
||||
}}
|
||||
/>
|
||||
</ExtendCollectionsProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const EditProfile = ({ visible, setVisible }) => {
|
||||
return (
|
||||
<ActionContextProvider value={{ visible, setVisible }}>
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<SchemaComponent
|
||||
components={{ ProfileEditForm }}
|
||||
schema={{
|
||||
type: 'object',
|
||||
properties: {
|
||||
[uid()]: {
|
||||
'x-component': 'Action.Drawer',
|
||||
'x-component-props': {
|
||||
// zIndex: 10000,
|
||||
},
|
||||
type: 'void',
|
||||
title: '{{t("Edit profile")}}',
|
||||
properties: {
|
||||
form: {
|
||||
type: 'void',
|
||||
'x-component': 'ProfileEditForm',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</ActionContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useEditProfile = () => {
|
||||
const ctx = useContext(DropdownVisibleContext);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const { t } = useUsersTranslation();
|
||||
const { data } = useSystemSettings() || {};
|
||||
const { enableEditProfile } = data?.data || {};
|
||||
const result = useMemo<MenuProps['items'][0]>(() => {
|
||||
return {
|
||||
key: 'profile',
|
||||
eventKey: 'EditProfile',
|
||||
onClick: () => {
|
||||
setVisible(true);
|
||||
ctx?.setVisible(false);
|
||||
},
|
||||
label: (
|
||||
<div>
|
||||
{t('Edit profile')}
|
||||
<ActionContextProvider value={{ visible, setVisible }}>
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<SchemaComponent
|
||||
components={{ ProfileEditForm }}
|
||||
schema={{
|
||||
type: 'object',
|
||||
properties: {
|
||||
[uid()]: {
|
||||
'x-component': 'Action.Drawer',
|
||||
'x-component-props': {
|
||||
// zIndex: 10000,
|
||||
},
|
||||
type: 'void',
|
||||
title: '{{t("Edit profile")}}',
|
||||
properties: {
|
||||
form: {
|
||||
type: 'void',
|
||||
'x-component': 'ProfileEditForm',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</ActionContextProvider>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
}, [visible]);
|
||||
if (enableEditProfile === false) {
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// Adding a user settings menu here causes the drawer to fail to open.
|
||||
// This provider will not be used for now.
|
||||
export const UsersProvider: React.FC = (props) => {
|
||||
const { addMenuItem } = useCurrentUserSettingsMenu();
|
||||
const profileItem = useEditProfile();
|
||||
const { data } = useSystemSettings();
|
||||
const { enableEditProfile } = data?.data || {};
|
||||
|
||||
useEffect(() => {
|
||||
if (enableEditProfile === false) {
|
||||
return;
|
||||
}
|
||||
addMenuItem(profileItem, { after: 'divider_1' });
|
||||
}, [addMenuItem, profileItem, enableEditProfile]);
|
||||
return <>{props.children}</>;
|
||||
};
|
@ -9,10 +9,9 @@
|
||||
|
||||
import { Plugin } from '@nocobase/client';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
// import { UsersManagement } from './UsersManagement';
|
||||
import ACLPlugin from '@nocobase/plugin-acl/client';
|
||||
// import { RoleUsersManager } from './RoleUsersManager';
|
||||
import { lazy } from '@nocobase/client';
|
||||
const { UsersProvider } = lazy(() => import('./UsersProvider'), 'UsersProvider');
|
||||
const { UsersManagement } = lazy(() => import('./UsersManagement'), 'UsersManagement');
|
||||
const { RoleUsersManager } = lazy(() => import('./RoleUsersManager'), 'RoleUsersManager');
|
||||
|
||||
|
@ -157,13 +157,7 @@ export const usersSchema: ISchema = {
|
||||
properties: {
|
||||
filter: {
|
||||
type: 'void',
|
||||
title: '{{ t("Filter") }}',
|
||||
'x-action': 'filter',
|
||||
'x-component': 'Filter.Action',
|
||||
'x-use-component-props': 'useFilterActionProps',
|
||||
'x-component-props': {
|
||||
icon: 'FilterOutlined',
|
||||
},
|
||||
'x-component': 'FilterAction',
|
||||
'x-align': 'left',
|
||||
},
|
||||
refresh: {
|
||||
@ -204,55 +198,9 @@ export const usersSchema: ISchema = {
|
||||
'x-decorator': 'FormV2',
|
||||
title: '{{t("Add user")}}',
|
||||
properties: {
|
||||
nickname: {
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
username: {
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
email: {
|
||||
title: '{{t("Email")}}',
|
||||
'x-component': 'Input',
|
||||
'x-validator': 'email',
|
||||
'x-decorator': 'FormItem',
|
||||
required: false,
|
||||
},
|
||||
phone: {
|
||||
title: '{{t("Phone")}}',
|
||||
'x-component': 'Input',
|
||||
'x-decorator': 'FormItem',
|
||||
required: false,
|
||||
},
|
||||
password: {
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
required: true,
|
||||
},
|
||||
roles: {
|
||||
'x-component': 'CollectionField',
|
||||
'x-collection-field': 'users.roles',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
footer: {
|
||||
form: {
|
||||
type: 'void',
|
||||
'x-component': 'Action.Drawer.Footer',
|
||||
properties: {
|
||||
cancel: {
|
||||
title: '{{t("Cancel")}}',
|
||||
'x-component': 'Action',
|
||||
'x-use-component-props': 'useCancelActionProps',
|
||||
},
|
||||
submit: {
|
||||
title: '{{t("Submit")}}',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
type: 'primary',
|
||||
},
|
||||
'x-use-component-props': 'useSubmitActionProps',
|
||||
},
|
||||
},
|
||||
'x-component': 'ProfileCreateForm',
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -352,54 +300,11 @@ export const usersSchema: ISchema = {
|
||||
drawer: {
|
||||
type: 'void',
|
||||
'x-component': 'Action.Drawer',
|
||||
'x-decorator': 'FormV2',
|
||||
'x-use-decorator-props': 'useEditFormProps',
|
||||
title: '{{t("Edit profile")}}',
|
||||
properties: {
|
||||
nickname: {
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
username: {
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
email: {
|
||||
title: '{{t("Email")}}',
|
||||
'x-component': 'Input',
|
||||
'x-validator': 'email',
|
||||
'x-decorator': 'FormItem',
|
||||
required: false,
|
||||
},
|
||||
phone: {
|
||||
title: '{{t("Phone")}}',
|
||||
'x-component': 'Input',
|
||||
'x-decorator': 'FormItem',
|
||||
required: false,
|
||||
},
|
||||
roles: {
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-collection-field': 'users.roles',
|
||||
},
|
||||
footer: {
|
||||
form: {
|
||||
type: 'void',
|
||||
'x-component': 'Action.Drawer.Footer',
|
||||
properties: {
|
||||
cancel: {
|
||||
title: '{{t("Cancel")}}',
|
||||
'x-component': 'Action',
|
||||
'x-use-component-props': 'useCancelActionProps',
|
||||
},
|
||||
submit: {
|
||||
title: '{{t("Submit")}}',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
type: 'primary',
|
||||
},
|
||||
'x-use-component-props': 'useSubmitActionProps',
|
||||
},
|
||||
},
|
||||
'x-component': 'ProfileEditForm',
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -507,19 +412,8 @@ export const usersSettingsSchema: ISchema = {
|
||||
default: true,
|
||||
'x-content': '{{t("Allow change password")}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
footer: {
|
||||
type: 'void',
|
||||
'x-component': 'div',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
float: 'right',
|
||||
},
|
||||
},
|
||||
properties: {
|
||||
submit: {
|
||||
title: 'Submit',
|
||||
title: '{{t("Save")}}',
|
||||
'x-component': 'Action',
|
||||
'x-use-component-props': 'useSubmitActionProps',
|
||||
},
|
||||
|
@ -23,7 +23,7 @@ describe('actions', () => {
|
||||
process.env.INIT_ROOT_PASSWORD = '123456';
|
||||
process.env.INIT_ROOT_NICKNAME = 'Test';
|
||||
app = await createMockServer({
|
||||
plugins: ['field-sort', 'auth', 'users', 'acl', 'data-source-manager', 'system-settings'],
|
||||
plugins: ['field-sort', 'auth', 'users', 'acl', 'data-source-manager', 'system-settings', 'ui-schema-storage'],
|
||||
});
|
||||
db = app.db;
|
||||
|
||||
@ -49,6 +49,7 @@ describe('actions', () => {
|
||||
filterByTk: adminUser.id,
|
||||
values: {
|
||||
nickname: 'a',
|
||||
username: 'A',
|
||||
},
|
||||
});
|
||||
expect(res1.status).toBe(401);
|
||||
@ -57,6 +58,7 @@ describe('actions', () => {
|
||||
filterByTk: adminUser.id,
|
||||
values: {
|
||||
nickname: 'a',
|
||||
username: 'A',
|
||||
},
|
||||
});
|
||||
expect(res2.status).toBe(200);
|
||||
|
@ -8,8 +8,28 @@
|
||||
*/
|
||||
|
||||
import { Context, DEFAULT_PAGE, DEFAULT_PER_PAGE, Next } from '@nocobase/actions';
|
||||
import { UiSchemaRepository } from '@nocobase/plugin-ui-schema-storage';
|
||||
import _ from 'lodash';
|
||||
import { namespace } from '..';
|
||||
import { ValidationError, ValidationErrorItem } from 'sequelize';
|
||||
|
||||
function parseProfileFormSchema(schema: any) {
|
||||
const properties = _.get(schema, 'properties.form.properties.edit.properties.grid.properties') || {};
|
||||
const fields = [];
|
||||
const requiredFields = [];
|
||||
Object.values(properties).forEach((row: any) => {
|
||||
const col = Object.values(row.properties)[0] as any;
|
||||
const [name, props] = Object.entries(col.properties)[0];
|
||||
if (props['x-read-pretty'] || props['x-disable']) {
|
||||
return;
|
||||
}
|
||||
if (props['required']) {
|
||||
requiredFields.push(name);
|
||||
}
|
||||
fields.push(name);
|
||||
});
|
||||
return { fields, requiredFields };
|
||||
}
|
||||
|
||||
export async function updateProfile(ctx: Context, next: Next) {
|
||||
const systemSettings = ctx.db.getRepository('systemSettings');
|
||||
@ -24,10 +44,34 @@ export async function updateProfile(ctx: Context, next: Next) {
|
||||
if (!currentUser) {
|
||||
ctx.throw(401);
|
||||
}
|
||||
const UserRepo = ctx.db.getRepository('users');
|
||||
const result = await UserRepo.update({
|
||||
const schemaRepo = ctx.db.getRepository<UiSchemaRepository>('uiSchemas');
|
||||
const schema = await schemaRepo.getJsonSchema('nocobase-user-profile-edit-form');
|
||||
const { fields, requiredFields } = parseProfileFormSchema(schema);
|
||||
const userRepo = ctx.db.getRepository('users');
|
||||
const user = await userRepo.findOne({ filter: { id: currentUser.id } });
|
||||
for (const field of requiredFields) {
|
||||
if (!values[field]) {
|
||||
// Throw a sequelize validation error and it will be caught by the error handler
|
||||
// so that the field name in error message will be translated
|
||||
throw new ValidationError(`${field} can not be null`, [
|
||||
new ValidationErrorItem(
|
||||
`${field} can not be null`,
|
||||
// @ts-ignore
|
||||
'notNull violation',
|
||||
field,
|
||||
null,
|
||||
user,
|
||||
'is_null',
|
||||
null,
|
||||
null,
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
const result = await userRepo.update({
|
||||
filterByTk: currentUser.id,
|
||||
values: _.pick(values, ['nickname', 'username', 'email', 'phone']),
|
||||
values: _.pick(values, fields),
|
||||
});
|
||||
ctx.body = result;
|
||||
await next();
|
||||
|
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { UiSchemaRepository } from '@nocobase/plugin-ui-schema-storage';
|
||||
import { Migration } from '@nocobase/server';
|
||||
import {
|
||||
adminProfileCreateFormSchema,
|
||||
adminProfileEditFormSchema,
|
||||
userProfileEditFormSchema,
|
||||
} from '../profile/edit-form-schema';
|
||||
|
||||
export default class extends Migration {
|
||||
on = 'afterLoad'; // 'beforeLoad' or 'afterLoad'
|
||||
appVersion = '<1.6.0-alpha.7';
|
||||
|
||||
async up() {
|
||||
const repo = this.db.getRepository<UiSchemaRepository>('uiSchemas');
|
||||
const adminCreateSchema = await repo.findOne({
|
||||
filter: {
|
||||
'x-uid': 'nocobase-admin-profile-create-form',
|
||||
},
|
||||
});
|
||||
if (!adminCreateSchema) {
|
||||
await repo.insert(adminProfileCreateFormSchema);
|
||||
}
|
||||
const adminEditSchema = await repo.findOne({
|
||||
filter: {
|
||||
'x-uid': 'nocobase-admin-profile-edit-form',
|
||||
},
|
||||
});
|
||||
if (!adminEditSchema) {
|
||||
await repo.insert(adminProfileEditFormSchema);
|
||||
}
|
||||
const userSchema = await repo.findOne({
|
||||
filter: {
|
||||
'x-uid': 'nocobase-user-profile-edit-form',
|
||||
},
|
||||
});
|
||||
if (!userSchema) {
|
||||
await repo.insert(userProfileEditFormSchema);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,493 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
export const adminProfileCreateFormSchema = {
|
||||
type: 'void',
|
||||
'x-uid': 'nocobase-admin-profile-create-form',
|
||||
properties: {
|
||||
form: {
|
||||
type: 'void',
|
||||
'x-decorator': 'FormBlockProvider',
|
||||
'x-decorator-props': {
|
||||
collection: 'users',
|
||||
dataSource: 'main',
|
||||
},
|
||||
'x-use-decorator-props': 'useCreateFormBlockDecoratorProps',
|
||||
properties: {
|
||||
create: {
|
||||
type: 'void',
|
||||
'x-component': 'FormV2',
|
||||
'x-use-component-props': 'useCreateFormBlockProps',
|
||||
properties: {
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'form:configureFields',
|
||||
properties: {
|
||||
nickname: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
nickname: {
|
||||
type: 'string',
|
||||
'x-toolbar': 'FormItemSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component-props': {},
|
||||
'x-collection-field': 'users.nickname',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
username: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
'x-toolbar': 'FormItemSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component-props': {},
|
||||
'x-collection-field': 'users.username',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
email: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
'x-toolbar': 'FormItemSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component-props': {},
|
||||
'x-collection-field': 'users.email',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
phone: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
phone: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
'x-toolbar': 'FormItemSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component-props': {},
|
||||
'x-collection-field': 'users.phone',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
password: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
'x-toolbar': 'FormItemSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component-props': {},
|
||||
'x-collection-field': 'users.password',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
roles: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
roles: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
'x-toolbar': 'FormItemSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component-props': {},
|
||||
'x-collection-field': 'users.roles',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
footer: {
|
||||
type: 'void',
|
||||
'x-component': 'Action.Drawer.FootBar',
|
||||
properties: {
|
||||
cancel: {
|
||||
title: '{{ t("Cancel") }}',
|
||||
'x-component': 'Action',
|
||||
'x-use-component-props': 'useCancelActionProps',
|
||||
},
|
||||
submit: {
|
||||
title: '{{ t("Submit") }}',
|
||||
'x-component': 'Action',
|
||||
'x-use-component-props': 'useCreateActionProps',
|
||||
'x-component-props': {
|
||||
type: 'primary',
|
||||
htmlType: 'submit',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const adminProfileEditFormSchema = {
|
||||
type: 'void',
|
||||
'x-uid': 'nocobase-admin-profile-edit-form',
|
||||
properties: {
|
||||
form: {
|
||||
type: 'void',
|
||||
'x-decorator': 'FormBlockProvider',
|
||||
'x-decorator-props': {
|
||||
collection: 'users',
|
||||
dataSource: 'main',
|
||||
action: 'get',
|
||||
},
|
||||
'x-use-decorator-props': 'useEditFormBlockDecoratorProps',
|
||||
properties: {
|
||||
edit: {
|
||||
type: 'void',
|
||||
'x-component': 'FormV2',
|
||||
'x-use-component-props': 'useEditFormBlockProps',
|
||||
properties: {
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'form:configureFields',
|
||||
properties: {
|
||||
nickname: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
nickname: {
|
||||
type: 'string',
|
||||
'x-toolbar': 'FormItemSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component-props': {},
|
||||
'x-collection-field': 'users.nickname',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
username: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
'x-toolbar': 'FormItemSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component-props': {},
|
||||
'x-collection-field': 'users.username',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
email: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
'x-toolbar': 'FormItemSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component-props': {},
|
||||
'x-collection-field': 'users.email',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
phone: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
phone: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
'x-toolbar': 'FormItemSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component-props': {},
|
||||
'x-collection-field': 'users.phone',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
roles: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
roles: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
'x-toolbar': 'FormItemSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component-props': {},
|
||||
'x-collection-field': 'users.roles',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
footer: {
|
||||
type: 'void',
|
||||
'x-component': 'Action.Drawer.FootBar',
|
||||
properties: {
|
||||
cancel: {
|
||||
title: '{{ t("Cancel") }}',
|
||||
'x-component': 'Action',
|
||||
'x-use-component-props': 'useCancelActionProps',
|
||||
},
|
||||
submit: {
|
||||
title: '{{ t("Submit") }}',
|
||||
'x-component': 'Action',
|
||||
'x-use-component-props': 'useUpdateActionProps',
|
||||
'x-component-props': {
|
||||
type: 'primary',
|
||||
htmlType: 'submit',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const userProfileEditFormSchema = {
|
||||
type: 'void',
|
||||
'x-uid': 'nocobase-user-profile-edit-form',
|
||||
properties: {
|
||||
form: {
|
||||
type: 'void',
|
||||
'x-decorator': 'FormBlockProvider',
|
||||
'x-decorator-props': {
|
||||
collection: 'users',
|
||||
dataSource: 'main',
|
||||
action: 'get',
|
||||
},
|
||||
'x-use-decorator-props': 'useEditFormBlockDecoratorProps',
|
||||
properties: {
|
||||
edit: {
|
||||
type: 'void',
|
||||
'x-component': 'FormV2',
|
||||
'x-use-component-props': 'useEditFormBlockProps',
|
||||
properties: {
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'form:configureFields',
|
||||
properties: {
|
||||
nickname: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
nickname: {
|
||||
type: 'string',
|
||||
'x-toolbar': 'FormItemSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component-props': {},
|
||||
'x-collection-field': 'users.nickname',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
username: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
'x-toolbar': 'FormItemSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component-props': {},
|
||||
'x-collection-field': 'users.username',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
email: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
'x-toolbar': 'FormItemSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component-props': {},
|
||||
'x-collection-field': 'users.email',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
phone: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
phone: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
'x-toolbar': 'FormItemSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component-props': {},
|
||||
'x-collection-field': 'users.phone',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
footer: {
|
||||
type: 'void',
|
||||
'x-component': 'Action.Drawer.FootBar',
|
||||
properties: {
|
||||
cancel: {
|
||||
title: '{{ t("Cancel") }}',
|
||||
'x-component': 'Action',
|
||||
'x-use-component-props': 'useCancelActionProps',
|
||||
},
|
||||
submit: {
|
||||
title: '{{ t("Submit") }}',
|
||||
'x-component': 'Action',
|
||||
'x-use-component-props': 'useUpdateProfileActionProps',
|
||||
'x-component-props': {
|
||||
type: 'primary',
|
||||
htmlType: 'submit',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
@ -8,12 +8,18 @@
|
||||
*/
|
||||
|
||||
import { Collection, Model, Op } from '@nocobase/database';
|
||||
import { Plugin } from '@nocobase/server';
|
||||
import { InstallOptions, Plugin } from '@nocobase/server';
|
||||
import { parse } from '@nocobase/utils';
|
||||
import * as actions from './actions/users';
|
||||
import { UserModel } from './models/UserModel';
|
||||
import PluginUserDataSyncServer from '@nocobase/plugin-user-data-sync';
|
||||
import { UserDataSyncResource } from './user-data-sync-resource';
|
||||
import {
|
||||
adminProfileCreateFormSchema,
|
||||
adminProfileEditFormSchema,
|
||||
userProfileEditFormSchema,
|
||||
} from './profile/edit-form-schema';
|
||||
import { UiSchemaRepository } from '@nocobase/plugin-ui-schema-storage';
|
||||
|
||||
export default class PluginUsersServer extends Plugin {
|
||||
async beforeLoad() {
|
||||
@ -200,6 +206,15 @@ export default class PluginUsersServer extends Plugin {
|
||||
}
|
||||
});
|
||||
|
||||
this.app.resourceManager.use(async (ctx, next) => {
|
||||
const { resourceName, actionName } = ctx.action;
|
||||
if (resourceName === 'users' && actionName === 'updateProfile') {
|
||||
// for triggering workflows
|
||||
ctx.action.actionName = 'update';
|
||||
}
|
||||
await next();
|
||||
});
|
||||
|
||||
const userDataSyncPlugin = this.app.pm.get('user-data-sync') as PluginUserDataSyncServer;
|
||||
if (userDataSyncPlugin && userDataSyncPlugin.enabled) {
|
||||
userDataSyncPlugin.resourceManager.registerResource(new UserDataSyncResource(this.db, this.app.logger));
|
||||
@ -231,7 +246,7 @@ export default class PluginUsersServer extends Plugin {
|
||||
};
|
||||
}
|
||||
|
||||
async install(options) {
|
||||
async initUserCollection(options: InstallOptions) {
|
||||
const { rootNickname, rootPassword, rootEmail, rootUsername } = this.getInstallingData(options);
|
||||
const User = this.db.getCollection('users');
|
||||
|
||||
@ -254,4 +269,19 @@ export default class PluginUsersServer extends Plugin {
|
||||
await repo.db2cm('users');
|
||||
}
|
||||
}
|
||||
|
||||
async initProfileSchema() {
|
||||
const uiSchemas = this.db.getRepository<UiSchemaRepository>('uiSchemas');
|
||||
if (!uiSchemas) {
|
||||
return;
|
||||
}
|
||||
await uiSchemas.insert(adminProfileCreateFormSchema);
|
||||
await uiSchemas.insert(adminProfileEditFormSchema);
|
||||
await uiSchemas.insert(userProfileEditFormSchema);
|
||||
}
|
||||
|
||||
async install(options: InstallOptions) {
|
||||
await this.initUserCollection(options);
|
||||
await this.initProfileSchema();
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user