mirror of
https://gitee.com/nocobase/nocobase.git
synced 2024-11-30 03:08:31 +08:00
fix(public-form): authorization validation and content optimization (#5330)
* fix: public form * refactor: locale improve * refactor: markdown locale * fix: translation --------- Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
51423a37ac
commit
4a5aa7542b
@ -5,8 +5,8 @@
|
||||
"dependencies": {},
|
||||
"displayName": "Public forms",
|
||||
"displayName.zh-CN": "公开表单",
|
||||
"description": "Provides a public form that allows users to submit information without requiring registration or login.",
|
||||
"description.zh-CN": "提供了一种无需用户注册或登录即可提交信息的表单",
|
||||
"description": "Share public forms externally to collect information from anonymous users",
|
||||
"description.zh-CN": "对外分享公开表单,向匿名用户收集信息。",
|
||||
"license": "AGPL-3.0",
|
||||
"homepage": "https://docs.nocobase.com/handbook/public-form",
|
||||
"homepage.zh-CN": "https://docs-cn.nocobase.com/public-form",
|
||||
|
@ -55,17 +55,6 @@ export const publicFormsCollection = {
|
||||
'x-component': 'DataSourceCollectionCascader',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'boolean',
|
||||
name: 'enabledPassword',
|
||||
interface: 'checkbox',
|
||||
uiSchema: {
|
||||
type: 'string',
|
||||
title: `{{t("Enable password",{ns:"${NAMESPACE}"})}}`,
|
||||
'x-component': 'Checkbox',
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'password',
|
||||
name: 'password',
|
||||
|
@ -79,25 +79,10 @@ export function AdminPublicFormPage() {
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
properties: {
|
||||
enabledPassword: {
|
||||
type: 'boolean',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
title: t('Enabled password'),
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input.Password',
|
||||
title: t('Password'),
|
||||
'x-reactions': {
|
||||
dependencies: ['enabledPassword'],
|
||||
fulfill: {
|
||||
state: {
|
||||
required: '{{$deps[0]}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
@ -110,8 +95,8 @@ export function AdminPublicFormPage() {
|
||||
).open({
|
||||
initialValues: { ...others },
|
||||
});
|
||||
const { enabledPassword, password } = values;
|
||||
await handleEditPublicForm({ enabledPassword, password });
|
||||
const { password } = values;
|
||||
await handleEditPublicForm({ password });
|
||||
};
|
||||
|
||||
const handleCopyLink = () => {
|
||||
|
@ -87,7 +87,6 @@ const PublicFormMessageProvider = ({ children }) => {
|
||||
if (f) {
|
||||
f.visible = visible;
|
||||
f.hidden = !visible;
|
||||
f.decoratorProps.title = null;
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -95,6 +94,14 @@ const PublicFormMessageProvider = ({ children }) => {
|
||||
useEffect(() => {
|
||||
toggleFieldVisibility('success', showMessage);
|
||||
toggleFieldVisibility('form', !showMessage);
|
||||
if (!showMessage) {
|
||||
field.form.query('promptMessage').take((f) => {
|
||||
if (f) {
|
||||
f.visible = false;
|
||||
f.hidden = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [showMessage]);
|
||||
|
||||
return (
|
||||
|
@ -12,38 +12,44 @@ import { uid } from '@formily/shared';
|
||||
import {
|
||||
useActionContext,
|
||||
useAPIClient,
|
||||
useBlockRequestContext,
|
||||
useCollection,
|
||||
useDataBlockRequest,
|
||||
useDataBlockResource,
|
||||
usePlugin,
|
||||
useBlockRequestContext,
|
||||
} from '@nocobase/client';
|
||||
import { App as AntdApp } from 'antd';
|
||||
import PluginPublicFormsClient from '..';
|
||||
|
||||
const initialSchema = (values, formSchema) => {
|
||||
const initialSchema = (values, formSchema, t) => {
|
||||
return {
|
||||
type: 'void',
|
||||
name: uid(),
|
||||
'x-decorator': 'PublicFormMessageProvider',
|
||||
properties: {
|
||||
form: formSchema,
|
||||
promptMessage: {
|
||||
type: 'void',
|
||||
'x-component': 'h3',
|
||||
'x-component-props': {
|
||||
style: { margin: '10px 0px 10px' },
|
||||
children: '{{ t("Prompt After successful submission",{ns:"public-forms"})}}',
|
||||
},
|
||||
},
|
||||
success: {
|
||||
type: 'void',
|
||||
'x-editable': false,
|
||||
'x-toolbar-props': {
|
||||
draggable: false,
|
||||
},
|
||||
'x-settings': 'blockSettings:markdown',
|
||||
'x-settings': 'blockSettings:publicMarkdown',
|
||||
'x-component': 'Markdown.Void',
|
||||
'x-decorator': 'CardItem',
|
||||
'x-component-props': {
|
||||
content: 'Submitted Successfully',
|
||||
content: t('# Submitted successfully!\nThis is a demo text, **supports Markdown syntax**.'),
|
||||
},
|
||||
'x-decorator-props': {
|
||||
name: 'markdown',
|
||||
engine: 'handlebars',
|
||||
title: '{{ t("After successful submission",{ns:"public-forms"})}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -82,6 +88,7 @@ export const useSubmitActionProps = () => {
|
||||
collection,
|
||||
dataSource,
|
||||
}),
|
||||
plugin.t.bind(plugin),
|
||||
);
|
||||
schema['x-uid'] = key;
|
||||
await resource.create({
|
||||
|
@ -12,7 +12,7 @@ import { AdminPublicFormList } from './components/AdminPublicFormList';
|
||||
import { AdminPublicFormPage } from './components/AdminPublicFormPage';
|
||||
import { PublicFormPage } from './components/PublicFormPage';
|
||||
import { formSchemaCallback } from './schemas/formSchemaCallback';
|
||||
import { publicFormBlockSettings } from './settings';
|
||||
import { publicFormBlockSettings, publicMarkdownBlockSettings } from './settings';
|
||||
import { NAMESPACE } from './locale';
|
||||
export class PluginPublicFormsClient extends Plugin {
|
||||
protected formTypes = new Map();
|
||||
@ -40,6 +40,8 @@ export class PluginPublicFormsClient extends Plugin {
|
||||
|
||||
async load() {
|
||||
this.app.schemaSettingsManager.add(publicFormBlockSettings);
|
||||
this.app.schemaSettingsManager.add(publicMarkdownBlockSettings);
|
||||
|
||||
this.registerFormType('form', {
|
||||
label: 'Form',
|
||||
uiSchema: formSchemaCallback,
|
||||
|
@ -50,24 +50,10 @@ export const createActionSchema = {
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
},
|
||||
enabledPassword: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
default: false,
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
'x-reactions': {
|
||||
dependencies: ['enabledPassword'],
|
||||
fulfill: {
|
||||
state: {
|
||||
required: '{{$deps[0]}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
enabled: {
|
||||
type: 'string',
|
||||
|
@ -52,24 +52,11 @@ export const editActionSchema = {
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
},
|
||||
enabledPassword: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
default: false,
|
||||
},
|
||||
|
||||
password: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
'x-reactions': {
|
||||
dependencies: ['enabledPassword'],
|
||||
fulfill: {
|
||||
state: {
|
||||
required: '{{$deps[0]}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
enabled: {
|
||||
type: 'string',
|
||||
|
@ -13,7 +13,10 @@ import {
|
||||
SchemaSettingsBlockTitleItem,
|
||||
SchemaSettingsLinkageRules,
|
||||
useCollection,
|
||||
SchemaSettingsRenderEngine,
|
||||
} from '@nocobase/client';
|
||||
import { useField } from '@formily/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const publicFormBlockSettings = new SchemaSettings({
|
||||
name: 'blockSettings:publicForm',
|
||||
@ -38,3 +41,28 @@ export const publicFormBlockSettings = new SchemaSettings({
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const publicMarkdownBlockSettings = new SchemaSettings({
|
||||
name: 'blockSettings:publicMarkdown',
|
||||
items: [
|
||||
{
|
||||
name: 'EditMarkdown',
|
||||
type: 'item',
|
||||
useComponentProps() {
|
||||
const field = useField();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return {
|
||||
title: t('Edit markdown'),
|
||||
onClick: () => {
|
||||
field.editable = true;
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'setBlockTemplate',
|
||||
Component: SchemaSettingsRenderEngine,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -10,5 +10,8 @@
|
||||
"The form is not enabled and cannot be accessed": "该表单未启用,无法访问",
|
||||
"Link copied successfully": "复制地址成功",
|
||||
"After successful submission": "提交成功后",
|
||||
"Enable password": "启用密码"
|
||||
"Enable password": "启用密码",
|
||||
"Prompt After successful submission": "提交成功后的信息提示",
|
||||
"Form": "普通表单",
|
||||
"# Submitted successfully!\nThis is a demo text, **supports Markdown syntax**.": "# 提交成功!\n这是一段演示文本,**支持 Markdown 语法**。"
|
||||
}
|
||||
|
@ -40,10 +40,6 @@ export default defineCollection({
|
||||
type: 'boolean',
|
||||
name: 'enabled',
|
||||
},
|
||||
{
|
||||
type: 'boolean',
|
||||
name: 'enabledPassword',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'password',
|
||||
|
@ -57,7 +57,6 @@ export const parseAssociationNames = (dataSourceKey: string, collectionName: str
|
||||
return initialValue;
|
||||
};
|
||||
|
||||
// 定义自定义的 reducer 函数,模仿你的原始逻辑
|
||||
const customReducer = (pre, s, key) => {
|
||||
const prefix = pre || str;
|
||||
const collection = dataSource.collectionManager.getCollection(
|
||||
|
@ -56,7 +56,7 @@ export class PluginPublicFormsServer extends Plugin {
|
||||
return null;
|
||||
}
|
||||
if (!token) {
|
||||
if (instance.get('password') && instance.get('enabledPassword')) {
|
||||
if (instance.get('password')) {
|
||||
if (password === undefined) {
|
||||
return {
|
||||
passwordRequired: true,
|
||||
@ -80,11 +80,16 @@ export class PluginPublicFormsServer extends Plugin {
|
||||
displayName: dataSourceKey,
|
||||
collections,
|
||||
},
|
||||
token: this.app.authManager.jwt.sign({
|
||||
collectionName,
|
||||
formKey: filterByTk,
|
||||
targetCollections: appends,
|
||||
}),
|
||||
token: this.app.authManager.jwt.sign(
|
||||
{
|
||||
collectionName,
|
||||
formKey: filterByTk,
|
||||
targetCollections: appends,
|
||||
},
|
||||
{
|
||||
expiresIn: '1h',
|
||||
},
|
||||
),
|
||||
schema,
|
||||
};
|
||||
}
|
||||
@ -131,6 +136,9 @@ export class PluginPublicFormsServer extends Plugin {
|
||||
key: tokenData.formKey,
|
||||
},
|
||||
});
|
||||
if (!instance) {
|
||||
throw new Error('The form is not found');
|
||||
}
|
||||
if (!instance.get('enabled')) {
|
||||
throw new Error('The form is not enabled');
|
||||
}
|
||||
@ -147,21 +155,19 @@ export class PluginPublicFormsServer extends Plugin {
|
||||
};
|
||||
|
||||
parseACL = async (ctx, next) => {
|
||||
const { resourceName, actionName } = ctx.action;
|
||||
if (ctx.PublicForm && ['create', 'list'].includes(actionName)) {
|
||||
if (actionName === 'create') {
|
||||
ctx.permission = {
|
||||
skip:
|
||||
ctx.PublicForm['collectionName'] === resourceName ||
|
||||
ctx.PublicForm['targetCollections'].includes(resourceName),
|
||||
};
|
||||
} else {
|
||||
ctx.permission = {
|
||||
skip: ctx.PublicForm['targetCollections'].includes(resourceName),
|
||||
};
|
||||
}
|
||||
if (!ctx.PublicForm) {
|
||||
return next();
|
||||
}
|
||||
const { resourceName, actionName } = ctx.action;
|
||||
if (actionName === 'create' && ctx.PublicForm['collectionName'] === resourceName) {
|
||||
ctx.permission = {
|
||||
skip: true,
|
||||
};
|
||||
} else if (actionName === 'list' && ctx.PublicForm['targetCollections'].includes(resourceName)) {
|
||||
ctx.permission = {
|
||||
skip: true,
|
||||
};
|
||||
}
|
||||
|
||||
await next();
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user