mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-09 23:49:27 +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();
|
await expect(page.getByTestId('drawer-Action.Drawer-Edit profile')).toBeVisible();
|
||||||
|
|
||||||
// click the Cancel button to close the drawer
|
// 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();
|
await expect(page.getByTestId('drawer-Action.Drawer-Edit profile')).not.toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -175,4 +175,27 @@ ActionDrawer.Footer = observer(
|
|||||||
{ displayName: 'ActionDrawer.Footer' },
|
{ 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;
|
export default ActionDrawer;
|
||||||
|
@ -96,4 +96,5 @@ export type ActionDrawerProps<T = DrawerProps> = T & {
|
|||||||
|
|
||||||
export type ComposedActionDrawer<T = DrawerProps> = React.FC<ActionDrawerProps<T>> & {
|
export type ComposedActionDrawer<T = DrawerProps> = React.FC<ActionDrawerProps<T>> & {
|
||||||
Footer?: React.FC;
|
Footer?: React.FC;
|
||||||
|
FootBar?: React.FC;
|
||||||
};
|
};
|
||||||
|
@ -125,10 +125,11 @@ export const SettingsMenu: React.FC<{
|
|||||||
},
|
},
|
||||||
editProfile,
|
editProfile,
|
||||||
changePassword,
|
changePassword,
|
||||||
(editProfile || changePassword) && {
|
editProfile ||
|
||||||
key: 'divider_2',
|
(changePassword && {
|
||||||
type: 'divider',
|
key: 'divider_2',
|
||||||
},
|
type: 'divider',
|
||||||
|
}),
|
||||||
switchRole,
|
switchRole,
|
||||||
{
|
{
|
||||||
key: 'divider_3',
|
key: 'divider_3',
|
||||||
@ -160,9 +161,9 @@ export const SettingsMenu: React.FC<{
|
|||||||
}, [
|
}, [
|
||||||
addMenuItem,
|
addMenuItem,
|
||||||
api.auth,
|
api.auth,
|
||||||
|
editProfile,
|
||||||
changePassword,
|
changePassword,
|
||||||
controlApp,
|
controlApp,
|
||||||
editProfile,
|
|
||||||
languageSettings,
|
languageSettings,
|
||||||
navigate,
|
navigate,
|
||||||
redirectUrl,
|
redirectUrl,
|
||||||
|
@ -7,133 +7,113 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* 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 { uid } from '@formily/shared';
|
||||||
import { MenuProps } from 'antd';
|
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 { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
ActionContextProvider,
|
ActionContextProvider,
|
||||||
DropdownVisibleContext,
|
DropdownVisibleContext,
|
||||||
|
ExtendCollectionsProvider,
|
||||||
|
RemoteSchemaComponent,
|
||||||
SchemaComponent,
|
SchemaComponent,
|
||||||
useActionContext,
|
useActionContext,
|
||||||
|
useCollectValuesToSubmit,
|
||||||
|
useCollectionManager,
|
||||||
useCurrentUserContext,
|
useCurrentUserContext,
|
||||||
useRequest,
|
|
||||||
useSystemSettings,
|
useSystemSettings,
|
||||||
} from '../';
|
} from '../';
|
||||||
import { useAPIClient } from '../api-client';
|
import { useAPIClient } from '../api-client';
|
||||||
|
|
||||||
const useCloseAction = () => {
|
const useUpdateProfileActionProps = () => {
|
||||||
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 ctx = useCurrentUserContext();
|
const ctx = useCurrentUserContext();
|
||||||
const { setVisible } = useActionContext();
|
const { setVisible } = useActionContext();
|
||||||
const form = useForm();
|
const form = useForm();
|
||||||
const api = useAPIClient();
|
const api = useAPIClient();
|
||||||
|
const actionSchema = useFieldSchema();
|
||||||
|
const actionField = useField();
|
||||||
|
const collectValues = useCollectValuesToSubmit();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async run() {
|
type: 'primary',
|
||||||
const values = await form.submit<any>();
|
htmlType: 'submit',
|
||||||
setVisible(false);
|
async onClick() {
|
||||||
await api.resource('users').updateProfile({
|
const { triggerWorkflows, skipValidator } = actionSchema?.['x-action-settings'] ?? {};
|
||||||
values,
|
if (!skipValidator) {
|
||||||
});
|
await form.submit();
|
||||||
ctx.mutate({
|
}
|
||||||
data: {
|
const values = await collectValues();
|
||||||
...ctx?.data?.data,
|
actionField.data = actionField.data || {};
|
||||||
...values,
|
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 = {
|
const useEditProfileFormBlockDecoratorProps = () => {
|
||||||
type: 'object',
|
const { data } = useCurrentUserContext();
|
||||||
properties: {
|
return {
|
||||||
[uid()]: {
|
filterByTk: data.data?.id,
|
||||||
'x-decorator': 'Form',
|
};
|
||||||
'x-decorator-props': {
|
};
|
||||||
useValues: '{{ useCurrentUserValues }}',
|
|
||||||
},
|
const useCancelActionProps = () => {
|
||||||
'x-component': 'Action.Drawer',
|
const { setVisible } = useActionContext();
|
||||||
'x-component-props': {
|
return {
|
||||||
zIndex: 10000,
|
type: 'default',
|
||||||
},
|
onClick() {
|
||||||
type: 'void',
|
setVisible(false);
|
||||||
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 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 = () => {
|
export const useEditProfile = () => {
|
||||||
@ -147,8 +127,8 @@ export const useEditProfile = () => {
|
|||||||
key: 'profile',
|
key: 'profile',
|
||||||
eventKey: 'EditProfile',
|
eventKey: 'EditProfile',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setVisible(true);
|
|
||||||
ctx?.setVisible(false);
|
ctx?.setVisible(false);
|
||||||
|
setVisible(true);
|
||||||
},
|
},
|
||||||
label: (
|
label: (
|
||||||
<div>
|
<div>
|
||||||
@ -156,8 +136,26 @@ export const useEditProfile = () => {
|
|||||||
<ActionContextProvider value={{ visible, setVisible }}>
|
<ActionContextProvider value={{ visible, setVisible }}>
|
||||||
<div onClick={(e) => e.stopPropagation()}>
|
<div onClick={(e) => e.stopPropagation()}>
|
||||||
<SchemaComponent
|
<SchemaComponent
|
||||||
scope={{ useCurrentUserValues, useCloseAction, useSaveCurrentUserValues, enableEditProfile }}
|
components={{ ProfileEditForm }}
|
||||||
schema={schema}
|
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>
|
</div>
|
||||||
</ActionContextProvider>
|
</ActionContextProvider>
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
"@nocobase/client": "1.x",
|
"@nocobase/client": "1.x",
|
||||||
"@nocobase/database": "1.x",
|
"@nocobase/database": "1.x",
|
||||||
"@nocobase/plugin-error-handler": "1.x",
|
"@nocobase/plugin-error-handler": "1.x",
|
||||||
"@nocobase/plugin-users": "1.x",
|
|
||||||
"@nocobase/resourcer": "1.x",
|
"@nocobase/resourcer": "1.x",
|
||||||
"@nocobase/server": "1.x",
|
"@nocobase/server": "1.x",
|
||||||
"@nocobase/test": "1.x",
|
"@nocobase/test": "1.x",
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
"@nocobase/plugin-acl": "1.x",
|
"@nocobase/plugin-acl": "1.x",
|
||||||
"@nocobase/plugin-auth": "1.x",
|
"@nocobase/plugin-auth": "1.x",
|
||||||
"@nocobase/plugin-user-data-sync": "1.x",
|
"@nocobase/plugin-user-data-sync": "1.x",
|
||||||
|
"@nocobase/plugin-ui-schema-storage": "1.x",
|
||||||
"@nocobase/resourcer": "1.x",
|
"@nocobase/resourcer": "1.x",
|
||||||
"@nocobase/server": "1.x",
|
"@nocobase/server": "1.x",
|
||||||
"@nocobase/test": "1.x",
|
"@nocobase/test": "1.x",
|
||||||
|
@ -20,6 +20,9 @@ import {
|
|||||||
useDataBlockRequest,
|
useDataBlockRequest,
|
||||||
useDataBlockResource,
|
useDataBlockResource,
|
||||||
useRequest,
|
useRequest,
|
||||||
|
RemoteSchemaComponent,
|
||||||
|
useCollectionManager,
|
||||||
|
ExtendCollectionsProvider,
|
||||||
useSchemaComponentContext,
|
useSchemaComponentContext,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { App, Tabs, message } from 'antd';
|
import { App, Tabs, message } from 'antd';
|
||||||
@ -50,6 +53,7 @@ const useSubmitActionProps = () => {
|
|||||||
const collection = useCollection();
|
const collection = useCollection();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
htmlType: 'submit',
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
async onClick() {
|
async onClick() {
|
||||||
await form.submit();
|
await form.submit();
|
||||||
@ -84,19 +88,60 @@ const useEditFormProps = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const UsersManagementTab: React.FC = () => {
|
const ProfileCreateForm = () => {
|
||||||
const { t } = useUsersTranslation();
|
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();
|
const scCtx = useSchemaComponentContext();
|
||||||
return (
|
return (
|
||||||
<SchemaComponentContext.Provider value={{ ...scCtx, designable: false }}>
|
<SchemaComponentContext.Provider value={{ ...scCtx, designable: false }}>
|
||||||
<SchemaComponent
|
<SchemaComponent
|
||||||
schema={usersSchema}
|
schema={{
|
||||||
scope={{ t, useCancelActionProps, useSubmitActionProps, useEditFormProps }}
|
type: 'void',
|
||||||
components={{ PasswordField }}
|
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>
|
</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 UsersSettingsContext = createContext<any>({});
|
||||||
|
|
||||||
const UsersSettingsProvider = (props) => {
|
const UsersSettingsProvider = (props) => {
|
||||||
@ -108,7 +153,6 @@ const UsersSettingsProvider = (props) => {
|
|||||||
|
|
||||||
const UsersSettingsTab: React.FC = () => {
|
const UsersSettingsTab: React.FC = () => {
|
||||||
const { t } = useUsersTranslation();
|
const { t } = useUsersTranslation();
|
||||||
const scCtx = useSchemaComponentContext();
|
|
||||||
const form = useForm();
|
const form = useForm();
|
||||||
const useFormBlockProps = () => {
|
const useFormBlockProps = () => {
|
||||||
const result = useContext(UsersSettingsContext);
|
const result = useContext(UsersSettingsContext);
|
||||||
@ -139,13 +183,11 @@ const UsersSettingsTab: React.FC = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<SchemaComponentContext.Provider value={{ ...scCtx, designable: false }}>
|
<SchemaComponent
|
||||||
<SchemaComponent
|
schema={usersSettingsSchema}
|
||||||
schema={usersSettingsSchema}
|
scope={{ t, useFormBlockProps, useSubmitActionProps }}
|
||||||
scope={{ t, useFormBlockProps, useSubmitActionProps }}
|
components={{ UsersSettingsProvider }}
|
||||||
components={{ UsersSettingsProvider }}
|
/>
|
||||||
/>
|
|
||||||
</SchemaComponentContext.Provider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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 { Plugin } from '@nocobase/client';
|
||||||
import { tval } from '@nocobase/utils/client';
|
import { tval } from '@nocobase/utils/client';
|
||||||
// import { UsersManagement } from './UsersManagement';
|
|
||||||
import ACLPlugin from '@nocobase/plugin-acl/client';
|
import ACLPlugin from '@nocobase/plugin-acl/client';
|
||||||
// import { RoleUsersManager } from './RoleUsersManager';
|
|
||||||
import { lazy } from '@nocobase/client';
|
import { lazy } from '@nocobase/client';
|
||||||
|
const { UsersProvider } = lazy(() => import('./UsersProvider'), 'UsersProvider');
|
||||||
const { UsersManagement } = lazy(() => import('./UsersManagement'), 'UsersManagement');
|
const { UsersManagement } = lazy(() => import('./UsersManagement'), 'UsersManagement');
|
||||||
const { RoleUsersManager } = lazy(() => import('./RoleUsersManager'), 'RoleUsersManager');
|
const { RoleUsersManager } = lazy(() => import('./RoleUsersManager'), 'RoleUsersManager');
|
||||||
|
|
||||||
|
@ -157,13 +157,7 @@ export const usersSchema: ISchema = {
|
|||||||
properties: {
|
properties: {
|
||||||
filter: {
|
filter: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
title: '{{ t("Filter") }}',
|
'x-component': 'FilterAction',
|
||||||
'x-action': 'filter',
|
|
||||||
'x-component': 'Filter.Action',
|
|
||||||
'x-use-component-props': 'useFilterActionProps',
|
|
||||||
'x-component-props': {
|
|
||||||
icon: 'FilterOutlined',
|
|
||||||
},
|
|
||||||
'x-align': 'left',
|
'x-align': 'left',
|
||||||
},
|
},
|
||||||
refresh: {
|
refresh: {
|
||||||
@ -204,55 +198,9 @@ export const usersSchema: ISchema = {
|
|||||||
'x-decorator': 'FormV2',
|
'x-decorator': 'FormV2',
|
||||||
title: '{{t("Add user")}}',
|
title: '{{t("Add user")}}',
|
||||||
properties: {
|
properties: {
|
||||||
nickname: {
|
form: {
|
||||||
'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: {
|
|
||||||
type: 'void',
|
type: 'void',
|
||||||
'x-component': 'Action.Drawer.Footer',
|
'x-component': 'ProfileCreateForm',
|
||||||
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',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -352,54 +300,11 @@ export const usersSchema: ISchema = {
|
|||||||
drawer: {
|
drawer: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
'x-component': 'Action.Drawer',
|
'x-component': 'Action.Drawer',
|
||||||
'x-decorator': 'FormV2',
|
|
||||||
'x-use-decorator-props': 'useEditFormProps',
|
|
||||||
title: '{{t("Edit profile")}}',
|
title: '{{t("Edit profile")}}',
|
||||||
properties: {
|
properties: {
|
||||||
nickname: {
|
form: {
|
||||||
'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: {
|
|
||||||
type: 'void',
|
type: 'void',
|
||||||
'x-component': 'Action.Drawer.Footer',
|
'x-component': 'ProfileEditForm',
|
||||||
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',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -507,19 +412,8 @@ export const usersSettingsSchema: ISchema = {
|
|||||||
default: true,
|
default: true,
|
||||||
'x-content': '{{t("Allow change password")}}',
|
'x-content': '{{t("Allow change password")}}',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
},
|
|
||||||
footer: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'div',
|
|
||||||
'x-component-props': {
|
|
||||||
style: {
|
|
||||||
float: 'right',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
properties: {
|
|
||||||
submit: {
|
submit: {
|
||||||
title: 'Submit',
|
title: '{{t("Save")}}',
|
||||||
'x-component': 'Action',
|
'x-component': 'Action',
|
||||||
'x-use-component-props': 'useSubmitActionProps',
|
'x-use-component-props': 'useSubmitActionProps',
|
||||||
},
|
},
|
||||||
|
@ -23,7 +23,7 @@ describe('actions', () => {
|
|||||||
process.env.INIT_ROOT_PASSWORD = '123456';
|
process.env.INIT_ROOT_PASSWORD = '123456';
|
||||||
process.env.INIT_ROOT_NICKNAME = 'Test';
|
process.env.INIT_ROOT_NICKNAME = 'Test';
|
||||||
app = await createMockServer({
|
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;
|
db = app.db;
|
||||||
|
|
||||||
@ -49,6 +49,7 @@ describe('actions', () => {
|
|||||||
filterByTk: adminUser.id,
|
filterByTk: adminUser.id,
|
||||||
values: {
|
values: {
|
||||||
nickname: 'a',
|
nickname: 'a',
|
||||||
|
username: 'A',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(res1.status).toBe(401);
|
expect(res1.status).toBe(401);
|
||||||
@ -57,6 +58,7 @@ describe('actions', () => {
|
|||||||
filterByTk: adminUser.id,
|
filterByTk: adminUser.id,
|
||||||
values: {
|
values: {
|
||||||
nickname: 'a',
|
nickname: 'a',
|
||||||
|
username: 'A',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(res2.status).toBe(200);
|
expect(res2.status).toBe(200);
|
||||||
|
@ -8,8 +8,28 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Context, DEFAULT_PAGE, DEFAULT_PER_PAGE, Next } from '@nocobase/actions';
|
import { Context, DEFAULT_PAGE, DEFAULT_PER_PAGE, Next } from '@nocobase/actions';
|
||||||
|
import { UiSchemaRepository } from '@nocobase/plugin-ui-schema-storage';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { namespace } from '..';
|
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) {
|
export async function updateProfile(ctx: Context, next: Next) {
|
||||||
const systemSettings = ctx.db.getRepository('systemSettings');
|
const systemSettings = ctx.db.getRepository('systemSettings');
|
||||||
@ -24,10 +44,34 @@ export async function updateProfile(ctx: Context, next: Next) {
|
|||||||
if (!currentUser) {
|
if (!currentUser) {
|
||||||
ctx.throw(401);
|
ctx.throw(401);
|
||||||
}
|
}
|
||||||
const UserRepo = ctx.db.getRepository('users');
|
const schemaRepo = ctx.db.getRepository<UiSchemaRepository>('uiSchemas');
|
||||||
const result = await UserRepo.update({
|
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,
|
filterByTk: currentUser.id,
|
||||||
values: _.pick(values, ['nickname', 'username', 'email', 'phone']),
|
values: _.pick(values, fields),
|
||||||
});
|
});
|
||||||
ctx.body = result;
|
ctx.body = result;
|
||||||
await next();
|
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 { Collection, Model, Op } from '@nocobase/database';
|
||||||
import { Plugin } from '@nocobase/server';
|
import { InstallOptions, Plugin } from '@nocobase/server';
|
||||||
import { parse } from '@nocobase/utils';
|
import { parse } from '@nocobase/utils';
|
||||||
import * as actions from './actions/users';
|
import * as actions from './actions/users';
|
||||||
import { UserModel } from './models/UserModel';
|
import { UserModel } from './models/UserModel';
|
||||||
import PluginUserDataSyncServer from '@nocobase/plugin-user-data-sync';
|
import PluginUserDataSyncServer from '@nocobase/plugin-user-data-sync';
|
||||||
import { UserDataSyncResource } from './user-data-sync-resource';
|
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 {
|
export default class PluginUsersServer extends Plugin {
|
||||||
async beforeLoad() {
|
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;
|
const userDataSyncPlugin = this.app.pm.get('user-data-sync') as PluginUserDataSyncServer;
|
||||||
if (userDataSyncPlugin && userDataSyncPlugin.enabled) {
|
if (userDataSyncPlugin && userDataSyncPlugin.enabled) {
|
||||||
userDataSyncPlugin.resourceManager.registerResource(new UserDataSyncResource(this.db, this.app.logger));
|
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 { rootNickname, rootPassword, rootEmail, rootUsername } = this.getInstallingData(options);
|
||||||
const User = this.db.getCollection('users');
|
const User = this.db.getCollection('users');
|
||||||
|
|
||||||
@ -254,4 +269,19 @@ export default class PluginUsersServer extends Plugin {
|
|||||||
await repo.db2cm('users');
|
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