feat: support i18n (#99)

* feat: i18next

* multi language data

* feat(client): locale support

* en-US for collections

* us-EN for routes

* en-US for routes

* en-US for attachments

* feat: partial translations

* translation

* add AntdConfigProvider

* translation

* translation

* feat: translation

* feat: add translation

* fix: improve translation

* feat: improve translation

* fix: SyntaxError: Unexpected token ) in JSON at position

* typo

* feat: improve translation

* feat: improve translation

* feat: language settings can be saved on the server

* feat: lang option of init cli

* demo translation

* typo

* change address field to textarea

* address data

* feat: chart translation

* feat: chart translation

* improve translation

* signin,signup,signout...

Co-authored-by: Zhou <zhou.working@gmail.com>
This commit is contained in:
chenos 2021-11-08 19:32:59 +08:00 committed by GitHub
parent 29184740bf
commit 479f64f197
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
131 changed files with 4616 additions and 1588 deletions

View File

@ -69,7 +69,8 @@ Installation
#### Quickstart #### Quickstart
~~~shell ~~~shell
yarn create nocobase-app my-nocobase-app --quickstart # `--lang=en-US` Language settings support `--lang=en-US` and `--lang=zh-CN`
yarn create nocobase-app my-nocobase-app --quickstart --lang=en-US # or zh-CN
~~~ ~~~
#### Step by step #### Step by step
@ -81,7 +82,9 @@ yarn create nocobase-app my-nocobase-app
cd my-nocobase-app cd my-nocobase-app
# 3. create initialization data # 3. create initialization data
yarn nocobase init --import-demo # `--import-demo` Importing demo data
# `--lang=en-US` Language settings support `--lang=en-US` and `--lang=zh-CN`
yarn nocobase init --import-demo --lang=en-US
# 4. start project # 4. start project
yarn start yarn start

View File

@ -75,7 +75,8 @@ Database:
#### 快速启动 #### 快速启动
~~~shell ~~~shell
yarn create nocobase-app my-nocobase-app --quickstart yarn create nocobase-app my-nocobase-app --quickstart --lang=zh-CN
# `--lang=en-US` 语言设置,支持 `--lang=en-US``--lang=zh-CN`
~~~ ~~~
#### 分步骤执行 #### 分步骤执行
@ -87,7 +88,9 @@ yarn create nocobase-app my-nocobase-app
cd my-nocobase-app cd my-nocobase-app
# 3. 初始化数据 # 3. 初始化数据
yarn nocobase init --import-demo yarn nocobase init --import-demo --lang=zh-CN
# `--import-demo` 导入演示数据
# `--lang=en-US` 语言设置,支持 `--lang=en-US``--lang=zh-CN`
# 4. 启动项目 # 4. 启动项目
yarn start yarn start

View File

@ -1,7 +1,7 @@
import { useRequest } from 'ahooks'; import { useRequest } from 'ahooks';
import { Spin } from 'antd'; import { Spin } from 'antd';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { MemoryRouter as Router } from 'react-router-dom'; import { HashRouter as Router } from 'react-router-dom';
import { import {
createRouteSwitch, createRouteSwitch,
RouteRedirectProps, RouteRedirectProps,
@ -56,7 +56,7 @@ const App = () => {
return ( return (
<div> <div>
<Router initialEntries={['/admin']}> <Router>
<RouteSwitch routes={data} /> <RouteSwitch routes={data} />
</Router> </Router>
</div> </div>

View File

@ -31,7 +31,7 @@ With the environment ready, the next step is to install a NocoBase application.
To make it easier for newcomers to install and start quickly, NocoBase provides a very simple command line. To make it easier for newcomers to install and start quickly, NocoBase provides a very simple command line.
```bash ```bash
$ yarn create nocobase-app my-nocobase-app --quickstart $ yarn create nocobase-app my-nocobase-app --quickstart --lang=en-US
``` ```
The above command will help you quickly download, install and start the NocoBase application. If you prefer to perform step by step, you can also do this: The above command will help you quickly download, install and start the NocoBase application. If you prefer to perform step by step, you can also do this:
@ -44,7 +44,7 @@ $ yarn create nocobase-app my-nocobase-app
$ cd my-nocobase-app $ cd my-nocobase-app
# 3. 初始化数据 # 3. 初始化数据
$ yarn nocobase init --import-demo $ yarn nocobase init --import-demo --lang=en-US
# 4. 启动项目 # 4. 启动项目
$ yarn start $ yarn start

View File

@ -37,7 +37,7 @@ $ yarn config set registry https://registry.npm.taobao.org/
为了方便新人快速的安装并启动, NocoBase 提供了一行非常简单的命令: 为了方便新人快速的安装并启动, NocoBase 提供了一行非常简单的命令:
```bash ```bash
$ yarn create nocobase-app my-nocobase-app --quickstart $ yarn create nocobase-app my-nocobase-app --quickstart --lang=zh-CN
``` ```
上面这行命令会帮助你快速的下载、安装并启动 NocoBase 应用。如果你喜欢分步执行,也可以这样: 上面这行命令会帮助你快速的下载、安装并启动 NocoBase 应用。如果你喜欢分步执行,也可以这样:
@ -50,7 +50,7 @@ $ yarn create nocobase-app my-nocobase-app
$ cd my-nocobase-app $ cd my-nocobase-app
# 3. 初始化数据 # 3. 初始化数据
$ yarn nocobase init --import-demo $ yarn nocobase init --import-demo --lang=zh-CN
# 4. 启动项目 # 4. 启动项目
$ yarn start $ yarn start

View File

@ -53,6 +53,7 @@
"eslint-plugin-promise": "^4.2.1", "eslint-plugin-promise": "^4.2.1",
"eslint-plugin-react": "^7.23.2", "eslint-plugin-react": "^7.23.2",
"eslint-plugin-standard": "^4.0.1", "eslint-plugin-standard": "^4.0.1",
"i18next": "^21.3.2",
"jest": "^26.6.3", "jest": "^26.6.3",
"koa-bodyparser": "^4.3.0", "koa-bodyparser": "^4.3.0",
"lerna": "^3.22.0", "lerna": "^3.22.0",
@ -65,6 +66,7 @@
"prettier": "^2.3.0", "prettier": "^2.3.0",
"react": "^17.0.1", "react": "^17.0.1",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-i18next": "^11.12.0",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-test-renderer": "^17.0.2", "react-test-renderer": "^17.0.2",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",

View File

@ -23,7 +23,7 @@ export async function create(ctx: Context, next: Next) {
resourceField, resourceField,
associatedName, associatedName,
resourceName, resourceName,
values: data, values: data = {},
fields, fields,
} = ctx.action.params; } = ctx.action.params;
const values = filterByFields(data, fields); const values = filterByFields(data, fields);

View File

@ -70,6 +70,7 @@ for (const plugin of plugins) {
api.plugin( api.plugin(
require(`@nocobase/plugin-client/${libDir}/server`).default, { require(`@nocobase/plugin-client/${libDir}/server`).default, {
dist: path.resolve(process.cwd(), './dist'), dist: path.resolve(process.cwd(), './dist'),
lang: process.env.APP_LANG,
// importDemo: true, // importDemo: true,
}); });

View File

@ -37,6 +37,7 @@
"monaco-editor": "^0.25.2", "monaco-editor": "^0.25.2",
"react-big-calendar": "^0.33.6", "react-big-calendar": "^0.33.6",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-i18next": "^11.12.0",
"react-image-lightbox": "^5.1.4", "react-image-lightbox": "^5.1.4",
"umi-request": "^1.3.5" "umi-request": "^1.3.5"
} }

View File

@ -1,15 +1,40 @@
import React, { createContext } from 'react'; import React, { createContext } from 'react';
// import { ConfigProvider as AntdConfigProvider } from 'antd'; import { I18nextProvider, useTranslation } from 'react-i18next';
// import enUS from 'antd/lib/locale/en_US'; import { i18n } from './i18n';
// import zhCN from 'antd/lib/locale/zh_CN'; import { ConfigProvider as AntdConfigProvider, Spin } from 'antd';
import { ClientProvider } from './constate'; import enUS from 'antd/lib/locale/en_US';
// import moment from 'moment'; import zhCN from 'antd/lib/locale/zh_CN';
// import 'moment/locale/zh-cn'; import { ClientProvider, useClient } from './constate';
// moment.locale('zh-cn'); import useRequest from '@ahooksjs/use-request';
const ConfigContext = createContext<any>(null); function AntdProvider(props) {
const { i18n } = useTranslation();
const { client } = useClient();
const { loading } = useRequest(() => client.request('app:getLang'), {
onSuccess(data) {
const locale = localStorage.getItem('locale');
if (locale !== data.lang) {
console.log('changeLanguage', data.lang);
i18n.changeLanguage(data.lang);
}
},
refreshDeps: [i18n.language],
formatResult: (data) => data?.data,
});
if (loading) {
return <Spin />;
}
console.log('i18n.language', i18n.language);
return <AntdConfigProvider locale={i18n.language === 'zh-CN' ? zhCN : enUS}>{props.children}</AntdConfigProvider>;
}
export function ConfigProvider(props: any) { export function ConfigProvider(props: any) {
const { client } = props; const { client } = props;
return <ClientProvider client={client}>{props.children}</ClientProvider>; return (
<ClientProvider client={client}>
<I18nextProvider i18n={i18n}>
<AntdProvider>{props.children}</AntdProvider>
</I18nextProvider>
</ClientProvider>
);
} }

View File

@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Menu, Switch } from 'antd'; import { Menu, Switch } from 'antd';
import { useCompile } from '../hooks/useCompile';
export interface SwitchMenuItemProps { export interface SwitchMenuItemProps {
onChange?: any; onChange?: any;
@ -9,6 +10,7 @@ export interface SwitchMenuItemProps {
export function SwitchMenuItem(props: SwitchMenuItemProps) { export function SwitchMenuItem(props: SwitchMenuItemProps) {
const { onChange } = props; const { onChange } = props;
const compile = useCompile();
const [checked, setChecked] = useState(props.checked); const [checked, setChecked] = useState(props.checked);
useEffect(() => { useEffect(() => {
setChecked(props.checked); setChecked(props.checked);
@ -30,7 +32,7 @@ export function SwitchMenuItem(props: SwitchMenuItemProps) {
justifyContent: 'space-between', justifyContent: 'space-between',
}} }}
> >
<span>{props.title}</span> <span>{compile(props.title)}</span>
<Switch checked={checked} size={'small'} /> <Switch checked={checked} size={'small'} />
</div> </div>
</Menu.Item> </Menu.Item>

View File

@ -26,7 +26,7 @@ export function AuthProvider(props) {
const { pathname, search } = location; const { pathname, search } = location;
let redirect = `?redirect=${pathname}${search}`; let redirect = `?redirect=${pathname}${search}`;
if (service.error) { if (service.error) {
return <Redirect to={'/login' + redirect} />; return <Redirect to={'/signin' + redirect} />;
} }
return ( return (
<AuthContext.Provider value={{ service, currentUser: service.data }}> <AuthContext.Provider value={{ service, currentUser: service.data }}>

View File

@ -25,6 +25,8 @@ import {
options, options,
} from '../../../schemas/database-field/interfaces'; } from '../../../schemas/database-field/interfaces';
import { useResourceRequest } from '../../../constate'; import { useResourceRequest } from '../../../constate';
import { useTranslation } from 'react-i18next';
import { useCompile } from '../../../hooks/useCompile';
export const RoleContext = createContext(null); export const RoleContext = createContext(null);
@ -206,7 +208,7 @@ const collectionSchema: ISchema = {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
name: 'action1', name: 'action1',
title: '删除', title: "{{t('Delete')}}",
'x-align': 'right', 'x-align': 'right',
'x-decorator': 'AddNew.Displayed', 'x-decorator': 'AddNew.Displayed',
'x-decorator-props': { 'x-decorator-props': {
@ -216,15 +218,15 @@ const collectionSchema: ISchema = {
'x-component-props': { 'x-component-props': {
icon: 'DeleteOutlined', icon: 'DeleteOutlined',
confirm: { confirm: {
title: '删除数据', title: "{{t('Delete record')}}",
content: '删除后无法恢复,确定要删除吗?', content: "{{t('Are you sure you want to delete it?')}}",
}, },
useAction: '{{ Table.useTableDestroyAction }}', useAction: '{{ Table.useTableDestroyAction }}',
}, },
}, },
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '添加', title: "{{t('Add new')}}",
'x-align': 'right', 'x-align': 'right',
'x-decorator': 'AddNew.Displayed', 'x-decorator': 'AddNew.Displayed',
'x-decorator-props': { 'x-decorator-props': {
@ -240,7 +242,7 @@ const collectionSchema: ISchema = {
}, },
column1: { column1: {
type: 'void', type: 'void',
title: '字段名称', title: "{{t('Field display name')}}",
'x-component': 'Table.Column', 'x-component': 'Table.Column',
properties: { properties: {
'uiSchema.title': { 'uiSchema.title': {
@ -252,7 +254,7 @@ const collectionSchema: ISchema = {
}, },
column2: { column2: {
type: 'void', type: 'void',
title: '字段标识', title: "{{t('Field name')}}",
'x-component': 'Table.Column', 'x-component': 'Table.Column',
properties: { properties: {
name: { name: {
@ -264,7 +266,7 @@ const collectionSchema: ISchema = {
}, },
column3: { column3: {
type: 'void', type: 'void',
title: '字段类型', title: "{{t('Field type')}}",
'x-component': 'Table.Column', 'x-component': 'Table.Column',
properties: { properties: {
interface: { interface: {
@ -278,7 +280,7 @@ const collectionSchema: ISchema = {
}, },
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '操作', title: "{{t('Actions')}}",
'x-component': 'Table.Column', 'x-component': 'Table.Column',
'x-component-props': { 'x-component-props': {
width: 160, width: 160,
@ -293,7 +295,7 @@ const collectionSchema: ISchema = {
properties: { properties: {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '编辑', title: "{{t('Edit')}}",
'x-component': 'EditFieldButton', 'x-component': 'EditFieldButton',
'x-component-props': { 'x-component-props': {
type: 'link', type: 'link',
@ -309,7 +311,7 @@ const collectionSchema: ISchema = {
}, },
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '删除', title: "{{t('Delete')}}",
'x-component': 'Action', 'x-component': 'Action',
'x-action-type': 'destroy', 'x-action-type': 'destroy',
'x-component-props': { 'x-component-props': {
@ -330,7 +332,9 @@ const collectionSchema: ISchema = {
function FieldConfigTitle() { function FieldConfigTitle() {
const ctx = useContext(TableRowContext); const ctx = useContext(TableRowContext);
return <>{`配置「${ctx.record.title}」表字段`}</>; const { t } = useTranslation();
const compile = useCompile();
return <>{t('Configure fields of {{title}}', { title: compile(ctx.record.title) })}</>;
} }
function useCollectionResource({ onSuccess }) { function useCollectionResource({ onSuccess }) {
@ -347,7 +351,7 @@ const schema: ISchema = {
name: 'action', name: 'action',
'x-component': 'Action', 'x-component': 'Action',
'x-component-props': { 'x-component-props': {
tooltip: '数据表配置', tooltip: "{{ t('Collections & Fields') }}",
className: 'nb-database-config', className: 'nb-database-config',
icon: 'DatabaseOutlined', icon: 'DatabaseOutlined',
type: 'primary', type: 'primary',
@ -355,7 +359,7 @@ const schema: ISchema = {
properties: { properties: {
modal1: { modal1: {
type: 'void', type: 'void',
title: '数据表配置', title: "{{t('Collections & Fields')}}",
'x-component': 'Action.Drawer', 'x-component': 'Action.Drawer',
properties: { properties: {
table: { table: {
@ -382,7 +386,7 @@ const schema: ISchema = {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
name: 'action1', name: 'action1',
title: '删除', title: "{{t('Delete')}}",
'x-align': 'right', 'x-align': 'right',
'x-decorator': 'AddNew.Displayed', 'x-decorator': 'AddNew.Displayed',
'x-decorator-props': { 'x-decorator-props': {
@ -392,15 +396,15 @@ const schema: ISchema = {
'x-component-props': { 'x-component-props': {
icon: 'DeleteOutlined', icon: 'DeleteOutlined',
confirm: { confirm: {
title: '删除数据', title: "{{t('Delete record')}}",
content: '删除后无法恢复,确定要删除吗?', content: "{{t('Are you sure you want to delete it?')}}",
}, },
useAction: '{{ Table.useTableDestroyAction }}', useAction: '{{ Table.useTableDestroyAction }}',
}, },
}, },
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '创建数据表', title: "{{t('Create collection')}}",
'x-align': 'right', 'x-align': 'right',
'x-decorator': 'AddNew.Displayed', 'x-decorator': 'AddNew.Displayed',
'x-decorator-props': { 'x-decorator-props': {
@ -414,7 +418,7 @@ const schema: ISchema = {
properties: { properties: {
modal: { modal: {
type: 'void', type: 'void',
title: '创建数据表', title: "{{t('Create collection')}}",
'x-decorator': 'Form', 'x-decorator': 'Form',
'x-decorator-props': { 'x-decorator-props': {
useResource: useCollectionResource, useResource: useCollectionResource,
@ -426,17 +430,17 @@ const schema: ISchema = {
properties: { properties: {
title: { title: {
type: 'string', type: 'string',
title: '数据表名称', title: "{{t('Collection display name')}}",
'x-component': 'Input', 'x-component': 'Input',
'x-decorator': 'FormilyFormItem', 'x-decorator': 'FormilyFormItem',
}, },
name: { name: {
type: 'string', type: 'string',
title: '数据表标识', title: "{{t('Collection name')}}",
'x-component': 'Input', 'x-component': 'Input',
'x-decorator': 'FormilyFormItem', 'x-decorator': 'FormilyFormItem',
description: description:
'随机生成,可修改。支持英文、数字和下划线,必须以英文字母开头', "{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.')}}",
}, },
}, },
}, },
@ -446,7 +450,7 @@ const schema: ISchema = {
}, },
column1: { column1: {
type: 'void', type: 'void',
title: '数据表名称', title: "{{t('Collection display name')}}",
'x-component': 'Table.Column', 'x-component': 'Table.Column',
properties: { properties: {
title: { title: {
@ -458,7 +462,7 @@ const schema: ISchema = {
}, },
column2: { column2: {
type: 'void', type: 'void',
title: '数据表标识', title: "{{t('Collection name')}}",
'x-component': 'Table.Column', 'x-component': 'Table.Column',
properties: { properties: {
name: { name: {
@ -470,7 +474,7 @@ const schema: ISchema = {
}, },
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '操作', title: "{{t('Actions')}}",
'x-component': 'Table.Column', 'x-component': 'Table.Column',
'x-component-props': { 'x-component-props': {
width: 160, width: 160,
@ -485,7 +489,7 @@ const schema: ISchema = {
properties: { properties: {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '配置字段', title: "{{t('Configure fields')}}",
'x-component': 'Action', 'x-component': 'Action',
'x-component-props': { 'x-component-props': {
type: 'link', type: 'link',
@ -506,7 +510,7 @@ const schema: ISchema = {
}, },
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '编辑', title: "{{t('Edit')}}",
'x-component': 'Action', 'x-component': 'Action',
'x-component-props': { 'x-component-props': {
type: 'link', type: 'link',
@ -515,7 +519,7 @@ const schema: ISchema = {
properties: { properties: {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '编辑数据表', title: "{{t('Edit collection')}}",
'x-decorator': 'Form', 'x-decorator': 'Form',
'x-decorator-props': { 'x-decorator-props': {
useResource: useDetailsResource, useResource: useDetailsResource,
@ -527,7 +531,7 @@ const schema: ISchema = {
properties: { properties: {
title: { title: {
type: 'string', type: 'string',
title: '数据表名称', title: "{{t('Collection display name')}}",
'x-component': 'Input', 'x-component': 'Input',
'x-decorator': 'FormilyFormItem', 'x-decorator': 'FormilyFormItem',
}, },
@ -537,14 +541,14 @@ const schema: ISchema = {
}, },
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '删除', title: "{{t('Delete')}}",
'x-component': 'Action', 'x-component': 'Action',
'x-action-type': 'destroy', 'x-action-type': 'destroy',
'x-component-props': { 'x-component-props': {
type: 'link', type: 'link',
confirm: { confirm: {
title: '删除数据', title: "{{t('Delete record')}}",
content: '删除后无法恢复,确定要删除吗?', content: "{{t('Are you sure you want to delete it?')}}",
}, },
useAction: '{{ Table.useTableDestroyAction }}', useAction: '{{ Table.useTableDestroyAction }}',
}, },
@ -561,6 +565,8 @@ const schema: ISchema = {
}; };
function CreateFieldButton() { function CreateFieldButton() {
const { t } = useTranslation();
const compile = useCompile();
const { refresh } = useCollectionsContext(); const { refresh } = useCollectionsContext();
const ctx = useContext(TableRowContext); const ctx = useContext(TableRowContext);
const { service } = useTable(); const { service } = useTable();
@ -612,14 +618,14 @@ function CreateFieldButton() {
{options.map( {options.map(
(option, groupIndex) => (option, groupIndex) =>
option.children.length > 0 && ( option.children.length > 0 && (
<Menu.SubMenu key={groupIndex} title={option.label}> <Menu.SubMenu key={groupIndex} title={compile(option.label)}>
{option.children.map((item) => ( {option.children.map((item) => (
<Menu.Item <Menu.Item
disabled={item.disabled} disabled={item.disabled}
style={{ minWidth: 120 }} style={{ minWidth: 120 }}
key={item.name} key={item.name}
> >
{item.title} {compile(item.title)}
</Menu.Item> </Menu.Item>
))} ))}
</Menu.SubMenu> </Menu.SubMenu>
@ -631,11 +637,11 @@ function CreateFieldButton() {
<> <>
<Dropdown overlay={menu} overlayClassName={'all-fields'}> <Dropdown overlay={menu} overlayClassName={'all-fields'}>
<Button type={'primary'} icon={<PlusOutlined />}> <Button type={'primary'} icon={<PlusOutlined />}>
<DownOutlined /> {t('Add new')} <DownOutlined />
</Button> </Button>
</Dropdown> </Dropdown>
<Drawer <Drawer
title={'添加字段'} title={t('Add field')}
width={'50%'} width={'50%'}
visible={visible} visible={visible}
onClose={() => { onClose={() => {
@ -650,7 +656,7 @@ function CreateFieldButton() {
await form.reset(); await form.reset();
}} }}
> >
{t('Cancel')}
</Button> </Button>
<Button <Button
type={'primary'} type={'primary'}
@ -662,7 +668,7 @@ function CreateFieldButton() {
await refresh(); await refresh();
}} }}
> >
{t('Submit')}
</Button> </Button>
</Space> </Space>
} }
@ -685,6 +691,7 @@ function CreateFieldButton() {
} }
function EditFieldButton() { function EditFieldButton() {
const { t } = useTranslation();
const { refresh, findCollection } = useCollectionsContext(); const { refresh, findCollection } = useCollectionsContext();
const { service } = useTable(); const { service } = useTable();
const ctx = useContext(TableRowContext); const ctx = useContext(TableRowContext);
@ -729,10 +736,10 @@ function EditFieldButton() {
form.setValues(clone(values || {})); form.setValues(clone(values || {}));
}} }}
> >
{t('Edit')}
</Button> </Button>
<Drawer <Drawer
title={'编辑字段'} title={t('Edit field')}
width={'50%'} width={'50%'}
visible={visible} visible={visible}
onClose={() => setVisible(false)} onClose={() => setVisible(false)}
@ -743,7 +750,7 @@ function EditFieldButton() {
setVisible(false); setVisible(false);
}} }}
> >
{t('Cancel')}
</Button> </Button>
<Button <Button
type={'primary'} type={'primary'}
@ -756,7 +763,7 @@ function EditFieldButton() {
await refresh(); await refresh();
}} }}
> >
{t('Submit')}
</Button> </Button>
</Space> </Space>
} }

View File

@ -6,6 +6,7 @@ import { useRequest } from 'ahooks';
import { VisibleContext } from '../../context'; import { VisibleContext } from '../../context';
import { ISchema } from '../../schemas'; import { ISchema } from '../../schemas';
import { SystemSettingsContext } from './SiteTitle'; import { SystemSettingsContext } from './SiteTitle';
import { i18n } from '../../i18n';
const useResource = ({ onSuccess }) => { const useResource = ({ onSuccess }) => {
const { service, resource } = useContext(SystemSettingsContext); const { service, resource } = useContext(SystemSettingsContext);
@ -49,7 +50,7 @@ const schema: ISchema = {
properties: { properties: {
item1: { item1: {
type: 'void', type: 'void',
title: `系统配置`, title: i18n.t('System settings'),
'x-component': 'Menu.Action', 'x-component': 'Menu.Action',
'x-component-props': { 'x-component-props': {
icon: 'SettingOutlined', icon: 'SettingOutlined',
@ -57,7 +58,7 @@ const schema: ISchema = {
properties: { properties: {
drawer1: { drawer1: {
type: 'void', type: 'void',
title: '系统配置', title: i18n.t('System settings'),
'x-decorator': 'Form', 'x-decorator': 'Form',
'x-decorator-props': { 'x-decorator-props': {
useResource, useResource,
@ -69,19 +70,29 @@ const schema: ISchema = {
properties: { properties: {
title: { title: {
type: 'string', type: 'string',
title: '系统名称', title: i18n.t('System title'),
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
logo: { logo: {
type: 'string', type: 'string',
title: 'LOGO', title: i18n.t('Logo'),
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Upload.Attachment', 'x-component': 'Upload.Attachment',
'x-component-props': { 'x-component-props': {
// accept: 'jpg,png' // accept: 'jpg,png'
}, },
}, },
appLang: {
type: 'string',
title: '{{t("Language")}}',
'x-component': 'Select',
'x-decorator': 'FormItem',
enum: [
{ label: 'English', value: 'en-US' },
{ label: '简体中文', value: 'zh-CN' },
],
},
}, },
}, },
}, },

View File

@ -17,13 +17,15 @@ import { createContext } from 'react';
import { RoleContext } from '.'; import { RoleContext } from '.';
import Checkbox from 'antd/lib/checkbox/Checkbox'; import Checkbox from 'antd/lib/checkbox/Checkbox';
import { useState } from 'react'; import { useState } from 'react';
import { useCompile } from '../../../hooks/useCompile';
import { useTranslation } from 'react-i18next';
const actionTypeMap = new Map( const actionTypeMap = new Map(
Object.entries({ Object.entries({
create: '添加', create: '{{t("Insert")}}',
get: '查看', get: '{{t("View")}}',
update: '编辑', update: '{{t("Update")}}',
destroy: '删除', destroy: '{{t("Delete")}}',
}), }),
); );
@ -44,6 +46,8 @@ const useActionDataSource = () => {
}; };
const useFieldPermissions = () => { const useFieldPermissions = () => {
const { t } = useTranslation();
const compile = useCompile();
const role = useContext(RoleContext); const role = useContext(RoleContext);
const ctx = useContext(TableRowContext); const ctx = useContext(TableRowContext);
const field = useField<Formily.Core.Models.ArrayField>(); const field = useField<Formily.Core.Models.ArrayField>();
@ -163,7 +167,7 @@ const useFieldPermissions = () => {
} }
}} }}
/>{' '} />{' '}
{actionTypeMap.get(actionName)} {compile(actionTypeMap.get(actionName))}
</div> </div>
); );
}; };
@ -173,13 +177,16 @@ const useFieldPermissions = () => {
render: (value, record) => renderCell(actionName, value, record), render: (value, record) => renderCell(actionName, value, record),
})); }));
columns.unshift({ columns.unshift({
title: '字段名称', title: t('Field display name'),
dataIndex: ['field', 'uiSchema', 'title'], dataIndex: ['field', 'uiSchema', 'title'],
render: (v) => compile(v),
}); });
return { columns, dataSource, service }; return { columns, dataSource, service };
}; };
export const ActionPermissionField = observer((props) => { export const ActionPermissionField = observer((props) => {
const { t } = useTranslation();
const compile = useCompile();
const role = useContext(RoleContext); const role = useContext(RoleContext);
const ctx = useContext(TableRowContext); const ctx = useContext(TableRowContext);
const field = useField<Formily.Core.Models.ArrayField>(); const field = useField<Formily.Core.Models.ArrayField>();
@ -193,22 +200,22 @@ export const ActionPermissionField = observer((props) => {
pagination={false} pagination={false}
columns={[ columns={[
{ {
title: '操作', title: t('Action name'),
dataIndex: 'actionName', dataIndex: 'actionName',
render: (value) => <span>{actionTypeMap.get(value)}</span>, render: (value) => <span>{compile(actionTypeMap.get(value))}</span>,
}, },
{ {
title: '类型', title: t('Action type'),
dataIndex: 'actionName', dataIndex: 'actionName',
render: (value) => render: (value) =>
value === 'create' ? ( value === 'create' ? (
<Tag></Tag> <Tag color={'green'}>{t('Operate on new data')}</Tag>
) : ( ) : (
<Tag></Tag> <Tag color={'blue'}>{t('Operate on existing data')}</Tag>
), ),
}, },
{ {
title: '允许操作', title: t('Allow action'),
dataIndex: 'enable', dataIndex: 'enable',
render: (value, record) => ( render: (value, record) => (
<Checkbox <Checkbox
@ -242,7 +249,7 @@ export const ActionPermissionField = observer((props) => {
), ),
}, },
{ {
title: '可操作的数据范围', title: t('Action scope'),
dataIndex: ['scope', 'title'], dataIndex: ['scope', 'title'],
render: (value) => ( render: (value) => (
<Select style={{ minWidth: 150 }} size={'small'}></Select> <Select style={{ minWidth: 150 }} size={'small'}></Select>

View File

@ -7,6 +7,7 @@ import { useContext } from 'react';
import { RoleContext } from '.'; import { RoleContext } from '.';
import { useState } from 'react'; import { useState } from 'react';
import { useResourceRequest } from '../../../constate'; import { useResourceRequest } from '../../../constate';
import { useTranslation } from 'react-i18next';
const getKeys = (items: any[]) => { const getKeys = (items: any[]) => {
const keys = []; const keys = [];
@ -19,6 +20,7 @@ const getKeys = (items: any[]) => {
}; };
export const MenuPermissionTable = observer((props) => { export const MenuPermissionTable = observer((props) => {
const { t } = useTranslation();
const role = useContext(RoleContext); const role = useContext(RoleContext);
const [allUiSchemaKyes, setAllUiSchemaKyes] = useState([]); const [allUiSchemaKyes, setAllUiSchemaKyes] = useState([]);
const [uiSchemaKyes, setUiSchemaKeys] = useState([]); const [uiSchemaKyes, setUiSchemaKeys] = useState([]);
@ -58,7 +60,7 @@ export const MenuPermissionTable = observer((props) => {
}} }}
columns={[ columns={[
{ {
title: '菜单名称', title: t('Menu item name'),
dataIndex: 'title', dataIndex: 'title',
}, },
{ {
@ -83,7 +85,7 @@ export const MenuPermissionTable = observer((props) => {
} }
}} }}
/>{' '} />{' '}
访 {t('Allow access')}
</div> </div>
), ),
dataIndex: 'key', dataIndex: 'key',

View File

@ -17,6 +17,7 @@ import { createContext } from 'react';
import { ActionPermissionField } from './ActionPermissionField'; import { ActionPermissionField } from './ActionPermissionField';
import { MenuPermissionTable } from './MenuPermissionTable'; import { MenuPermissionTable } from './MenuPermissionTable';
import { ISchema } from '../../../'; import { ISchema } from '../../../';
import { useTranslation } from 'react-i18next';
export const RoleContext = createContext(null); export const RoleContext = createContext(null);
@ -166,7 +167,7 @@ const collectionSchema: ISchema = {
properties: { properties: {
column1: { column1: {
type: 'void', type: 'void',
title: '数据表名称', title: '{{t("Collection display name")}}',
'x-component': 'Table.Column', 'x-component': 'Table.Column',
properties: { properties: {
title: { title: {
@ -178,7 +179,7 @@ const collectionSchema: ISchema = {
}, },
column2: { column2: {
type: 'void', type: 'void',
title: '数据表标识', title: '{{t("Collection name")}}',
'x-component': 'Table.Column', 'x-component': 'Table.Column',
properties: { properties: {
name: { name: {
@ -190,7 +191,7 @@ const collectionSchema: ISchema = {
}, },
column3: { column3: {
type: 'void', type: 'void',
title: '操作', title: '{{t("Actions")}}',
'x-component': 'Table.Column', 'x-component': 'Table.Column',
'x-component-props': { 'x-component-props': {
width: 60, width: 60,
@ -205,7 +206,7 @@ const collectionSchema: ISchema = {
properties: { properties: {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '配置', title: '{{t("Configure")}}',
'x-component': 'Action', 'x-component': 'Action',
'x-component-props': { 'x-component-props': {
type: 'link', type: 'link',
@ -214,7 +215,7 @@ const collectionSchema: ISchema = {
properties: { properties: {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '数据表操作权限', title: '{{t("Action permissions")}}',
'x-component': 'Action.Drawer', 'x-component': 'Action.Drawer',
'x-decorator': 'Form', 'x-decorator': 'Form',
'x-decorator-props': { 'x-decorator-props': {
@ -244,183 +245,186 @@ const menuSchema: ISchema = {
'x-component': 'MenuPermissionTable', 'x-component': 'MenuPermissionTable',
}; };
const schema: ISchema = { export const Permissions = () => {
type: 'void', const { t } = useTranslation();
name: 'action', const schema: ISchema = {
'x-component': 'Action', type: 'void',
'x-component-props': { name: 'action',
tooltip: '权限配置', 'x-component': 'Action',
className: 'nb-database-config', 'x-component-props': {
icon: 'LockOutlined', tooltip: '{{ t("Roles & Permissions") }}',
type: 'primary', className: 'nb-database-config',
}, icon: 'LockOutlined',
properties: { type: 'primary',
modal1: { },
type: 'void', properties: {
title: '角色', modal1: {
'x-component': 'Action.Drawer', type: 'void',
properties: { title: '{{t("Roles")}}',
table: { 'x-component': 'Action.Drawer',
type: 'array', properties: {
// 'x-decorator': 'CardItem', table: {
'x-component': 'Table', type: 'array',
default: [], // 'x-decorator': 'CardItem',
'x-component-props': { 'x-component': 'Table',
rowKey: 'name', default: [],
dragSort: true, 'x-component-props': {
showIndex: true, rowKey: 'name',
refreshRequestOnChange: true, dragSort: true,
pagination: { showIndex: true,
pageSize: 10, refreshRequestOnChange: true,
pagination: {
pageSize: 10,
},
useResource,
collectionName: 'roles',
}, },
useResource, properties: {
collectionName: 'roles', [uid()]: {
}, type: 'void',
properties: { 'x-component': 'Table.ActionBar',
[uid()]: { properties: {
type: 'void', [uid()]: {
'x-component': 'Table.ActionBar', type: 'void',
properties: { name: 'action1',
[uid()]: { title: '{{t("Delete")}}',
type: 'void', 'x-align': 'right',
name: 'action1', 'x-decorator': 'AddNew.Displayed',
title: '删除', 'x-decorator-props': {
'x-align': 'right', displayName: 'destroy',
'x-decorator': 'AddNew.Displayed',
'x-decorator-props': {
displayName: 'destroy',
},
'x-component': 'Action',
'x-component-props': {
icon: 'DeleteOutlined',
confirm: {
title: '删除数据',
content: '删除后无法恢复,确定要删除吗?',
}, },
useAction: '{{ Table.useTableDestroyAction }}', 'x-component': 'Action',
}, 'x-component-props': {
}, icon: 'DeleteOutlined',
[uid()]: { confirm: {
type: 'void', title: '{{t("Delete record")}}',
title: '添加', content: '{{t("Are you sure you want to delete it?")}}',
'x-align': 'right',
'x-decorator': 'AddNew.Displayed',
'x-decorator-props': {
displayName: 'create',
},
'x-component': 'Action',
'x-component-props': {
type: 'primary',
icon: 'PlusOutlined',
},
properties: {
modal: {
type: 'void',
title: '添加数据',
'x-decorator': 'Form',
'x-component': 'Action.Drawer',
'x-component-props': {
useOkAction: '{{ Table.useTableCreateAction }}',
}, },
properties: { useAction: '{{ Table.useTableDestroyAction }}',
title: { },
type: 'string', },
title: '角色名称', [uid()]: {
'x-component': 'Input', type: 'void',
'x-decorator': 'FormilyFormItem', title: '{{t("Add new")}}',
'x-align': 'right',
'x-decorator': 'AddNew.Displayed',
'x-decorator-props': {
displayName: 'create',
},
'x-component': 'Action',
'x-component-props': {
type: 'primary',
icon: 'PlusOutlined',
},
properties: {
modal: {
type: 'void',
title: '{{t("Add role")}}',
'x-decorator': 'Form',
'x-component': 'Action.Drawer',
'x-component-props': {
useOkAction: '{{ Table.useTableCreateAction }}',
},
properties: {
title: {
type: 'string',
title: '{{t("Role name")}}',
'x-component': 'Input',
'x-decorator': 'FormilyFormItem',
},
// name: {
// type: 'string',
// title: '角色标识',
// 'x-component': 'Input',
// 'x-decorator': 'FormilyFormItem',
// },
}, },
// name: {
// type: 'string',
// title: '角色标识',
// 'x-component': 'Input',
// 'x-decorator': 'FormilyFormItem',
// },
}, },
}, },
}, },
}, },
}, },
}, column1: {
column1: { type: 'void',
type: 'void', title: '{{t("Role name")}}',
title: '角色名称', 'x-component': 'Table.Column',
'x-component': 'Table.Column', properties: {
properties: { title: {
title: { type: 'string',
type: 'string', 'x-component': 'Input',
'x-component': 'Input', 'x-read-pretty': true,
'x-read-pretty': true, },
}, },
}, },
}, // column2: {
// column2: { // type: 'void',
// type: 'void', // title: '角色标识',
// title: '角色标识', // 'x-component': 'Table.Column',
// 'x-component': 'Table.Column', // properties: {
// properties: { // name: {
// name: { // type: 'string',
// type: 'string', // 'x-component': 'Input',
// 'x-component': 'Input', // 'x-read-pretty': true,
// 'x-read-pretty': true, // },
// }, // },
// }, // },
// }, [uid()]: {
[uid()]: { type: 'void',
type: 'void', title: '{{t("Actions")}}',
title: '操作', 'x-component': 'Table.Column',
'x-component': 'Table.Column', 'x-component-props': {
'x-component-props': { width: 160,
width: 160, },
}, properties: {
properties: { [uid()]: {
[uid()]: { type: 'void',
type: 'void', 'x-component': 'Action.Group',
'x-component': 'Action.Group', 'x-component-props': {
'x-component-props': { type: 'link',
type: 'link', },
}, properties: {
properties: { [uid()]: {
[uid()]: { type: 'void',
type: 'void', title: '{{t("Configure")}}',
title: '配置', 'x-component': 'Action',
'x-component': 'Action', 'x-component-props': {
'x-component-props': { type: 'link',
type: 'link', },
}, 'x-action-type': 'view',
'x-action-type': 'view', properties: {
properties: { [uid()]: {
[uid()]: { type: 'void',
type: 'void', title: '{{t("Configure permissions")}}',
title: '配置权限', 'x-decorator': 'RoleProvider',
'x-decorator': 'RoleProvider', 'x-component': 'Action.Drawer',
'x-component': 'Action.Drawer', 'x-component-props': {
'x-component-props': { bodyStyle: {
bodyStyle: { background: '#f0f2f5',
background: '#f0f2f5', paddingTop: 0,
paddingTop: 0, },
}, },
}, properties: {
properties: { [uid()]: {
[uid()]: { type: 'void',
type: 'void', 'x-component': 'Tabs',
'x-component': 'Tabs', properties: {
properties: { [uid()]: {
[uid()]: { type: 'void',
type: 'void', title: '{{t("Action permissions")}}',
title: '数据表操作权限', 'x-component': 'Tabs.TabPane',
'x-component': 'Tabs.TabPane', 'x-component-props': {},
'x-component-props': {}, properties: {
properties: { collectionSchema,
collectionSchema, },
}, },
}, [uid()]: {
[uid()]: { type: 'void',
type: 'void', title: '{{t("Menu permissions")}}',
title: '菜单访问权限', 'x-component': 'Tabs.TabPane',
'x-component': 'Tabs.TabPane', 'x-component-props': {},
'x-component-props': {}, properties: {
properties: { menuSchema,
menuSchema, },
}, },
}, },
}, },
@ -428,51 +432,51 @@ const schema: ISchema = {
}, },
}, },
}, },
}, [uid()]: {
[uid()]: { type: 'void',
type: 'void', title: '{{t("Edit")}}',
title: '编辑', 'x-component': 'Action',
'x-component': 'Action', 'x-component-props': {
'x-component-props': { type: 'link',
type: 'link', },
}, 'x-action-type': 'update',
'x-action-type': 'update', properties: {
properties: { [uid()]: {
[uid()]: { type: 'void',
type: 'void', title: '{{t("Edit role")}}',
title: '编辑角色', 'x-decorator': 'Form',
'x-decorator': 'Form', 'x-decorator-props': {
'x-decorator-props': { useResource: useDetailsResource,
useResource: useDetailsResource, },
}, 'x-component': 'Action.Drawer',
'x-component': 'Action.Drawer', 'x-component-props': {
'x-component-props': { useOkAction: '{{ Table.useTableUpdateAction }}',
useOkAction: '{{ Table.useTableUpdateAction }}', },
}, properties: {
properties: { title: {
title: { type: 'string',
type: 'string', title: '{{t("Role name")}}',
title: '角色名称', 'x-component': 'Input',
'x-component': 'Input', 'x-decorator': 'FormilyFormItem',
'x-decorator': 'FormilyFormItem', },
}, },
}, },
}, },
}, },
}, [uid()]: {
[uid()]: { type: 'void',
type: 'void', title: '{{t("Delete")}}',
title: '删除', 'x-component': 'Action',
'x-component': 'Action', 'x-designable-bar': 'Table.Action.DesignableBar',
'x-designable-bar': 'Table.Action.DesignableBar', 'x-action-type': 'destroy',
'x-action-type': 'destroy', 'x-component-props': {
'x-component-props': { type: 'link',
type: 'link', confirm: {
confirm: { title: '{{t("Delete record")}}',
title: '删除数据', content: "{{t('Are you sure you want to delete it?')}}",
content: '删除后无法恢复,确定要删除吗?', },
useAction: '{{ Table.useTableDestroyAction }}',
}, },
useAction: '{{ Table.useTableDestroyAction }}',
}, },
}, },
}, },
@ -483,12 +487,11 @@ const schema: ISchema = {
}, },
}, },
}, },
}, };
};
export const Permissions = () => {
return ( return (
<SchemaRenderer <SchemaRenderer
scope={{ t }}
components={{ RoleProvider, ActionPermissionField, MenuPermissionTable }} components={{ RoleProvider, ActionPermissionField, MenuPermissionTable }}
schema={schema} schema={schema}
/> />

View File

@ -6,12 +6,14 @@ import { AuthContext, useCurrentUser } from './Auth';
import { FormButtonGroup, FormDrawer, FormLayout, Submit } from '@formily/antd'; import { FormButtonGroup, FormDrawer, FormLayout, Submit } from '@formily/antd';
import { useState } from 'react'; import { useState } from 'react';
import { useClient } from '../../constate'; import { useClient } from '../../constate';
import { useTranslation } from 'react-i18next';
export const UserInfo = () => { export const UserInfo = () => {
const history = useHistory(); const history = useHistory();
const { service, currentUser } = useContext(AuthContext); const { service, currentUser } = useContext(AuthContext);
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const { request } = useClient(); const { request } = useClient();
const { t, i18n } = useTranslation();
return ( return (
<Dropdown <Dropdown
visible={visible} visible={visible}
@ -21,7 +23,7 @@ export const UserInfo = () => {
<Menu.Item <Menu.Item
onClick={async () => { onClick={async () => {
setVisible(false); setVisible(false);
const values = await FormDrawer('个人资料', () => { const values = await FormDrawer(t('Edit profile'), () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
<SchemaField <SchemaField
@ -30,22 +32,32 @@ export const UserInfo = () => {
properties: { properties: {
email: { email: {
type: 'string', type: 'string',
title: '邮箱', title: t('Email'),
'x-component': 'Input', 'x-component': 'Input',
'x-decorator': 'FormilyFormItem', 'x-decorator': 'FormilyFormItem',
}, },
nickname: { nickname: {
type: 'string', type: 'string',
title: '昵称', title: t('Nickname'),
'x-component': 'Input', 'x-component': 'Input',
'x-decorator': 'FormilyFormItem', 'x-decorator': 'FormilyFormItem',
}, },
// appLang: {
// type: 'string',
// title: '{{t("Language")}}',
// 'x-component': 'Select',
// 'x-decorator': 'FormItem',
// enum: [
// { label: 'English', value: 'en-US' },
// { label: '简体中文', value: 'zh-CN' },
// ],
// },
}, },
}} }}
/> />
<FormDrawer.Footer> <FormDrawer.Footer>
<FormButtonGroup align="right"> <FormButtonGroup align="right">
<Submit onSubmit={(values) => {}}></Submit> <Submit onSubmit={(values) => {}}>{t('Submit')}</Submit>
</FormButtonGroup> </FormButtonGroup>
</FormDrawer.Footer> </FormDrawer.Footer>
</FormLayout> </FormLayout>
@ -60,12 +72,12 @@ export const UserInfo = () => {
service.mutate(data); service.mutate(data);
}} }}
> >
{t('Edit profile')}
</Menu.Item> </Menu.Item>
<Menu.Item> <Menu.Item>
<div style={{ display: 'flex', justifyContent: 'space-between' }}> <div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span style={{ marginRight: 16, display: 'inline-block' }}> <span style={{ marginRight: 16, display: 'inline-block' }}>
{t('Switch role')}
</span> </span>
<Select <Select
className={'roles-select'} className={'roles-select'}
@ -75,7 +87,7 @@ export const UserInfo = () => {
defaultValue={'admin'} defaultValue={'admin'}
disabled disabled
options={[ options={[
{ label: '超级管理员', value: 'admin' }, { label: t('Super admin'), value: 'admin' },
{ label: '数据管理员', value: 'editor' }, { label: '数据管理员', value: 'editor' },
{ label: '普通成员', value: 'member' }, { label: '普通成员', value: 'member' },
]} ]}
@ -85,14 +97,22 @@ export const UserInfo = () => {
<Menu.Item> <Menu.Item>
<div style={{ display: 'flex', justifyContent: 'space-between' }}> <div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span style={{ marginRight: 16, display: 'inline-block' }}> <span style={{ marginRight: 16, display: 'inline-block' }}>
{t('Language')}
</span> </span>
<Select <Select
className={'roles-select'} className={'roles-select'}
bordered={false} bordered={false}
size={'small'} size={'small'}
disabled defaultValue={i18n.language}
defaultValue={'zh-CN'} onChange={async (value) => {
await request('users:updateProfile', {
method: 'post',
data: {
appLang: value,
},
});
await i18n.changeLanguage(value);
}}
options={[ options={[
{ label: '简体中文', value: 'zh-CN' }, { label: '简体中文', value: 'zh-CN' },
{ label: 'English', value: 'en-US' }, { label: 'English', value: 'en-US' },
@ -103,12 +123,12 @@ export const UserInfo = () => {
<Menu.Divider /> <Menu.Divider />
<Menu.Item <Menu.Item
onClick={async () => { onClick={async () => {
await request('users:logout'); await request('users:signout');
localStorage.removeItem('NOCOBASE_TOKEN'); localStorage.removeItem('NOCOBASE_TOKEN');
history.push('/login'); history.push('/signin');
}} }}
> >
退 {t('Sign out')}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }

View File

@ -47,8 +47,10 @@ import {
import { AuthProvider } from './Auth'; import { AuthProvider } from './Auth';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { MenuSelectedKeysContext } from '../../schemas/menu'; import { MenuSelectedKeysContext } from '../../schemas/menu';
import { useTranslation } from 'react-i18next';
function DesignableToggle() { function DesignableToggle() {
const { t } = useTranslation();
const { designable, setDesignable } = useDesignableSwitchContext(); const { designable, setDesignable } = useDesignableSwitchContext();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
useEffect(() => { useEffect(() => {
@ -69,14 +71,14 @@ function DesignableToggle() {
placement={'bottom'} placement={'bottom'}
title={ title={
<span> <span>
{' '} {t('Click here to enter UI editor.')}{' '}
<a <a
onClick={() => { onClick={() => {
localStorage.setItem('hide-tip', 'true'); localStorage.setItem('hide-tip', 'true');
setVisible(false); setVisible(false);
}} }}
> >
{t('Close')}
</a> </a>
</span> </span>
} }
@ -87,7 +89,7 @@ function DesignableToggle() {
> >
<span style={{ height: 45, width: 45, position: 'absolute' }}></span> <span style={{ height: 45, width: 45, position: 'absolute' }}></span>
</Tooltip> </Tooltip>
<Tooltip title={'界面配置'}> <Tooltip title={t('UI editor')}>
<Button <Button
className={cls('nb-designable-toggle', { className={cls('nb-designable-toggle', {
'has-tip': visible, 'has-tip': visible,

View File

@ -7,45 +7,53 @@ import { useForm } from '@formily/react';
import { useHistory, useLocation } from 'react-router-dom'; import { useHistory, useLocation } from 'react-router-dom';
import { useSystemSettings } from '../admin-layout/SiteTitle'; import { useSystemSettings } from '../admin-layout/SiteTitle';
import { useClient } from '../../constate'; import { useClient } from '../../constate';
import { useCompile } from '../../hooks/useCompile';
import { useTranslation } from 'react-i18next';
function Div(props) { function Div(props) {
return <div {...props}></div>; return <div {...props}></div>;
} }
export function useLogin() { export function useSignin() {
const form = useForm(); const form = useForm();
const history = useHistory(); const history = useHistory();
const location = useLocation<any>(); const location = useLocation<any>();
const query = new URLSearchParams(location.search); const query = new URLSearchParams(location.search);
const redirect = query.get('redirect'); const redirect = query.get('redirect');
const { request } = useClient(); const { request } = useClient();
const { i18n } = useTranslation();
return { return {
async run() { async run() {
await form.submit(); await form.submit();
const { data } = await request('users:login', { const { data } = await request('users:signin', {
method: 'post', method: 'post',
data: form.values, data: form.values,
}); });
const lang = localStorage.getItem('locale');
if (data.appLang !== lang) {
await i18n.changeLanguage(data.appLang);
}
history.push(redirect || '/admin'); history.push(redirect || '/admin');
localStorage.setItem('NOCOBASE_TOKEN', data?.token); localStorage.setItem('NOCOBASE_TOKEN', data?.token);
}, },
}; };
} }
export function useRegister() { export function useSignup() {
const form = useForm(); const form = useForm();
const history = useHistory(); const history = useHistory();
const { request } = useClient(); const { request } = useClient();
const { t } = useTranslation();
return { return {
async run() { async run() {
await form.submit(); await form.submit();
const { data } = await request('users:register', { const { data } = await request('users:signup', {
method: 'post', method: 'post',
data: form.values, data: form.values,
}); });
message.success('注册成功,将跳转登录页'); message.success(t('Signed up successfully. It will jump to the login page.'));
setTimeout(() => { setTimeout(() => {
history.push('/login'); history.push('/signin');
}, 1000); }, 1000);
console.log(form.values); console.log(form.values);
}, },
@ -60,6 +68,7 @@ export function RouteSchemaRenderer({ route }) {
formatResult: (result) => result?.data, formatResult: (result) => result?.data,
}, },
); );
const compile = useCompile();
const { title } = useSystemSettings(); const { title } = useSystemSettings();
if (loading) { if (loading) {
return <Spin size={'large'} className={'nb-spin-center'} />; return <Spin size={'large'} className={'nb-spin-center'} />;
@ -67,11 +76,13 @@ export function RouteSchemaRenderer({ route }) {
return ( return (
<div> <div>
<Helmet> <Helmet>
<title>{title ? `${data.title} - ${title}` : data.title}</title> <title>
{title ? `${compile(data.title)} - ${title}` : compile(data.title)}
</title>
</Helmet> </Helmet>
<SchemaRenderer <SchemaRenderer
components={{ Div }} components={{ Div }}
scope={{ useLogin, useRegister }} scope={{ useSignin, useSignup }}
schema={data} schema={data}
/> />
</div> </div>

View File

@ -13,7 +13,7 @@ const RouteSwitch = createRouteSwitch({
console.log({ props }); console.log({ props });
return <div>Home {props.children}</div>; return <div>Home {props.children}</div>;
}, },
Login: () => <div>Login</div>, Signin: () => <div>Signin</div>,
}, },
}); });
@ -21,9 +21,9 @@ export default () => {
const routes: Array<RouteRedirectProps> = [ const routes: Array<RouteRedirectProps> = [
{ {
type: 'route', type: 'route',
path: '/login', path: '/signin',
exact: true, exact: true,
component: 'Login', component: 'Signin',
}, },
{ {
type: 'route', type: 'route',
@ -34,7 +34,7 @@ export default () => {
type: 'route', type: 'route',
path: '/home/123', path: '/home/123',
exact: true, exact: true,
component: 'Login', component: 'Signin',
}, },
], ],
}, },

View File

@ -38,14 +38,14 @@ const routes: Array<RouteRedirectProps> = [
children: [ children: [
{ {
type: 'route', type: 'route',
path: '/login', path: '/signin',
component: 'RouteSchemaRenderer', component: 'RouteSchemaRenderer',
title: `登录`, title: `登录`,
uiSchemaKey: 'dtf9j0b8p9u', uiSchemaKey: 'dtf9j0b8p9u',
}, },
{ {
type: 'route', type: 'route',
path: '/register', path: '/signup',
component: 'RouteSchemaRenderer', component: 'RouteSchemaRenderer',
title: `注册`, title: `注册`,
uiSchemaKey: '46qlxqam3xk', uiSchemaKey: '46qlxqam3xk',

View File

@ -78,6 +78,7 @@ import {
useCollectionContext, useCollectionContext,
useResourceRequest, useResourceRequest,
} from '../../constate'; } from '../../constate';
import { i18n } from '../../i18n';
export const BlockContext = createContext({ dragRef: null }); export const BlockContext = createContext({ dragRef: null });
@ -189,6 +190,7 @@ const useAssociationResource = (options) => {
export const SchemaField = createSchemaField({ export const SchemaField = createSchemaField({
scope: { scope: {
t: i18n.t.bind(i18n),
Table, Table,
Calendar, Calendar,
Kanban, Kanban,

View File

@ -13,6 +13,8 @@ import {
} from '../'; } from '../';
import { UseRequestProvider } from 'ahooks'; import { UseRequestProvider } from 'ahooks';
import { extend } from 'umi-request'; import { extend } from 'umi-request';
import { I18nextProvider } from 'react-i18next';
import i18n from '../i18n';
const request = extend({ const request = extend({
prefix: process.env.API_URL, prefix: process.env.API_URL,

View File

@ -0,0 +1,9 @@
import { compile } from '@formily/json-schema/lib/compiler';
import { useTranslation } from 'react-i18next';
export const useCompile = () => {
const { t } = useTranslation();
return (source: any) => {
return compile(source, { t });
};
};

View File

@ -0,0 +1,52 @@
import i18next from 'i18next';
import { initReactI18next } from 'react-i18next';
import zhCN from './locale/zh_CN';
import enUS from './locale/en_US';
import moment from 'moment';
export const i18n = i18next.createInstance();
i18n.use(initReactI18next).init({
lng: localStorage.getItem('locale') || 'en-US',
debug: true,
defaultNS: 'client',
parseMissingKeyHandler: (key) => {
console.log('parseMissingKeyHandler', `'${key}': '${key}',`);
return key;
},
// ns: ['client'],
resources: {
'en-US': {
client: {
...enUS,
},
},
'zh-CN': {
client: {
...zhCN,
},
},
},
});
const momentLngs = {
'en-US': 'en',
'zh-CN': 'zh-cn',
};
function setMomentLng(language) {
const lng = momentLngs[language||'en-US'] || 'en';
console.log("localStorage.getItem('locale')", lng);
moment.locale(lng);
}
setMomentLng(localStorage.getItem('locale'));
i18n.on('languageChanged', (lng) => {
localStorage.setItem('locale', lng);
setMomentLng(lng);
});
// export const t = i18n.t;
export default i18n;

View File

@ -6,3 +6,4 @@ export * from './schemas';
export * from './constate'; export * from './constate';
export * from './ClientSDK'; export * from './ClientSDK';
export * from './ConfigProvider'; export * from './ConfigProvider';
export * from './i18n';

View File

@ -1 +1,274 @@
export default {}; export default {
'{{count}} filter items_one': '{{count}} filter item',
'{{count}} filter items_other': '{{count}} filter items',
'{{count}} more items_one': '{{count}} more item',
'{{count}} more items_other': '{{count}} more items',
'Delete menu item': 'Delete menu item',
'Today': 'Today',
'Month': 'Month',
'Week': 'Week',
'Work week': 'Work week',
'Day': 'Day',
'Agenda': 'Agenda',
'Date': 'Date',
'Time': 'Time',
'Event': 'Event',
'None': 'None',
'System settings': 'System settings',
'System title': 'System title',
'Logo': 'Logo',
'Add menu item': 'Add menu item',
'Page': 'Page',
'Name': 'Name',
'Icon': 'Icon',
'Group': 'Group',
'Link': 'Link',
'Edit menu item': 'Edit menu item',
'Move to': 'Move to',
'Insert left': 'Insert left',
'Insert right': 'Insert right',
'Insert inner': 'Insert inner',
'Delete': 'Delete',
'UI editor': 'UI editor',
'Collections & Fields': 'Collections & Fields',
'Roles & Permissions': 'Roles & Permissions',
'Edit profile': 'Edit profile',
'Switch role': 'Switch role',
'Super admin': 'Super admin',
'Language': 'Language',
'Sign out': 'Sign out',
'Cancel': 'Cancel',
'Submit': 'Submit',
'Set the data scope': 'Set the data scope',
'Data blocks': 'Data blocks',
'Table': 'Table',
'Form': 'Form',
'Select data source': 'Select data source',
'Calendar': 'Calendar',
'Kanban': 'Kanban',
'Select group field': 'Select group field',
'Media': 'Media',
'Markdown': 'Markdown',
'Wysiwyg': 'Wysiwyg',
'Charts': 'Charts',
'Column chart': 'Column chart',
'Bar chart': 'Bar chart',
'Line chart': 'Line chart',
'Pie chart': 'Pie chart',
'Select template': 'Select template',
'Action logs': 'Action logs',
'Create template': 'Create template',
'Edit markdown': 'Edit markdown',
'Add block': 'Add block',
'Add new': 'Add new',
'Add record': 'Add record',
'Custom field display name': 'Custom field display name',
'Display fields': 'Display fields',
'Edit record': 'Edit record',
'Add page': 'Add page',
'Add group': 'Add group',
'Add link': 'Add link',
'Insert above': 'Insert above',
'Insert below': 'Insert below',
'Save': 'Save',
'Delete block': 'Delete block',
'Are you sure you want to delete it?': 'Are you sure you want to delete it?',
'This is a demo text, **supports Markdown syntax**.': 'This is a demo text, **supports Markdown syntax**.',
'Filter': 'Filter',
'Action type': 'Action type',
'Actions': 'Actions',
'Insert': 'Insert',
'Update': 'Update',
'View': 'View',
'View record': 'View record',
'Data changes': 'Data changes',
'Field name': 'Field name',
'Before change': 'Before change',
'After change': 'After change',
'Delete record': 'Delete record',
'Create collection': 'Create collection',
'Collection display name': 'Collection display name',
'Collection name': 'Collection name',
'Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.': 'Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.',
'Storage type': 'Storage type',
'Edit': 'Edit',
'Edit collection': 'Edit collection',
'Configure fields': 'Configure fields',
'Edit field': 'Edit field',
'Configure fields of {{title}}': 'Configure fields of {{title}}',
'Basic': 'Basic',
'Single line text': 'Single line text',
'Long text': 'Long text',
'Phone': 'Phone',
'Email': 'Email',
'Number': 'Number',
'Percent': 'Percent',
'Password': 'Password',
'Choices': 'Choices',
'Checkbox': 'Checkbox',
'Single select': 'Single select',
'Multiple select': 'Multiple select',
'Radio group': 'Radio group',
'Checkbox group': 'Checkbox group',
'China region': 'China region',
'Attachment': 'Attachment',
'Date & Time': 'Date & Time',
'Datetime': 'Datetime',
'Relation': 'Relation',
'Link to': 'Link to',
'Sub-table': 'Sub-table',
'System info': 'System info',
'Created at': 'Created at',
'Last updated at': 'Last updated at',
'Created by': 'Created by',
'Last updated by': 'Last updated by',
'Add field': 'Add field',
'Field display name': 'Field display name',
'Field type': 'Field type',
'Date format': 'Date format',
'Year/Month/Day': 'Year/Month/Day',
'Year-Month-Day': 'Year-Month-Day',
'Day/Month/Year': 'Day/Month/Year',
'Show time': 'Show time',
'Time format': 'Time format',
'12 hour': '12 hour',
'24 hour': '24 hour',
'Meet <1><0>All</0><1>Any</1></1> conditions in the group': 'Meet <1><0>All</0><1>Any</1></1> conditions in the group',
'Add filter': 'Add filter',
'Add filter group': 'Add filter group',
'is': 'is',
'is not': 'is not',
'contains': 'contains',
'does not contain': 'does not contain',
'is empty': 'is empty',
'is not empty': 'is not empty',
'Display <1><0>10</0><1>20</1><2>50</2><3>100</3></1> items per page': 'Display <1><0>10</0><1>20</1><2>50</2><3>100</3></1> items per page',
'Edit chart': 'Edit chart',
'Add text': 'Add text',
'Filterable fields': 'Filterable fields',
'Edit button': 'Edit button',
'Hide': 'Hide',
'Enable actions': 'Enable actions',
'Export': 'Export',
'Customize': 'Customize',
'Function': 'Function',
'Popup form': 'Popup form',
'Flexible popup': 'Flexible popup',
'Configure actions': 'Configure actions',
'Display order number': 'Display order number',
'Enable drag and drop sorting': 'Enable drag and drop sorting',
'Trigger on click the table row': 'Trigger on click the table row',
'Add tab': 'Add tab',
'Disable tabs': 'Disable tabs',
'Details': 'Details',
'Edit tab': 'Edit tab',
'Relationship blocks': 'Relationship blocks',
'Select record': 'Select record',
'Display name': 'Display name',
'Select icon': 'Select icon',
'Custom column name': 'Custom column name',
'Edit description': 'Edit description',
'Required': 'Required',
'Label field': 'Label field',
'Default is the ID field': 'Default is the ID field',
'Set default sorting rules': 'Set default sorting rules',
'is before': 'is before',
'is after': 'is after',
'is on or after': 'is on or after',
'is on or before': 'is on or before',
'Upload': 'Upload',
'Select level': 'Select level',
'Province': 'Province',
'City': 'City',
'Area': 'Area',
'Street': 'Street',
'Village': 'Village',
'Must select to the last level': 'Must select to the last level',
'Move {{title}} to': 'Move {{title}} to',
'Target position': 'Target position',
'After': 'After',
'Before': 'Before',
'Add {{type}} before "{{title}}"': 'Add {{type}} before "{{title}}"',
'Add {{type}} after "{{title}}"': 'Add {{type}} after "{{title}}"',
'Add {{type}} in "{{title}}"': 'Add {{type}} in "{{title}}"',
'Original name': 'Original name',
'Custom name': 'Custom name',
'Options': 'Options',
'Option value': 'Option value',
'Option label': 'Option label',
'Color': 'Color',
'Add option': 'Add option',
'Related collection': 'Related collection',
'Allow linking to multiple records': 'Allow linking to multiple records',
'Allow uploading multiple files': 'Allow uploading multiple files',
'Configure calendar': 'Configure calendar',
'Title field': 'Title field',
'Start date field': 'Start date field',
'End date field': 'End date field',
'Navigate': 'Navigate',
'Title': 'Title',
'Select view': 'View',
'Reset': 'Reset',
'Export fields': 'Export fields',
'Saved successfully': 'Saved successfully',
'Templates': 'Templates',
'Nickname': 'Nickname',
'Sign in': 'Sign in',
'Create an account': 'Create an account',
'Sign up': 'Sign up',
'Confirm password': 'Confirm password',
'Log in with an existing account': 'Log in with an existing account',
'Password mismatch': 'Password mismatch',
'Signed up successfully. It will jump to the login page.': 'Signed up successfully. It will jump to the login page.',
'Users': 'Users',
'Roles': 'Roles',
'Add role': 'Add role',
'Role name': 'Role name',
'Configure': 'Configure',
'Configure permissions': 'Configure permissions',
'Edit role': 'Edit role',
'Action permissions': 'Action permissions',
'Menu permissions': 'Menu permissions',
'Menu item name': 'Menu item name',
'Allow access': 'Allow access',
'Action name': 'Action name',
'Allow action': 'Allow action',
'Action scope': 'Action scope',
'Operate on new data': 'Operate on new data',
'Operate on existing data': 'Operate on existing data',
'Yes': 'Yes',
'No': 'No',
'Red': 'Red',
'Magenta': 'Magenta',
'Volcano': 'Volcano',
'Orange': 'Orange',
'Gold': 'Gold',
'Lime': 'Lime',
'Green': 'Green',
'Cyan': 'Cyan',
'Blue': 'Blue',
'Geek Blue': 'Geek Blue',
'Purple': 'Purple',
'Default': 'Default',
'Add card': 'Add card',
};

View File

@ -1 +1,274 @@
export default {}; export default {
'Display <1><0>10</0><1>20</1><2>50</2><3>100</3></1> items per page': '每页显示 <1><0>10</0><1>20</1><2>50</2><3>100</3></1> 条',
'Meet <1><0>All</0><1>Any</1></1> conditions in the group': '满足组内 <1><0>全部</0><1>任意</1></1> 条件',
'Open in<1><0>Modal</0><1>Drawer</1><2>Window</2></1>': '在 <1><0>对话框</0><1>抽屉</1><2>窗口</2></1> 内打开',
'{{count}} filter items': '{{count}} 个筛选项',
'{{count}} more items': '还有 {{count}} 项',
'Today': '今天',
'Month': '月',
'Week': '周',
'Work week': '工作日',
'Day': '天',
'Agenda': '列表',
'Date': '日期',
'Time': '时间',
'Event': '事件',
'None': '无',
'System settings': '系统设置',
'System title': '系统名称',
'Logo': 'Logo',
'Add menu item': '添加菜单项',
'Page': '页面',
'Name': '名称',
'Icon': '图标',
'Group': '分组',
'Link': '链接',
'Edit menu item': '编辑菜单项',
'Move to': '移动到',
'Insert left': '在左边插入',
'Insert right': '在右边插入',
'Insert inner': '在里面插入',
'Delete': '删除',
'UI editor': '界面配置',
'Collections & Fields': '数据表配置',
'Roles & Permissions': '角色和权限',
'Edit profile': '个人资料',
'Switch role': '切换角色',
'Super admin': '超级管理员',
'Language': '语言设置',
'Sign out': '注销',
'Cancel': '取消',
'Submit': '提交',
'Set the data scope': '设置数据范围',
'Data blocks': '数据区块',
'Table': '表格',
'Form': '表单',
'Select data source': '选择数据源',
'Calendar': '日历',
'Kanban': '看板',
'Select group field': '选择分组字段',
'Media': '多媒体',
'Markdown': 'Markdown',
'Wysiwyg': '富文本',
'Charts': '图表',
'Column chart': '柱状图',
'Bar chart': '条形图',
'Line chart': '折线图',
'Pie chart': '饼图',
'Templates': '模板',
'Select template': '选择模板',
'Action logs': '操作日志',
'Create template': '创建模板',
'Edit markdown': '编辑 Markdown',
'Add block': '创建区块',
'Add new': '添加',
'Add record': '添加数据',
'Custom field display name': '自定义字段名称',
'Display fields': '显示字段',
'Edit record': '编辑数据',
'Delete menu item': '删除菜单项',
'Add page': '添加页面',
'Add group': '添加分组',
'Add link': '添加链接',
'Insert above': '在上面插入',
'Insert below': '在下面插入',
'Save': '保存',
'Delete block': '删除区块',
'Are you sure you want to delete it?': '你确定要删除吗?',
'This is a demo text, **supports Markdown syntax**.': '这是一段演示文本,**支持 Markdown 语法**。',
'Filter': '筛选',
'Action type': '操作类型',
'Actions': '操作',
'Insert': '新增',
'Update': '更新',
'View': '查看',
'View record': '查看数据',
'Data changes': '数据变更',
'Field name': '字段标识',
'Before change': '变更前',
'After change': '变更后',
'Delete record': '删除数据',
'Create collection': '创建数据表',
'Collection display name': '数据表名称',
'Collection name': '数据表标识',
'Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.': '随机生成,可修改。支持英文、数字和下划线,必须以英文字母开头。',
'Storage type': '存储类型',
'Edit': '编辑',
'Edit collection': '编辑数据表',
'Configure fields': '配置字段',
'Edit field': '编辑字段',
'Configure fields of {{title}}': '「{{title}}」的字段配置',
'Basic': '基本类型',
'Single line text': '单行文本',
'Long text': '多行文本',
'Phone': '手机号码',
'Email': '电子邮箱',
'Number': '数字',
'Percent': '百分比',
'Password': '密码',
'Choices': '选择类型',
'Checkbox': '勾选',
'Single select': '下拉菜单(单选)',
'Multiple select': '下拉菜单(多选)',
'Radio group': '单选框',
'Checkbox group': '复选框',
'China region': '中国行政区',
'Attachment': '附件',
'Date & Time': '日期 & 时间',
'Datetime': '日期',
'Relation': '关系类型',
'Link to': '关联',
'Sub-table': '子表格',
'System info': '系统信息',
'Created at': '创建日期',
'Last updated at': '最后修改日期',
'Created by': '创建人',
'Last updated by': '最后更新日期',
'Add field': '添加字段',
'Field display name': '字段名称',
'Field type': '字段类型',
'Date format': '日期格式',
'Year/Month/Day': '年/月/日',
'Year-Month-Day': '年-月-日',
'Day/Month/Year': '日/月/年',
'Show time': '显示时间',
'Time format': '时间格式',
'12 hour': '12 小时制',
'24 hour': '24 小时制',
'Add filter': '添加筛选条件',
'Add filter group': '添加筛选分组',
'is': '等于',
'is not': '不等于',
'contains': '包含',
'does not contain': '不包含',
'is empty': '为空',
'is not empty': '不为空',
'Edit chart': '编辑图表',
'Add text': '添加文本',
'Filterable fields': '可筛选字段',
'Edit button': '编辑按钮',
'Hide': '隐藏',
'Enable actions': '启用操作',
'Export': '导出',
'Customize': '自定义',
'Function': 'Function',
'Popup form': 'Popup form',
'Flexible popup': 'Flexible popup',
'Configure actions': '配置操作',
'Display order number': '显示序号',
'Enable drag and drop sorting': '启用拖拽排序',
'Trigger on click the table row': '点击表格行时触发',
'Add tab': '添加标签页',
'Disable tabs': '禁用标签页',
'Details': '详情',
'Edit tab': '编辑标签页',
'Relationship blocks': '关系数据区块',
'Select record': '选择数据',
'Display name': 'Display name',
'Select icon': '选择图标',
'Custom column name': '自定义列名称',
'Edit description': '编辑描述',
'Required': '必填',
'Label field': '标签字段',
'Default is the ID field': '默认为 ID 字段',
'Set default sorting rules': '设置排序规则',
'is before': '早于',
'is after': '晚于',
'is on or after': '不早于',
'is on or before': '不晚于',
'Upload': '上传',
'Select level': '选择层级',
'Province': '省',
'City': '市',
'Area': '区/县',
'Street': '乡镇/街道',
'Village': '村/居委会',
'Must select to the last level': '必须选到最后一级',
'Move {{title}} to': '将 {{title}} 移动到',
'Target position': '目标位置',
'After': '之后',
'Before': '之前',
'Add {{type}} before "{{title}}"': '在 "{{title}}" 前插入{{type}}',
'Add {{type}} after "{{title}}"': '在 "{{title}}" 前插入{{type}}',
'Add {{type}} in "{{title}}"': '在 "{{title}}" 里插入{{type}}',
'Original name': '原名称',
'Custom name': '自定义名称',
'Options': '选项',
'Option value': '选项值',
'Option label': '选项',
'Color': '颜色',
'Add option': '添加选项',
'Related collection': '关系表',
'Allow linking to multiple records': '允许关联多条记录',
'Allow uploading multiple files': '允许上传多个文件',
'Configure calendar': '配置日历',
'Title field': '标题字段',
'Start date field': '开始日期字段',
'End date field': '结束日期字段',
'Navigate': '分页',
'Title': '标题',
'Select view': '切换视图',
'Reset': '重置',
'Export fields': '导出字段',
'Saved successfully': '保存成功',
'Nickname': '昵称',
'Sign in': '登录',
'Create an account': '注册账号',
'Sign up': '注册',
'Confirm password': '确认密码',
'Log in with an existing account': '使用已有账号登录',
'Signed up successfully. It will jump to the login page.': '注册成功,将跳转登录页。',
'Password mismatch': '确认密码不匹配',
'Users': '用户',
'Roles': '角色',
'Add role': '添加角色',
'Role name': '角色名称',
'Configure': '配置',
'Configure permissions': '配置权限',
'Edit role': '编辑角色',
'Action permissions': '数据表操作权限',
'Menu permissions': '菜单访问权限',
'Menu item name': '菜单名称',
'Allow access': '允许访问',
'Action name': '操作名称',
'Allow action': '允许操作',
'Action scope': '可操作数据范围',
'Operate on new data': '对新增数据操作',
'Operate on existing data': '对已有数据操作',
'Yes': '是',
'No': '否',
'Red': '薄暮',
'Magenta': '法式洋红',
'Volcano': '火山',
'Orange': '日暮',
'Gold': '金盏花',
'Lime': '青柠',
'Green': '极光绿',
'Cyan': '明青',
'Blue': '拂晓蓝',
'Geek Blue': '极客蓝',
'Purple': '酱紫',
'Default': '默认',
'Add card': '添加卡片',
};

View File

@ -15,6 +15,7 @@ import { uid } from '@formily/shared';
import { PlusOutlined } from '@ant-design/icons'; import { PlusOutlined } from '@ant-design/icons';
import { Droppable, SortableItem } from '../../components/Sortable'; import { Droppable, SortableItem } from '../../components/Sortable';
import { useClient } from '../../constate'; import { useClient } from '../../constate';
import { useTranslation } from 'react-i18next';
export const ActionBar = observer((props: any) => { export const ActionBar = observer((props: any) => {
const { align = 'top' } = props; const { align = 'top' } = props;
@ -117,7 +118,7 @@ function generateActionSchema(type) {
const actions: { [key: string]: ISchema } = { const actions: { [key: string]: ISchema } = {
update: { update: {
type: 'void', type: 'void',
title: '编辑', title: "{{ t('Edit') }}",
'x-align': 'right', 'x-align': 'right',
'x-decorator': 'AddNew.Displayed', 'x-decorator': 'AddNew.Displayed',
'x-decorator-props': { 'x-decorator-props': {
@ -132,7 +133,7 @@ function generateActionSchema(type) {
properties: { properties: {
modal: { modal: {
type: 'void', type: 'void',
title: '编辑数据', title: "{{ t('Edit record') }}",
'x-decorator': 'Form', 'x-decorator': 'Form',
'x-decorator-props': { 'x-decorator-props': {
useResource: '{{ Table.useResource }}', useResource: '{{ Table.useResource }}',
@ -156,7 +157,7 @@ function generateActionSchema(type) {
}, },
destroy: { destroy: {
type: 'void', type: 'void',
title: '删除', title: "{{ t('Delete') }}",
'x-align': 'right', 'x-align': 'right',
'x-decorator': 'AddNew.Displayed', 'x-decorator': 'AddNew.Displayed',
'x-decorator-props': { 'x-decorator-props': {
@ -167,8 +168,8 @@ function generateActionSchema(type) {
'x-designable-bar': 'Action.DesignableBar', 'x-designable-bar': 'Action.DesignableBar',
'x-component-props': { 'x-component-props': {
confirm: { confirm: {
title: '删除数据', title: "{{ t('Delete record') }}",
content: '删除后无法恢复,确定要删除吗?', content: "{{ t('Are you sure you want to delete it?') }}",
}, },
useAction: '{{ Table.useTableDestroyAction }}', useAction: '{{ Table.useTableDestroyAction }}',
}, },
@ -178,6 +179,7 @@ function generateActionSchema(type) {
} }
function AddActionButton() { function AddActionButton() {
const { t } = useTranslation();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const displayed = useDisplayedMapContext(); const displayed = useDisplayedMapContext();
const { appendChild, remove } = useDesignable(); const { appendChild, remove } = useDesignable();
@ -193,10 +195,10 @@ function AddActionButton() {
onVisibleChange={setVisible} onVisibleChange={setVisible}
overlay={ overlay={
<Menu> <Menu>
<Menu.ItemGroup title={'启用操作'}> <Menu.ItemGroup title={t('Enable actions')}>
{[ {[
{ title: '编辑', name: 'update' }, { title: t('Edit'), name: 'update' },
{ title: '删除', name: 'destroy' }, { title: t('Delete'), name: 'destroy' },
].map((item) => ( ].map((item) => (
<SwitchMenuItem <SwitchMenuItem
key={item.name} key={item.name}
@ -219,10 +221,10 @@ function AddActionButton() {
))} ))}
</Menu.ItemGroup> </Menu.ItemGroup>
<Menu.Divider /> <Menu.Divider />
<Menu.SubMenu disabled title={'自定义'}> <Menu.SubMenu disabled title={t('Customize')}>
<Menu.Item style={{ minWidth: 120 }}></Menu.Item> <Menu.Item style={{ minWidth: 120 }}>{t('Function')}</Menu.Item>
<Menu.Item></Menu.Item> <Menu.Item>{t('Popup form')}</Menu.Item>
<Menu.Item></Menu.Item> <Menu.Item>{t('Flexible popup')}</Menu.Item>
</Menu.SubMenu> </Menu.SubMenu>
</Menu> </Menu>
} }
@ -233,7 +235,7 @@ function AddActionButton() {
type={'dashed'} type={'dashed'}
icon={<PlusOutlined />} icon={<PlusOutlined />}
> >
{t('Configure actions')}
</Button> </Button>
</Dropdown> </Dropdown>
); );

View File

@ -47,12 +47,16 @@ import { createPortal } from 'react-dom';
import { ActionBar } from './ActionBar'; import { ActionBar } from './ActionBar';
import { DragHandle, SortableItem } from '../../components/Sortable'; import { DragHandle, SortableItem } from '../../components/Sortable';
import { useDisplayedMapContext } from '../../constate'; import { useDisplayedMapContext } from '../../constate';
import { Trans, useTranslation } from 'react-i18next';
import { useCompile } from '../../hooks/useCompile';
export const ButtonComponentContext = createContext(null); export const ButtonComponentContext = createContext(null);
function getTooltipProps(tooltip) { function getTooltipProps(tooltip) {
const { t } = useTranslation();
const compile = useCompile();
if (typeof tooltip === 'string') { if (typeof tooltip === 'string') {
return { title: tooltip }; return { title: compile(tooltip) };
} }
return tooltip; return tooltip;
} }
@ -65,6 +69,8 @@ export const Action: any = observer((props: any) => {
icon, icon,
...others ...others
} = props; } = props;
const { t } = useTranslation();
const compile = useCompile();
const { run } = useAction(); const { run } = useAction();
const field = useField(); const field = useField();
const { schema, DesignableBar } = useDesignable(); const { schema, DesignableBar } = useDesignable();
@ -91,10 +97,11 @@ export const Action: any = observer((props: any) => {
} }
}} }}
> >
{schema.title} {compile(schema.title)}
<DesignableBar path={getSchemaPath(schema)} /> <DesignableBar path={getSchemaPath(schema)} />
</Button> </Button>
); );
console.log('tooltip', tooltip);
if (tooltip) { if (tooltip) {
button = <Tooltip {...getTooltipProps(tooltip)}>{button}</Tooltip>; button = <Tooltip {...getTooltipProps(tooltip)}>{button}</Tooltip>;
} }
@ -110,14 +117,16 @@ export const Action: any = observer((props: any) => {
Action.Link = observer((props: any) => { Action.Link = observer((props: any) => {
const { schema } = useDesignable(); const { schema } = useDesignable();
return <Link {...props}>{schema.title}</Link>; const compile = useCompile();
return <Link {...props}>{compile(schema.title)}</Link>;
}); });
Action.URL = observer((props: any) => { Action.URL = observer((props: any) => {
const { schema } = useDesignable(); const { schema } = useDesignable();
const compile = useCompile();
return ( return (
<a target={'_blank'} {...props}> <a target={'_blank'} {...props}>
{schema.title} {compile(schema.title)}
</a> </a>
); );
}); });
@ -188,6 +197,8 @@ Action.Group = observer((props: any) => {
}); });
Action.Modal = observer((props: any) => { Action.Modal = observer((props: any) => {
const { t } = useTranslation();
const compile = useCompile();
const { const {
useOkAction = useDefaultAction, useOkAction = useDefaultAction,
useCancelAction = useDefaultAction, useCancelAction = useDefaultAction,
@ -203,7 +214,7 @@ Action.Modal = observer((props: any) => {
console.log('Action.Modal.field', schema['x-read-pretty']); console.log('Action.Modal.field', schema['x-read-pretty']);
return ( return (
<Modal <Modal
title={schema.title} title={compile(schema.title)}
destroyOnClose destroyOnClose
maskClosable maskClosable
width={'50%'} width={'50%'}
@ -222,7 +233,7 @@ Action.Modal = observer((props: any) => {
} }
}} }}
> >
{t('Cancel')}
</Button>, </Button>,
<Button <Button
type={'primary'} type={'primary'}
@ -237,7 +248,7 @@ Action.Modal = observer((props: any) => {
} }
}} }}
> >
{t('Submit')}
</Button>, </Button>,
] ]
: null : null
@ -261,6 +272,8 @@ Action.Modal = observer((props: any) => {
}); });
Action.Drawer = observer((props: any) => { Action.Drawer = observer((props: any) => {
const { t } = useTranslation();
const compile = useCompile();
const { const {
useOkAction = useDefaultAction, useOkAction = useDefaultAction,
useCancelAction = useDefaultAction, useCancelAction = useDefaultAction,
@ -278,7 +291,7 @@ Action.Drawer = observer((props: any) => {
{createPortal( {createPortal(
<Drawer <Drawer
width={'50%'} width={'50%'}
title={schema.title} title={compile(schema.title)}
maskClosable maskClosable
destroyOnClose destroyOnClose
footer={ footer={
@ -296,7 +309,7 @@ Action.Drawer = observer((props: any) => {
} }
}} }}
> >
{t('Cancel')}
</Button> </Button>
<Button <Button
onClick={async (e) => { onClick={async (e) => {
@ -310,7 +323,7 @@ Action.Drawer = observer((props: any) => {
}} }}
type={'primary'} type={'primary'}
> >
{t('Submit')}
</Button> </Button>
</Space> </Space>
) )
@ -394,6 +407,8 @@ Action.Dropdown = observer((props: any) => {
}); });
Action.Popover = observer((props) => { Action.Popover = observer((props) => {
const { t } = useTranslation();
const compile = useCompile();
const { schema } = useDesignable(); const { schema } = useDesignable();
const form = useForm(); const form = useForm();
const isFormDecorator = schema['x-decorator'] === 'Form'; const isFormDecorator = schema['x-decorator'] === 'Form';
@ -405,7 +420,7 @@ Action.Popover = observer((props) => {
trigger={['click']} trigger={['click']}
onVisibleChange={setVisible} onVisibleChange={setVisible}
{...props} {...props}
title={schema.title} title={compile(schema.title)}
content={ content={
<div> <div>
{props.children} {props.children}
@ -418,7 +433,7 @@ Action.Popover = observer((props) => {
setVisible(false); setVisible(false);
}} }}
> >
Cancel {t('Cancel')}
</Button> </Button>
<Button <Button
type={'primary'} type={'primary'}
@ -427,7 +442,7 @@ Action.Popover = observer((props) => {
setVisible(false); setVisible(false);
}} }}
> >
Ok {t('Submit')}
</Button> </Button>
</Space> </Space>
</div> </div>
@ -441,6 +456,7 @@ Action.Popover = observer((props) => {
}); });
Action.DesignableBar = (props: any) => { Action.DesignableBar = (props: any) => {
const { t } = useTranslation();
const { schema, remove, refresh, insertAfter } = useDesignable(); const { schema, remove, refresh, insertAfter } = useDesignable();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const isPopup = Object.keys(schema.properties || {}).length > 0; const isPopup = Object.keys(schema.properties || {}).length > 0;
@ -466,7 +482,7 @@ Action.DesignableBar = (props: any) => {
<Menu> <Menu>
<Menu.Item <Menu.Item
onClick={async (e) => { onClick={async (e) => {
const values = await FormDialog('编辑按钮', () => { const values = await FormDialog(t('Edit button'), () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
<SchemaField <SchemaField
@ -475,14 +491,14 @@ Action.DesignableBar = (props: any) => {
properties: { properties: {
title: { title: {
type: 'string', type: 'string',
title: '按钮名称', title: t('Display name'),
required: true, required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
icon: { icon: {
type: 'string', type: 'string',
title: '按钮图标', title: t('Icon'),
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'IconPicker', 'x-component': 'IconPicker',
}, },
@ -505,33 +521,34 @@ Action.DesignableBar = (props: any) => {
refresh(); refresh();
}} }}
> >
{t('Edit button')}
</Menu.Item> </Menu.Item>
{isPopup && ( {isPopup && (
<Menu.Item> <Menu.Item>
{' '} <Trans>
<Select Open in
bordered={false} <Select
size={'small'} bordered={false}
defaultValue={'Action.Modal'} size={'small'}
onChange={(value) => { defaultValue={'Action.Modal'}
const s = Object.values(schema.properties).shift(); onChange={(value) => {
s['x-component'] = value; const s = Object.values(schema.properties).shift();
refresh(); s['x-component'] = value;
updateSchema(s); refresh();
}} updateSchema(s);
> }}
<Select.Option value={'Action.Modal'}> >
<Select.Option value={'Action.Modal'}>
</Select.Option> Modal
<Select.Option value={'Action.Drawer'}> </Select.Option>
<Select.Option value={'Action.Drawer'}>
</Select.Option> Drawer
<Select.Option value={'Action.Window'}> </Select.Option>
<Select.Option value={'Action.Window'}>
</Select.Option> Window
</Select>{' '} </Select.Option>
</Select>
</Trans>
</Menu.Item> </Menu.Item>
)} )}
<Menu.Divider /> <Menu.Divider />
@ -547,7 +564,7 @@ Action.DesignableBar = (props: any) => {
setVisible(false); setVisible(false);
}} }}
> >
{t('Hide')}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }

View File

@ -1,150 +1,207 @@
export const columnChartConfig = { export const columnChartConfig = {
data: [ 'zh-CN': {
{ data: [
type: '家具家电', {
sales: 38, type: '家具家电',
}, sales: 38,
{ },
type: '粮油副食', {
sales: 52, type: '粮油副食',
}, sales: 52,
{ },
type: '生鲜水果', {
sales: 61, type: '生鲜水果',
}, sales: 61,
{ },
type: '美容洗护', {
sales: 145, type: '美容洗护',
}, sales: 145,
{ },
type: '母婴用品', {
sales: 48, type: '母婴用品',
}, sales: 48,
{ },
type: '进口食品', {
sales: 38, type: '进口食品',
}, sales: 38,
{ },
type: '食品饮料', {
sales: 38, type: '食品饮料',
}, sales: 38,
{ },
type: '家庭清洁', {
sales: 38, type: '家庭清洁',
}, sales: 38,
], },
xField: 'type', ],
yField: 'sales', xField: 'type',
label: { yField: 'sales',
position: 'middle',
style: {
fill: '#FFFFFF',
opacity: 0.6,
},
},
xAxis: {
label: { label: {
autoHide: true, position: 'middle',
autoRotate: false, style: {
fill: '#FFFFFF',
opacity: 0.6,
},
},
xAxis: {
label: {
autoHide: true,
autoRotate: false,
},
},
meta: {
type: { alias: '类别' },
sales: { alias: '销售额' },
}, },
}, },
meta: { 'en-US': {
type: { alias: '类别' }, data: [
sales: { alias: '销售额' }, {
}, type: 'Furniture & Appliances',
}; sales: 38,
},
var data = [ {
{ type: 'Food, oil and food',
type: '家具家电', sales: 52,
sales: 38, },
}, {
{ type: 'Fresh fruit',
type: '粮油副食', sales: 61,
sales: 52, },
}, {
{ type: 'Beauty care',
type: '生鲜水果', sales: 145,
sales: 61, },
}, {
{ type: 'Mother & Baby',
type: '美容洗护', sales: 48,
sales: 145, },
}, {
{ type: 'Imported food',
type: '母婴用品', sales: 38,
sales: 48, },
}, {
{ type: 'Food & Beverage',
type: '进口食品', sales: 38,
sales: 38, },
}, {
{ type: 'Household cleaning',
type: '食品饮料', sales: 38,
sales: 38, },
}, ],
{ xField: 'type',
type: '家庭清洁', yField: 'sales',
sales: 38, label: {
}, position: 'middle',
]; style: {
var config = { fill: '#FFFFFF',
data: data, opacity: 0.6,
xField: 'sales', },
yField: 'type',
legend: { position: 'top-left' },
barBackground: { style: { fill: 'rgba(0,0,0,0.1)' } },
interactions: [
{
type: 'active-region',
enable: false,
}, },
], xAxis: {
label: {
autoHide: true,
autoRotate: false,
},
},
meta: {
type: { alias: 'Category' },
sales: { alias: 'Sales' },
},
},
}; };
export const barChartConfig = { export const barChartConfig = {
data: [ 'zh-CN': {
{ data: [
type: '家具家电', {
sales: 38, type: '家具家电',
}, sales: 38,
{ },
type: '粮油副食', {
sales: 52, type: '粮油副食',
}, sales: 52,
{ },
type: '生鲜水果', {
sales: 61, type: '生鲜水果',
}, sales: 61,
{ },
type: '美容洗护', {
sales: 145, type: '美容洗护',
}, sales: 145,
{ },
type: '母婴用品', {
sales: 48, type: '母婴用品',
}, sales: 48,
{ },
type: '进口食品', {
sales: 38, type: '进口食品',
}, sales: 38,
{ },
type: '食品饮料', {
sales: 38, type: '食品饮料',
}, sales: 38,
{ },
type: '家庭清洁', {
sales: 38, type: '家庭清洁',
}, sales: 38,
], },
xField: 'sales', ],
yField: 'type', xField: 'sales',
legend: { position: 'top-left' }, yField: 'type',
barBackground: { style: { fill: 'rgba(0,0,0,0.1)' } }, legend: { position: 'top-left' },
interactions: [ barBackground: { style: { fill: 'rgba(0,0,0,0.1)' } },
{ interactions: [
type: 'active-region', {
enable: false, type: 'active-region',
}, enable: false,
], },
],
},
'en-US': {
data: [
{
type: 'Furniture & Appliances',
sales: 38,
},
{
type: 'Food, oil and food',
sales: 52,
},
{
type: 'Fresh fruit',
sales: 61,
},
{
type: 'Beauty care',
sales: 145,
},
{
type: 'Mother & Baby',
sales: 48,
},
{
type: 'Imported food',
sales: 38,
},
{
type: 'Food & Beverage',
sales: 38,
},
{
type: 'Household cleaning',
sales: 38,
},
],
xField: 'sales',
yField: 'type',
legend: { position: 'top-left' },
barBackground: { style: { fill: 'rgba(0,0,0,0.1)' } },
interactions: [
{
type: 'active-region',
enable: false,
},
],
},
}; };

View File

@ -70,6 +70,9 @@ import {
} from '../../constate'; } from '../../constate';
import SwitchMenuItem from '../../components/SwitchMenuItem'; import SwitchMenuItem from '../../components/SwitchMenuItem';
import { BlockSchemaContext } from '../../context'; import { BlockSchemaContext } from '../../context';
import { useTranslation } from 'react-i18next';
import { i18n } from '../../i18n';
import { useCompile } from '../../hooks/useCompile';
const generateGridBlock = (schema: ISchema) => { const generateGridBlock = (schema: ISchema) => {
const name = schema.name || uid(); const name = schema.name || uid();
@ -112,7 +115,7 @@ function generateCardItemSchema(component) {
const defaults: { [key: string]: ISchema } = { const defaults: { [key: string]: ISchema } = {
'Markdown.Void': { 'Markdown.Void': {
type: 'void', type: 'void',
default: '这是一段演示文字,**支持使用 Markdown 语法**', default: i18n.t('This is a demo text, **supports Markdown syntax**.'),
'x-designable-bar': 'Markdown.Void.DesignableBar', 'x-designable-bar': 'Markdown.Void.DesignableBar',
'x-decorator': 'CardItem', 'x-decorator': 'CardItem',
'x-read-pretty': true, 'x-read-pretty': true,
@ -141,7 +144,7 @@ function generateCardItemSchema(component) {
properties: { properties: {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '筛选', title: "{{t('Filter')}}",
'x-decorator': 'AddNew.Displayed', 'x-decorator': 'AddNew.Displayed',
'x-decorator-props': { 'x-decorator-props': {
displayName: 'filter', displayName: 'filter',
@ -156,7 +159,7 @@ function generateCardItemSchema(component) {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
name: 'action1', name: 'action1',
title: '删除', title: "{{t('Delete')}}",
'x-align': 'right', 'x-align': 'right',
'x-decorator': 'AddNew.Displayed', 'x-decorator': 'AddNew.Displayed',
'x-decorator-props': { 'x-decorator-props': {
@ -167,8 +170,8 @@ function generateCardItemSchema(component) {
'x-component-props': { 'x-component-props': {
icon: 'DeleteOutlined', icon: 'DeleteOutlined',
confirm: { confirm: {
title: '删除数据', title: "{{t('Delete record')}}",
content: '删除后无法恢复,确定要删除吗?', content: "{{t('Are you sure you want to delete it?')}}",
}, },
useAction: '{{ Table.useTableDestroyAction }}', useAction: '{{ Table.useTableDestroyAction }}',
}, },
@ -176,7 +179,7 @@ function generateCardItemSchema(component) {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
name: 'action1', name: 'action1',
title: '添加', title: "{{t('Add new')}}",
'x-align': 'right', 'x-align': 'right',
'x-decorator': 'AddNew.Displayed', 'x-decorator': 'AddNew.Displayed',
'x-decorator-props': { 'x-decorator-props': {
@ -191,7 +194,7 @@ function generateCardItemSchema(component) {
properties: { properties: {
modal: { modal: {
type: 'void', type: 'void',
title: '添加数据', title: "{{t('Add record')}}",
'x-decorator': 'Form', 'x-decorator': 'Form',
'x-component': 'Action.Drawer', 'x-component': 'Action.Drawer',
'x-component-props': { 'x-component-props': {
@ -213,7 +216,7 @@ function generateCardItemSchema(component) {
}, },
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '操作', title: '{{t("Actions")}}',
'x-component': 'Table.Column', 'x-component': 'Table.Column',
'x-component-props': {}, 'x-component-props': {},
'x-designable-bar': 'Table.Operation.DesignableBar', 'x-designable-bar': 'Table.Operation.DesignableBar',
@ -228,7 +231,7 @@ function generateCardItemSchema(component) {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
name: 'action1', name: 'action1',
title: '查看', title: "{{t('View')}}",
'x-component': 'Action', 'x-component': 'Action',
'x-component-props': { 'x-component-props': {
type: 'link', type: 'link',
@ -238,7 +241,7 @@ function generateCardItemSchema(component) {
properties: { properties: {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '查看数据', title: "{{t('View record')}}",
'x-component': 'Action.Drawer', 'x-component': 'Action.Drawer',
'x-component-props': { 'x-component-props': {
bodyStyle: { bodyStyle: {
@ -254,7 +257,7 @@ function generateCardItemSchema(component) {
properties: { properties: {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '详情', title: "{{t('Details')}}",
'x-designable-bar': 'Tabs.TabPane.DesignableBar', 'x-designable-bar': 'Tabs.TabPane.DesignableBar',
'x-component': 'Tabs.TabPane', 'x-component': 'Tabs.TabPane',
'x-component-props': {}, 'x-component-props': {},
@ -276,7 +279,7 @@ function generateCardItemSchema(component) {
}, },
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '编辑', title: "{{t('Edit')}}",
'x-component': 'Action', 'x-component': 'Action',
'x-component-props': { 'x-component-props': {
type: 'link', type: 'link',
@ -286,7 +289,7 @@ function generateCardItemSchema(component) {
properties: { properties: {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '编辑数据', title: "{{t('Edit record')}}",
'x-decorator': 'Form', 'x-decorator': 'Form',
'x-decorator-props': { 'x-decorator-props': {
useResource: '{{ Table.useResource }}', useResource: '{{ Table.useResource }}',
@ -378,7 +381,7 @@ function generateCardItemSchema(component) {
properties: { properties: {
create: { create: {
type: 'void', type: 'void',
title: '添加卡片', title: "{{t('Add card')}}",
// 'x-designable-bar': 'Kanban.AddCardDesignableBar', // 'x-designable-bar': 'Kanban.AddCardDesignableBar',
'x-component': 'Kanban.Card.AddNew', 'x-component': 'Kanban.Card.AddNew',
// 'x-decorator': 'AddNew.Displayed', // 'x-decorator': 'AddNew.Displayed',
@ -389,7 +392,7 @@ function generateCardItemSchema(component) {
properties: { properties: {
modal: { modal: {
type: 'void', type: 'void',
title: '添加数据', title: "{{t('Add record')}}",
'x-decorator': 'Form', 'x-decorator': 'Form',
'x-decorator-props': { 'x-decorator-props': {
useResource: '{{ Kanban.useCreateResource }}', useResource: '{{ Kanban.useCreateResource }}',
@ -424,7 +427,7 @@ function generateCardItemSchema(component) {
}, },
view1: { view1: {
type: 'void', type: 'void',
title: '编辑数据', title: "{{t('Edit record')}}",
'x-decorator': 'Form', 'x-decorator': 'Form',
'x-component': 'Kanban.Card.View', 'x-component': 'Kanban.Card.View',
'x-component-props': { 'x-component-props': {
@ -459,7 +462,7 @@ function generateCardItemSchema(component) {
properties: { properties: {
today: { today: {
type: 'void', type: 'void',
title: '今天', title: "{{t('Today')}}",
'x-designable-bar': 'Calendar.ActionDesignableBar', 'x-designable-bar': 'Calendar.ActionDesignableBar',
'x-component': 'Calendar.Today', 'x-component': 'Calendar.Today',
'x-align': 'left', 'x-align': 'left',
@ -470,7 +473,7 @@ function generateCardItemSchema(component) {
}, },
nav: { nav: {
type: 'void', type: 'void',
title: '翻页', title: "{{t('Navigate')}}",
'x-designable-bar': 'Calendar.ActionDesignableBar', 'x-designable-bar': 'Calendar.ActionDesignableBar',
'x-component': 'Calendar.Nav', 'x-component': 'Calendar.Nav',
'x-align': 'left', 'x-align': 'left',
@ -481,7 +484,7 @@ function generateCardItemSchema(component) {
}, },
title: { title: {
type: 'void', type: 'void',
title: '标题', title: "{{t('Title')}}",
'x-designable-bar': 'Calendar.ActionDesignableBar', 'x-designable-bar': 'Calendar.ActionDesignableBar',
'x-component': 'Calendar.Title', 'x-component': 'Calendar.Title',
'x-align': 'left', 'x-align': 'left',
@ -492,7 +495,7 @@ function generateCardItemSchema(component) {
}, },
viewSelect: { viewSelect: {
type: 'void', type: 'void',
title: '视图切换', title: "{{t('Select view')}}",
'x-designable-bar': 'Calendar.ActionDesignableBar', 'x-designable-bar': 'Calendar.ActionDesignableBar',
'x-component': 'Calendar.ViewSelect', 'x-component': 'Calendar.ViewSelect',
'x-align': 'right', 'x-align': 'right',
@ -503,7 +506,7 @@ function generateCardItemSchema(component) {
}, },
filter: { filter: {
type: 'void', type: 'void',
title: '筛选', title: "{{t('Filter')}}",
'x-align': 'right', 'x-align': 'right',
'x-designable-bar': 'Calendar.Filter.DesignableBar', 'x-designable-bar': 'Calendar.Filter.DesignableBar',
'x-component': 'Calendar.Filter', 'x-component': 'Calendar.Filter',
@ -514,7 +517,7 @@ function generateCardItemSchema(component) {
}, },
create: { create: {
type: 'void', type: 'void',
title: '添加', title: "{{t('Add new')}}",
'x-align': 'right', 'x-align': 'right',
'x-designable-bar': 'Calendar.ActionDesignableBar', 'x-designable-bar': 'Calendar.ActionDesignableBar',
'x-component': 'Action', 'x-component': 'Action',
@ -529,7 +532,7 @@ function generateCardItemSchema(component) {
properties: { properties: {
modal: { modal: {
type: 'void', type: 'void',
title: '添加数据', title: "{{t('Add record')}}",
'x-decorator': 'Form', 'x-decorator': 'Form',
'x-component': 'Action.Drawer', 'x-component': 'Action.Drawer',
'x-component-props': { 'x-component-props': {
@ -560,7 +563,7 @@ function generateCardItemSchema(component) {
properties: { properties: {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '详情', title: "{{t('Details')}}",
'x-designable-bar': 'Tabs.TabPane.DesignableBar', 'x-designable-bar': 'Tabs.TabPane.DesignableBar',
'x-component': 'Tabs.TabPane', 'x-component': 'Tabs.TabPane',
'x-component-props': {}, 'x-component-props': {},
@ -586,7 +589,7 @@ function generateCardItemSchema(component) {
'x-component': 'Chart.Column', 'x-component': 'Chart.Column',
'x-designable-bar': 'Chart.DesignableBar', 'x-designable-bar': 'Chart.DesignableBar',
'x-component-props': { 'x-component-props': {
config: cloneDeep(columnChartConfig), config: cloneDeep(columnChartConfig[i18n.language] || columnChartConfig['en-US']),
}, },
}, },
'Chart.Bar': { 'Chart.Bar': {
@ -595,7 +598,7 @@ function generateCardItemSchema(component) {
'x-component': 'Chart.Bar', 'x-component': 'Chart.Bar',
'x-designable-bar': 'Chart.DesignableBar', 'x-designable-bar': 'Chart.DesignableBar',
'x-component-props': { 'x-component-props': {
config: cloneDeep(barChartConfig), config: cloneDeep(barChartConfig[i18n.language] || barChartConfig['en-US']),
}, },
}, },
'Ref.ActionLogs': { 'Ref.ActionLogs': {
@ -625,7 +628,7 @@ function generateCardItemSchema(component) {
properties: { properties: {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '筛选', title: "{{t('Filter')}}",
'x-align': 'left', 'x-align': 'left',
'x-component': 'Table.Filter', 'x-component': 'Table.Filter',
'x-component-props': { 'x-component-props': {
@ -634,23 +637,23 @@ function generateCardItemSchema(component) {
properties: { properties: {
column1: { column1: {
type: 'void', type: 'void',
title: '操作类型', title: "{{t('Action type')}}",
'x-component': 'Filter.Column', 'x-component': 'Filter.Column',
'x-component-props': { 'x-component-props': {
operations: [ operations: [
{ {
label: '等于', label: "{{ t('is') }}",
value: 'eq', value: 'eq',
selected: true, selected: true,
schema: { 'x-component': 'Select' }, schema: { 'x-component': 'Select' },
}, },
{ {
label: '不等于', label: "{{ t('is not') }}",
value: 'ne', value: 'ne',
schema: { 'x-component': 'Select' }, schema: { 'x-component': 'Select' },
}, },
{ {
label: '包含', label: "{{ t('contains') }}",
value: 'in', value: 'in',
schema: { schema: {
'x-component': 'Select', 'x-component': 'Select',
@ -658,15 +661,23 @@ function generateCardItemSchema(component) {
}, },
}, },
{ {
label: '不包含', label: "{{ t('does not contain') }}",
value: 'notIn', value: 'notIn',
schema: { schema: {
'x-component': 'Select', 'x-component': 'Select',
'x-component-props': { mode: 'tags' }, 'x-component-props': { mode: 'tags' },
}, },
}, },
{ label: '非空', value: '$notNull', noValue: true }, {
{ label: '为空', value: '$null', noValue: true }, label: "{{ t('is empty') }}",
value: '$null',
noValue: true,
},
{
label: "{{ t('is not empty') }}",
value: '$notNull',
noValue: true,
},
], ],
}, },
properties: { properties: {
@ -674,9 +685,21 @@ function generateCardItemSchema(component) {
type: 'string', type: 'string',
'x-component': 'Select', 'x-component': 'Select',
enum: [ enum: [
{ label: '新增', value: 'create', color: 'green' }, {
{ label: '更新', value: 'update', color: 'blue' }, label: "{{ t('Insert') }}",
{ label: '删除', value: 'destroy', color: 'red' }, value: 'create',
color: 'green',
},
{
label: "{{ t('Update') }}",
value: 'update',
color: 'blue',
},
{
label: "{{ t('Delete') }}",
value: 'destroy',
color: 'red',
},
], ],
}, },
}, },
@ -687,7 +710,7 @@ function generateCardItemSchema(component) {
}, },
column1: { column1: {
type: 'void', type: 'void',
title: '操作时间', title: "{{t('Created at')}}",
'x-component': 'Table.Column', 'x-component': 'Table.Column',
properties: { properties: {
created_at: { created_at: {
@ -702,7 +725,7 @@ function generateCardItemSchema(component) {
}, },
column2: { column2: {
type: 'void', type: 'void',
title: '操作用户', title: "{{t('Created by')}}",
'x-component': 'Table.Column', 'x-component': 'Table.Column',
properties: { properties: {
'user.nickname': { 'user.nickname': {
@ -714,7 +737,7 @@ function generateCardItemSchema(component) {
}, },
column3: { column3: {
type: 'void', type: 'void',
title: '数据表', title: "{{t('Collection display name')}}",
'x-component': 'Table.Column', 'x-component': 'Table.Column',
properties: { properties: {
'collection.title': { 'collection.title': {
@ -726,7 +749,7 @@ function generateCardItemSchema(component) {
}, },
column4: { column4: {
type: 'void', type: 'void',
title: '操作类型', title: "{{t('Action type')}}",
'x-component': 'Table.Column', 'x-component': 'Table.Column',
properties: { properties: {
type: { type: {
@ -734,16 +757,16 @@ function generateCardItemSchema(component) {
'x-component': 'Select', 'x-component': 'Select',
'x-read-pretty': true, 'x-read-pretty': true,
enum: [ enum: [
{ label: '新增', value: 'create', color: 'green' }, { label: "{{ t('Insert') }}", value: 'create', color: 'green' },
{ label: '更新', value: 'update', color: 'blue' }, { label: "{{ t('Update') }}", value: 'update', color: 'blue' },
{ label: '删除', value: 'destroy', color: 'red' }, { label: "{{ t('Delete') }}", value: 'destroy', color: 'red' },
], ],
}, },
}, },
}, },
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '操作', title: "{{t('Actions')}}",
'x-component': 'Table.Column', 'x-component': 'Table.Column',
'x-component-props': { 'x-component-props': {
width: 60, width: 60,
@ -753,7 +776,7 @@ function generateCardItemSchema(component) {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
name: 'action1', name: 'action1',
title: '查看', title: "{{t('View')}}",
'x-component': 'Action', 'x-component': 'Action',
'x-component-props': { 'x-component-props': {
type: 'link', type: 'link',
@ -766,7 +789,7 @@ function generateCardItemSchema(component) {
properties: { properties: {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '查看数据', title: "{{t('View record')}}",
'x-read-pretty': true, 'x-read-pretty': true,
'x-decorator': 'Form', 'x-decorator': 'Form',
'x-decorator-props': { 'x-decorator-props': {
@ -782,7 +805,7 @@ function generateCardItemSchema(component) {
properties: { properties: {
created_at: { created_at: {
type: 'string', type: 'string',
title: '操作时间', title: "{{t('Created at')}}",
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'DatePicker', 'x-component': 'DatePicker',
'x-read-pretty': true, 'x-read-pretty': true,
@ -792,37 +815,37 @@ function generateCardItemSchema(component) {
}, },
'user.nickname': { 'user.nickname': {
type: 'string', type: 'string',
title: '操作用户', title: "{{t('Created by')}}",
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
'x-read-pretty': true, 'x-read-pretty': true,
}, },
'collection.title': { 'collection.title': {
type: 'string', type: 'string',
title: '数据表', title: "{{t('Collection display name')}}",
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
'x-read-pretty': true, 'x-read-pretty': true,
}, },
type: { type: {
type: 'string', type: 'string',
title: '操作类型', title: "{{t('Action type')}}",
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Select', 'x-component': 'Select',
'x-read-pretty': true, 'x-read-pretty': true,
enum: [ enum: [
{ {
label: '新增', label: "{{t('Insert')}}",
value: 'create', value: 'create',
color: 'green', color: 'green',
}, },
{ {
label: '更新', label: "{{t('Update')}}",
value: 'update', value: 'update',
color: 'blue', color: 'blue',
}, },
{ {
label: '删除', label: "{{t('Delete')}}",
value: 'destroy', value: 'destroy',
color: 'red', color: 'red',
}, },
@ -830,7 +853,7 @@ function generateCardItemSchema(component) {
}, },
changes: { changes: {
type: 'array', type: 'array',
title: '数据变动', title: "{{t('Data changes')}}",
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'ArrayTable', 'x-component': 'ArrayTable',
'x-component-props': { 'x-component-props': {
@ -858,7 +881,7 @@ function generateCardItemSchema(component) {
column1: { column1: {
type: 'void', type: 'void',
'x-component': 'ArrayTable.Column', 'x-component': 'ArrayTable.Column',
'x-component-props': { title: '字段名称' }, 'x-component-props': { title: "{{t('Field display name')}}" },
properties: { properties: {
field: { field: {
type: 'string', type: 'string',
@ -870,7 +893,7 @@ function generateCardItemSchema(component) {
column3: { column3: {
type: 'void', type: 'void',
'x-component': 'ArrayTable.Column', 'x-component': 'ArrayTable.Column',
'x-component-props': { title: '操作前' }, 'x-component-props': { title: "{{ t('Before change') }}" },
properties: { properties: {
before: { before: {
type: 'string', type: 'string',
@ -882,7 +905,7 @@ function generateCardItemSchema(component) {
column4: { column4: {
type: 'void', type: 'void',
'x-component': 'ArrayTable.Column', 'x-component': 'ArrayTable.Column',
'x-component-props': { title: '操作后' }, 'x-component-props': { title: "{{ t('After change') }}" },
properties: { properties: {
after: { after: {
type: 'string', type: 'string',
@ -920,33 +943,6 @@ function generateFormItemSchema(component) {
return defaults[component]; return defaults[component];
} }
const dbSchema = {
type: 'object',
properties: {
title: {
type: 'string',
title: '数据表名称',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
name: {
type: 'string',
title: '数据表标识',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-read-pretty': true,
},
generalFields: {
type: 'array',
title: '数据表字段',
'x-decorator': 'FormItem',
'x-component': 'DatabaseField',
default: [],
},
},
};
export const AddNew = () => null; export const AddNew = () => null;
AddNew.CardItem = observer((props: any) => { AddNew.CardItem = observer((props: any) => {
@ -955,6 +951,8 @@ AddNew.CardItem = observer((props: any) => {
const path = useSchemaPath(); const path = useSchemaPath();
const { collections = [], loading, refresh } = useCollectionsContext(); const { collections = [], loading, refresh } = useCollectionsContext();
const { createSchema } = useClient(); const { createSchema } = useClient();
const { t } = useTranslation();
const compile = useCompile();
return ( return (
<Dropdown <Dropdown
trigger={['hover']} trigger={['hover']}
@ -1037,10 +1035,10 @@ AddNew.CardItem = observer((props: any) => {
} }
}} }}
> >
<Menu.ItemGroup title={'数据区块'}> <Menu.ItemGroup title={t('Data blocks')}>
{[ {[
{ key: 'Table', title: '表格', icon: 'TableOutlined' }, { key: 'Table', title: t('Table'), icon: 'TableOutlined' },
{ key: 'Form', title: '表单', icon: 'FormOutlined' }, { key: 'Form', title: t('Form'), icon: 'FormOutlined' },
].map((view) => ( ].map((view) => (
<Menu.SubMenu <Menu.SubMenu
icon={<IconPicker type={view.icon} />} icon={<IconPicker type={view.icon} />}
@ -1051,14 +1049,14 @@ AddNew.CardItem = observer((props: any) => {
<Menu.ItemGroup <Menu.ItemGroup
className={'display-fields'} className={'display-fields'}
key={`${view.key}-select`} key={`${view.key}-select`}
title={'选择数据源'} title={t('Select data source')}
> >
{collections?.map((item) => ( {collections?.map((item) => (
<Menu.Item <Menu.Item
style={{ minWidth: 150 }} style={{ minWidth: 150 }}
key={`collection.${item.name}.${view.key}`} key={`collection.${item.name}.${view.key}`}
> >
{item.title} {compile(item.title)}
</Menu.Item> </Menu.Item>
))} ))}
</Menu.ItemGroup> </Menu.ItemGroup>
@ -1071,7 +1069,7 @@ AddNew.CardItem = observer((props: any) => {
{[ {[
{ {
key: 'Calendar', key: 'Calendar',
title: '日历', title: t('Calendar'),
icon: 'CalendarOutlined', icon: 'CalendarOutlined',
// disabled: true, // disabled: true,
}, },
@ -1085,14 +1083,14 @@ AddNew.CardItem = observer((props: any) => {
<Menu.ItemGroup <Menu.ItemGroup
className={'display-fields'} className={'display-fields'}
key={`${view.key}-select`} key={`${view.key}-select`}
title={'选择数据源'} title={t('Select data source')}
> >
{collections?.map((item) => ( {collections?.map((item) => (
<Menu.Item <Menu.Item
style={{ minWidth: 150 }} style={{ minWidth: 150 }}
key={`Calendar.collection.${item.name}.${view.key}`} key={`Calendar.collection.${item.name}.${view.key}`}
onClick={async () => { onClick={async () => {
const values = await FormDialog(`日历配置`, () => { const values = await FormDialog(t('Configure calendar'), () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
<SchemaField <SchemaField
@ -1101,7 +1099,7 @@ AddNew.CardItem = observer((props: any) => {
properties: { properties: {
title: { title: {
type: 'string', type: 'string',
title: '标题字段', title: t('Title field'),
required: true, required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Select', 'x-component': 'Select',
@ -1115,7 +1113,7 @@ AddNew.CardItem = observer((props: any) => {
), ),
}, },
start: { start: {
title: '开始日期字段', title: t('Start date field'),
required: true, required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Select', 'x-component': 'Select',
@ -1131,7 +1129,7 @@ AddNew.CardItem = observer((props: any) => {
}), }),
}, },
end: { end: {
title: '结束日期字段', title: t('End date field'),
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Select', 'x-component': 'Select',
enum: item?.generalFields enum: item?.generalFields
@ -1186,7 +1184,7 @@ AddNew.CardItem = observer((props: any) => {
} }
}} }}
> >
{item.title} {compile(item.title)}
</Menu.Item> </Menu.Item>
))} ))}
</Menu.ItemGroup> </Menu.ItemGroup>
@ -1199,7 +1197,7 @@ AddNew.CardItem = observer((props: any) => {
{[ {[
{ {
key: 'Kanban', key: 'Kanban',
title: '看板', title: t('Kanban'),
icon: 'CreditCardOutlined', icon: 'CreditCardOutlined',
// disabled: true, // disabled: true,
}, },
@ -1213,15 +1211,15 @@ AddNew.CardItem = observer((props: any) => {
<Menu.ItemGroup <Menu.ItemGroup
className={'display-fields'} className={'display-fields'}
key={`${view.key}-select`} key={`${view.key}-select`}
title={'选择数据源'} title={t('Select data source')}
> >
{collections?.map((item) => ( {collections?.map((item) => (
<Menu.SubMenu <Menu.SubMenu
// style={{ minWidth: 150 }} // style={{ minWidth: 150 }}
key={`collection.${item.name}.${view.key}`} key={`collection.${item.name}.${view.key}`}
title={item.title} title={compile(item.title)}
> >
<Menu.ItemGroup title={'选择分组字段'}> <Menu.ItemGroup title={t('Select group field')}>
{item?.generalFields {item?.generalFields
?.filter((item) => { ?.filter((item) => {
return item?.uiSchema?.enum; return item?.uiSchema?.enum;
@ -1271,7 +1269,7 @@ AddNew.CardItem = observer((props: any) => {
} }
}} }}
> >
{field?.uiSchema?.title} {compile(field?.uiSchema?.title)}
</Menu.Item> </Menu.Item>
); );
})} })}
@ -1293,58 +1291,58 @@ AddNew.CardItem = observer((props: any) => {
))} ))}
</Menu.ItemGroup> </Menu.ItemGroup>
<Menu.Divider /> <Menu.Divider />
<Menu.ItemGroup title={'多媒体区块'}> <Menu.ItemGroup title={t('Media')}>
<Menu.Item <Menu.Item
key={'Markdown.Void'} key={'Markdown.Void'}
icon={<IconPicker type={'FileMarkdownOutlined'} />} icon={<IconPicker type={'FileMarkdownOutlined'} />}
> >
Markdown {t('Markdown')}
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
disabled disabled
key={'Wysiwyg.Void'} key={'Wysiwyg.Void'}
icon={<IconPicker type={'FileTextOutlined'} />} icon={<IconPicker type={'FileTextOutlined'} />}
> >
{t('Wysiwyg')}
</Menu.Item> </Menu.Item>
</Menu.ItemGroup> </Menu.ItemGroup>
<Menu.Divider /> <Menu.Divider />
<Menu.ItemGroup title={'图表区块'}> <Menu.ItemGroup title={t('Charts')}>
<Menu.Item <Menu.Item
key={'Chart.Column'} key={'Chart.Column'}
icon={<IconPicker type={'BarChartOutlined'} />} icon={<IconPicker type={'BarChartOutlined'} />}
> >
{t('Column chart')}
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
key={'Chart.Bar'} key={'Chart.Bar'}
icon={<IconPicker type={'BarChartOutlined'} />} icon={<IconPicker type={'BarChartOutlined'} />}
> >
{t('Bar chart')}
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
disabled disabled
key={'Chart.Line'} key={'Chart.Line'}
icon={<IconPicker type={'LineChartOutlined'} />} icon={<IconPicker type={'LineChartOutlined'} />}
> >
线 {t('Line chart')}
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
disabled disabled
key={'Chart.Pie'} key={'Chart.Pie'}
icon={<IconPicker type={'PieChartOutlined'} />} icon={<IconPicker type={'PieChartOutlined'} />}
> >
{t('Pie chart')}
</Menu.Item> </Menu.Item>
</Menu.ItemGroup> </Menu.ItemGroup>
<Menu.Divider /> <Menu.Divider />
<Menu.SubMenu key={'Ref'} icon={<LinkOutlined />} title={'引用模板'}> <Menu.SubMenu key={'Ref'} icon={<LinkOutlined />} title={t('Templates')}>
<Menu.ItemGroup key={'form-select'} title={'选择模板'}> <Menu.ItemGroup key={'form-select'} title={t('Select template')}>
<Menu.Item key={'Ref.ActionLogs'}></Menu.Item> <Menu.Item key={'Ref.ActionLogs'}>{t('Action logs')}</Menu.Item>
</Menu.ItemGroup> </Menu.ItemGroup>
<Menu.Divider></Menu.Divider> <Menu.Divider></Menu.Divider>
<Menu.Item disabled key={'addNewRef'}> <Menu.Item disabled key={'addNewRef'}>
{t('Create template')}
</Menu.Item> </Menu.Item>
</Menu.SubMenu> </Menu.SubMenu>
</Menu> </Menu>
@ -1358,7 +1356,7 @@ AddNew.CardItem = observer((props: any) => {
className={'designable-btn designable-btn-dash'} className={'designable-btn designable-btn-dash'}
icon={<PlusOutlined />} icon={<PlusOutlined />}
> >
{t('Add block')}
</Button> </Button>
)} )}
</Dropdown> </Dropdown>
@ -1375,6 +1373,7 @@ AddNew.FormItem = observer((props: any) => {
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const displayed = useDisplayedMapContext(); const displayed = useDisplayedMapContext();
const { createSchema, removeSchema } = useClient(); const { createSchema, removeSchema } = useClient();
const { t } = useTranslation();
return ( return (
<Dropdown <Dropdown
@ -1386,7 +1385,10 @@ AddNew.FormItem = observer((props: any) => {
}} }}
overlay={ overlay={
<Menu> <Menu>
<Menu.ItemGroup className={'display-fields'} title={`显示字段`}> <Menu.ItemGroup
className={'display-fields'}
title={t('Display fields')}
>
{fields?.map((field) => ( {fields?.map((field) => (
<SwitchMenuItem <SwitchMenuItem
key={field.key} key={field.key}
@ -1427,7 +1429,7 @@ AddNew.FormItem = observer((props: any) => {
'x-component-props': { 'x-component-props': {
useOkAction: '{{ Select.useOkAction }}', useOkAction: '{{ Select.useOkAction }}',
}, },
title: '关联数据', title: "{{t('Select record')}}",
properties: { properties: {
table: { table: {
type: 'array', type: 'array',
@ -1461,7 +1463,7 @@ AddNew.FormItem = observer((props: any) => {
properties: { properties: {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '筛选', title: "{{t('Filter')}}",
'x-decorator': 'AddNew.Displayed', 'x-decorator': 'AddNew.Displayed',
'x-decorator-props': { 'x-decorator-props': {
displayName: 'filter', displayName: 'filter',
@ -1486,7 +1488,7 @@ AddNew.FormItem = observer((props: any) => {
properties: { properties: {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '查看数据', title: "{{ t('View record') }}",
'x-component': 'Action.Drawer', 'x-component': 'Action.Drawer',
'x-component-props': { 'x-component-props': {
bodyStyle: { bodyStyle: {
@ -1501,7 +1503,7 @@ AddNew.FormItem = observer((props: any) => {
properties: { properties: {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '详情', title: "{{t('Details')}}",
'x-designable-bar': 'x-designable-bar':
'Tabs.TabPane.DesignableBar', 'Tabs.TabPane.DesignableBar',
'x-component': 'Tabs.TabPane', 'x-component': 'Tabs.TabPane',
@ -1551,7 +1553,7 @@ AddNew.FormItem = observer((props: any) => {
disabled disabled
popupClassName={'add-new-fields-popup'} popupClassName={'add-new-fields-popup'}
className={'sub-menu-add-new-fields'} className={'sub-menu-add-new-fields'}
title={'添加字段'} title={t('Add field')}
> >
{options.map( {options.map(
(option) => (option) =>
@ -1563,16 +1565,19 @@ AddNew.FormItem = observer((props: any) => {
key={item.name} key={item.name}
onClick={async () => { onClick={async () => {
setVisible(false); setVisible(false);
const values = await FormDialog(`添加字段`, () => { const values = await FormDialog(
return ( t('Add field'),
<FormLayout layout={'vertical'}> () => {
<SchemaField return (
scope={{ loadCollections }} <FormLayout layout={'vertical'}>
schema={item} <SchemaField
/> scope={{ loadCollections }}
</FormLayout> schema={item}
); />
}).open({ </FormLayout>
);
},
).open({
initialValues: { initialValues: {
interface: item.name, interface: item.name,
...item.default, ...item.default,
@ -1618,7 +1623,9 @@ AddNew.FormItem = observer((props: any) => {
onClick={async () => { onClick={async () => {
let data: ISchema = { let data: ISchema = {
type: 'void', type: 'void',
default: '这是一段演示文字,**支持使用 Markdown 语法**', default: t(
'This is a demo text, **supports Markdown syntax**.',
),
'x-designable-bar': 'Markdown.Void.DesignableBar', 'x-designable-bar': 'Markdown.Void.DesignableBar',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-read-pretty': true, 'x-read-pretty': true,
@ -1649,7 +1656,7 @@ AddNew.FormItem = observer((props: any) => {
setVisible(false); setVisible(false);
}} }}
> >
{t('Add text')}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }
@ -1662,7 +1669,7 @@ AddNew.FormItem = observer((props: any) => {
className={'designable-btn designable-btn-dash'} className={'designable-btn designable-btn-dash'}
icon={<SettingOutlined />} icon={<SettingOutlined />}
> >
{t('Configure fields')}
</Button> </Button>
)} )}
</Dropdown> </Dropdown>
@ -1679,7 +1686,7 @@ AddNew.PaneItem = observer((props: any) => {
console.log('AddNew.PaneItem.useResource', useResource); console.log('AddNew.PaneItem.useResource', useResource);
const { collection, fields } = useCollectionContext(); const { collection, fields } = useCollectionContext();
const { createSchema } = useClient(); const { createSchema } = useClient();
const { t } = useTranslation();
return ( return (
<Dropdown <Dropdown
trigger={['hover']} trigger={['hover']}
@ -1691,7 +1698,7 @@ AddNew.PaneItem = observer((props: any) => {
// placement={'bottomCenter'} // placement={'bottomCenter'}
overlay={ overlay={
<Menu> <Menu>
<Menu.ItemGroup title={'数据区块'}> <Menu.ItemGroup title={t('Data blocks')}>
<Menu.Item <Menu.Item
icon={<IconPicker type={'FileOutlined'} />} icon={<IconPicker type={'FileOutlined'} />}
onClick={async () => { onClick={async () => {
@ -1743,7 +1750,7 @@ AddNew.PaneItem = observer((props: any) => {
}} }}
style={{ minWidth: 150 }} style={{ minWidth: 150 }}
> >
{t('Details')}
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
icon={<IconPicker type={'FormOutlined'} />} icon={<IconPicker type={'FormOutlined'} />}
@ -1789,10 +1796,10 @@ AddNew.PaneItem = observer((props: any) => {
setVisible(false); setVisible(false);
}} }}
> >
{t('Form')}
</Menu.Item> </Menu.Item>
</Menu.ItemGroup> </Menu.ItemGroup>
<Menu.ItemGroup title={'相关数据区块'}> <Menu.ItemGroup title={t('Relationship blocks')}>
<Menu.Item <Menu.Item
style={{ minWidth: 150 }} style={{ minWidth: 150 }}
icon={<IconPicker type={'HistoryOutlined'} />} icon={<IconPicker type={'HistoryOutlined'} />}
@ -1819,7 +1826,7 @@ AddNew.PaneItem = observer((props: any) => {
setVisible(false); setVisible(false);
}} }}
> >
{t('Action logs')}
</Menu.Item> </Menu.Item>
{fields {fields
?.filter((f) => f.interface === 'linkTo') ?.filter((f) => f.interface === 'linkTo')
@ -1872,14 +1879,16 @@ AddNew.PaneItem = observer((props: any) => {
); );
})} })}
</Menu.ItemGroup> </Menu.ItemGroup>
<Menu.ItemGroup title={'多媒体区块'}> <Menu.ItemGroup title={t('Media')}>
<Menu.Item <Menu.Item
icon={<IconPicker type={'FileMarkdownOutlined'} />} icon={<IconPicker type={'FileMarkdownOutlined'} />}
onClick={async () => { onClick={async () => {
let data: ISchema = { let data: ISchema = {
key: uid(), key: uid(),
type: 'void', type: 'void',
default: '这是一段演示文字,**支持使用 Markdown 语法**', default: t(
'This is a demo text, **supports Markdown syntax**.',
),
'x-designable-bar': 'Markdown.Void.DesignableBar', 'x-designable-bar': 'Markdown.Void.DesignableBar',
'x-decorator': 'CardItem', 'x-decorator': 'CardItem',
'x-read-pretty': true, 'x-read-pretty': true,
@ -1906,7 +1915,7 @@ AddNew.PaneItem = observer((props: any) => {
setVisible(false); setVisible(false);
}} }}
> >
Markdown {t('Markdown')}
</Menu.Item> </Menu.Item>
</Menu.ItemGroup> </Menu.ItemGroup>
</Menu> </Menu>
@ -1920,7 +1929,7 @@ AddNew.PaneItem = observer((props: any) => {
type={'dashed'} type={'dashed'}
icon={<PlusOutlined />} icon={<PlusOutlined />}
> >
{t('Add block')}
</Button> </Button>
)} )}
</Dropdown> </Dropdown>

View File

@ -34,6 +34,7 @@ import { uid } from '@formily/shared';
import { getSchemaPath } from '../../components/schema-renderer'; import { getSchemaPath } from '../../components/schema-renderer';
import { useDisplayedMapContext } from '../../constate'; import { useDisplayedMapContext } from '../../constate';
import { DragHandle } from '../../components/Sortable'; import { DragHandle } from '../../components/Sortable';
import { useTranslation } from 'react-i18next';
export const ActionDesignableBar = (props: any) => { export const ActionDesignableBar = (props: any) => {
const { schema, remove, refresh, insertAfter } = useDesignable(); const { schema, remove, refresh, insertAfter } = useDesignable();
@ -41,6 +42,7 @@ export const ActionDesignableBar = (props: any) => {
const isPopup = Object.keys(schema.properties || {}).length > 0; const isPopup = Object.keys(schema.properties || {}).length > 0;
const displayed = useDisplayedMapContext(); const displayed = useDisplayedMapContext();
const field = useField(); const field = useField();
const { t } = useTranslation();
return ( return (
<div className={cls('designable-bar', { active: visible })}> <div className={cls('designable-bar', { active: visible })}>
<span <span
@ -71,7 +73,7 @@ export const ActionDesignableBar = (props: any) => {
setVisible(false); setVisible(false);
}} }}
> >
{t('Hide')}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }

View File

@ -18,6 +18,7 @@ import SwitchMenuItem from '../../components/SwitchMenuItem';
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
import { SettingOutlined } from '@ant-design/icons'; import { SettingOutlined } from '@ant-design/icons';
import { Droppable, SortableItem } from '../../components/Sortable'; import { Droppable, SortableItem } from '../../components/Sortable';
import { useTranslation } from 'react-i18next';
export const ActionBar = observer((props: any) => { export const ActionBar = observer((props: any) => {
const { align = 'top' } = props; const { align = 'top' } = props;
@ -114,7 +115,7 @@ function generateActionSchema(type) {
const actions: { [key: string]: ISchema } = { const actions: { [key: string]: ISchema } = {
today: { today: {
type: 'void', type: 'void',
title: '今天', title: '{{t("Today")}}',
'x-designable-bar': 'Calendar.ActionDesignableBar', 'x-designable-bar': 'Calendar.ActionDesignableBar',
'x-component': 'Calendar.Today', 'x-component': 'Calendar.Today',
'x-align': 'left', 'x-align': 'left',
@ -125,7 +126,7 @@ function generateActionSchema(type) {
}, },
nav: { nav: {
type: 'void', type: 'void',
title: '翻页', title: '{{t("Navigate")}}',
'x-designable-bar': 'Calendar.ActionDesignableBar', 'x-designable-bar': 'Calendar.ActionDesignableBar',
'x-component': 'Calendar.Nav', 'x-component': 'Calendar.Nav',
'x-align': 'left', 'x-align': 'left',
@ -136,7 +137,7 @@ function generateActionSchema(type) {
}, },
title: { title: {
type: 'void', type: 'void',
title: '标题', title: '{{t("Title")}}',
'x-designable-bar': 'Calendar.ActionDesignableBar', 'x-designable-bar': 'Calendar.ActionDesignableBar',
'x-component': 'Calendar.Title', 'x-component': 'Calendar.Title',
'x-align': 'left', 'x-align': 'left',
@ -147,7 +148,7 @@ function generateActionSchema(type) {
}, },
viewSelect: { viewSelect: {
type: 'void', type: 'void',
title: '视图切换', title: '{{t("Select view")}}',
'x-designable-bar': 'Calendar.ActionDesignableBar', 'x-designable-bar': 'Calendar.ActionDesignableBar',
'x-component': 'Calendar.ViewSelect', 'x-component': 'Calendar.ViewSelect',
'x-align': 'right', 'x-align': 'right',
@ -158,7 +159,7 @@ function generateActionSchema(type) {
}, },
filter: { filter: {
type: 'void', type: 'void',
title: '过滤', title: '{{t("Filter")}}',
'x-align': 'right', 'x-align': 'right',
'x-designable-bar': 'Calendar.ActionDesignableBar', 'x-designable-bar': 'Calendar.ActionDesignableBar',
'x-component': 'Calendar.Filter', 'x-component': 'Calendar.Filter',
@ -169,7 +170,7 @@ function generateActionSchema(type) {
}, },
create: { create: {
type: 'void', type: 'void',
title: '添加', title: '{{t("Add new")}}',
'x-align': 'right', 'x-align': 'right',
'x-designable-bar': 'Calendar.ActionDesignableBar', 'x-designable-bar': 'Calendar.ActionDesignableBar',
'x-component': 'Action', 'x-component': 'Action',
@ -184,7 +185,7 @@ function generateActionSchema(type) {
properties: { properties: {
modal: { modal: {
type: 'void', type: 'void',
title: '添加数据', title: '{{t("Add record")}}',
'x-decorator': 'Form', 'x-decorator': 'Form',
'x-component': 'Action.Drawer', 'x-component': 'Action.Drawer',
'x-component-props': { 'x-component-props': {
@ -207,6 +208,7 @@ function generateActionSchema(type) {
} }
function AddActionButton() { function AddActionButton() {
const { t } = useTranslation();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const displayed = useDisplayedMapContext(); const displayed = useDisplayedMapContext();
const { appendChild, remove } = useDesignable(); const { appendChild, remove } = useDesignable();
@ -223,14 +225,14 @@ function AddActionButton() {
onVisibleChange={setVisible} onVisibleChange={setVisible}
overlay={ overlay={
<Menu> <Menu>
<Menu.ItemGroup title={'启用操作'}> <Menu.ItemGroup title={t('Enable actions')}>
{[ {[
{ title: '今天', name: 'today' }, { title: t('Today'), name: 'today' },
{ title: '翻页', name: 'nav' }, { title: t('Navigate'), name: 'nav' },
{ title: '标题', name: 'title' }, { title: t('Title'), name: 'title' },
{ title: '视图切换', name: 'viewSelect' }, { title: t('Select view'), name: 'viewSelect' },
{ title: '筛选', name: 'filter' }, { title: t('Filter'), name: 'filter' },
{ title: '添加', name: 'create' }, { title: t('Add new'), name: 'create' },
].map((item) => ( ].map((item) => (
<SwitchMenuItem <SwitchMenuItem
key={item.name} key={item.name}
@ -253,10 +255,10 @@ function AddActionButton() {
))} ))}
</Menu.ItemGroup> </Menu.ItemGroup>
<Menu.Divider /> <Menu.Divider />
<Menu.SubMenu disabled title={'自定义'}> <Menu.SubMenu disabled title={t('Customize')}>
<Menu.Item style={{ minWidth: 120 }}></Menu.Item> <Menu.Item style={{ minWidth: 120 }}>{t('Function')}</Menu.Item>
<Menu.Item></Menu.Item> <Menu.Item>{t('Popup form')}</Menu.Item>
<Menu.Item></Menu.Item> <Menu.Item>{t('Flexible popup')}</Menu.Item>
</Menu.SubMenu> </Menu.SubMenu>
</Menu> </Menu>
} }
@ -267,7 +269,7 @@ function AddActionButton() {
type={'dashed'} type={'dashed'}
icon={<SettingOutlined />} icon={<SettingOutlined />}
> >
{t('Configure actions')}
</Button> </Button>
</Dropdown> </Dropdown>
); );

View File

@ -43,8 +43,12 @@ import { fieldsToFilterColumns } from './';
import { set } from 'lodash'; import { set } from 'lodash';
import { DragHandle } from '../../components/Sortable'; import { DragHandle } from '../../components/Sortable';
import { DeleteOutlined, FilterOutlined } from '@ant-design/icons'; import { DeleteOutlined, FilterOutlined } from '@ant-design/icons';
import { useTranslation } from 'react-i18next';
import { useCompile } from '../../hooks/useCompile';
export const DesignableBar = observer((props) => { export const DesignableBar = observer((props) => {
const { t } = useTranslation();
const compile = useCompile();
const field = useField(); const field = useField();
const { designable, schema, refresh, deepRemove } = useDesignable(); const { designable, schema, refresh, deepRemove } = useDesignable();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
@ -57,7 +61,7 @@ export const DesignableBar = observer((props) => {
return ( return (
<div className={cls('designable-bar', { active: visible })}> <div className={cls('designable-bar', { active: visible })}>
<div className={'designable-info'}> <div className={'designable-info'}>
{collection?.title || collection?.name} {compile(collection?.title || collection?.name)}
</div> </div>
<span <span
onClick={(e) => { onClick={(e) => {
@ -80,7 +84,7 @@ export const DesignableBar = observer((props) => {
<div <div
style={{ display: 'flex', justifyContent: 'space-between' }} style={{ display: 'flex', justifyContent: 'space-between' }}
> >
{t('Title field')}
<Select <Select
bordered={false} bordered={false}
size={'small'} size={'small'}
@ -101,7 +105,7 @@ export const DesignableBar = observer((props) => {
}} }}
options={fields?.map((field) => { options={fields?.map((field) => {
return { return {
label: field?.uiSchema.title, label: compile(field?.uiSchema.title),
value: field?.name, value: field?.name,
}; };
})} })}
@ -112,7 +116,7 @@ export const DesignableBar = observer((props) => {
<div <div
style={{ display: 'flex', justifyContent: 'space-between' }} style={{ display: 'flex', justifyContent: 'space-between' }}
> >
{t('Start date field')}
<Select <Select
bordered={false} bordered={false}
size={'small'} size={'small'}
@ -135,7 +139,7 @@ export const DesignableBar = observer((props) => {
?.filter((field) => field.dataType === 'date') ?.filter((field) => field.dataType === 'date')
?.map((field) => { ?.map((field) => {
return { return {
label: field?.uiSchema.title, label: compile(field?.uiSchema.title),
value: field?.name, value: field?.name,
}; };
})} })}
@ -146,7 +150,7 @@ export const DesignableBar = observer((props) => {
<div <div
style={{ display: 'flex', justifyContent: 'space-between' }} style={{ display: 'flex', justifyContent: 'space-between' }}
> >
{t('End date field')}
<Select <Select
bordered={false} bordered={false}
size={'small'} size={'small'}
@ -168,7 +172,7 @@ export const DesignableBar = observer((props) => {
?.filter((field) => field.dataType === 'date') ?.filter((field) => field.dataType === 'date')
?.map((field) => { ?.map((field) => {
return { return {
label: field?.uiSchema.title, label: compile(field?.uiSchema.title),
value: field?.name, value: field?.name,
}; };
})} })}
@ -180,7 +184,7 @@ export const DesignableBar = observer((props) => {
icon={<FilterOutlined />} icon={<FilterOutlined />}
onClick={async () => { onClick={async () => {
const { defaultFilter } = await FormDialog( const { defaultFilter } = await FormDialog(
'设置数据范围', t('Set the data scope'),
() => { () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
@ -211,7 +215,7 @@ export const DesignableBar = observer((props) => {
await updateSchema(schema); await updateSchema(schema);
}} }}
> >
{t('Set the data scope')}
</Menu.Item> </Menu.Item>
<Menu.Divider /> <Menu.Divider />
<Menu.Item <Menu.Item
@ -220,8 +224,8 @@ export const DesignableBar = observer((props) => {
onClick={async () => { onClick={async () => {
setVisible(false); setVisible(false);
Modal.confirm({ Modal.confirm({
title: '删除区块', title: t('Delete block'),
content: '删除后无法恢复,确定要删除吗?', content: t('Are you sure you want to delete it?'),
onOk: async () => { onOk: async () => {
const removed = deepRemove(); const removed = deepRemove();
// console.log({ removed }) // console.log({ removed })
@ -231,7 +235,7 @@ export const DesignableBar = observer((props) => {
}); });
}} }}
> >
{t('Delete')}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }

View File

@ -40,7 +40,8 @@ import { SchemaField, useDesignable } from '../../components/schema-renderer';
import { ActionBar } from './ActionBar'; import { ActionBar } from './ActionBar';
import { ActionDesignableBar } from './Action'; import { ActionDesignableBar } from './Action';
import { createForm } from '@formily/core'; import { createForm } from '@formily/core';
import { flatten, get } from 'lodash'; import { get } from 'lodash';
import flatten from 'flat';
import { import {
CollectionProvider, CollectionProvider,
useCollectionContext, useCollectionContext,
@ -57,11 +58,12 @@ import { interfaces } from '../database-field/interfaces';
import cls from 'classnames'; import cls from 'classnames';
import { Resource } from '../../resource'; import { Resource } from '../../resource';
import { useRequest } from 'ahooks'; import { useRequest } from 'ahooks';
import { useTranslation } from 'react-i18next';
import { useCompile } from '../../hooks/useCompile';
import { i18n } from '../../i18n';
const localizer = momentLocalizer(moment); const localizer = momentLocalizer(moment);
moment.locale('zh-CN');
const allViews = Object.keys(Views).map((k) => Views[k]); const allViews = Object.keys(Views).map((k) => Views[k]);
interface ToolbarProps { interface ToolbarProps {
@ -113,17 +115,17 @@ const messages: any = {
<RightOutlined /> <RightOutlined />
</div> </div>
), ),
today: '今天', today: i18n.t('Today'),
month: '月', month: i18n.t('Month'),
week: '周', week: i18n.t('Week'),
work_week: '工作日', work_week: i18n.t('Work week'),
day: '天', day: i18n.t('Day'),
agenda: '列表', agenda: i18n.t('Agenda'),
date: '日期', date: i18n.t('Date'),
time: '时间', time: i18n.t('Time'),
event: '事件', event: i18n.t('Event'),
noEventsInRange: '无', noEventsInRange: i18n.t('None'),
showMore: (count) => `还有 ${count}`, showMore: (count) => i18n.t('{{count}} more items', { count }),
}; };
const useCalendar = () => { const useCalendar = () => {
@ -134,14 +136,15 @@ const toEvents = (data: any[], fieldNames: any) => {
return data?.map((item) => { return data?.map((item) => {
return { return {
id: get(item, fieldNames.id || 'id'), id: get(item, fieldNames.id || 'id'),
title: get(item, fieldNames.title) || '无标题', title: get(item, fieldNames.title) || i18n.t('Untitle'),
start: get(item, fieldNames.start), start: new Date(get(item, fieldNames.start)),
end: get(item, fieldNames.end || fieldNames.start), end: new Date(get(item, fieldNames.end || fieldNames.start)),
}; };
}); });
}; };
export const Calendar: any = observer((props: any) => { export const Calendar: any = observer((props: any) => {
const { t } = useTranslation();
const field = useField<Formily.Core.Models.ArrayField>(); const field = useField<Formily.Core.Models.ArrayField>();
const { collectionName, fieldNames = {} } = props; const { collectionName, fieldNames = {} } = props;
console.log('Calendar', props); console.log('Calendar', props);
@ -167,7 +170,7 @@ export const Calendar: any = observer((props: any) => {
formatResult: (data) => data?.data, formatResult: (data) => data?.data,
onSuccess(data) { onSuccess(data) {
const events = toEvents(data, fieldNames) || []; const events = toEvents(data, fieldNames) || [];
// console.log('events', events); console.log('events', events);
field.setValue(events); field.setValue(events);
}, },
// refreshDeps: [props.fieldNames], // refreshDeps: [props.fieldNames],
@ -192,7 +195,7 @@ export const Calendar: any = observer((props: any) => {
onClose={() => { onClose={() => {
setVisible(false); setVisible(false);
}} }}
title={'查看数据'} title={t('View record')}
bodyStyle={{ bodyStyle={{
background: '#f0f2f5', background: '#f0f2f5',
paddingTop: 0, paddingTop: 0,
@ -232,18 +235,18 @@ export const Calendar: any = observer((props: any) => {
console.log('onSelectEvent'); console.log('onSelectEvent');
}} }}
formats={{ formats={{
monthHeaderFormat: 'Y年M月', monthHeaderFormat: 'Y-M',
agendaDateFormat: 'M月DD日', agendaDateFormat: 'M-DD',
dayHeaderFormat: 'Y年M月DD日', dayHeaderFormat: 'Y-M-DD',
dayRangeHeaderFormat: ({ start, end }, culture, local) => { dayRangeHeaderFormat: ({ start, end }, culture, local) => {
if (dates.eq(start, end, 'month')) { if (dates.eq(start, end, 'month')) {
return local.format(start, 'Y年M月', culture); return local.format(start, 'Y-M', culture);
} }
return `${local.format( return `${local.format(
start, start,
'Y年M月', 'Y-M',
culture, culture,
)} - ${local.format(end, 'Y年M月', culture)}`; )} - ${local.format(end, 'Y-M', culture)}`;
}, },
}} }}
defaultDate={new Date()} defaultDate={new Date()}
@ -316,13 +319,14 @@ Calendar.Title = observer(() => {
Calendar.Today = observer((props) => { Calendar.Today = observer((props) => {
const { DesignableBar } = useDesignable(); const { DesignableBar } = useDesignable();
const { onNavigate } = useContext(ToolbarContext); const { onNavigate } = useContext(ToolbarContext);
const { t } = useTranslation();
return ( return (
<Button <Button
onClick={() => { onClick={() => {
onNavigate(navigate.TODAY); onNavigate(navigate.TODAY);
}} }}
> >
{t('Today')}
<DesignableBar /> <DesignableBar />
</Button> </Button>
); );
@ -398,13 +402,14 @@ export const fieldsToFilterColumns = (fields: any[], options: any = {}) => {
Calendar.Filter = observer((props: any) => { Calendar.Filter = observer((props: any) => {
const { service } = useCalendar(); const { service } = useCalendar();
const { t } = useTranslation();
const compile = useCompile();
const { fieldNames = [] } = props; const { fieldNames = [] } = props;
const { schema, DesignableBar } = useDesignable(); const { schema, DesignableBar } = useDesignable();
const form = useMemo(() => createForm(), []); const form = useMemo(() => createForm(), []);
const { fields = [] } = useCollectionContext(); const { fields = [] } = useCollectionContext();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const obj = flatten(form.values.filter || {}); const obj = flatten(form.values.filter || {});
console.log('flatten', obj, Object.values(obj));
const count = Object.values(obj).filter((i) => const count = Object.values(obj).filter((i) =>
Array.isArray(i) ? i.length : i, Array.isArray(i) ? i.length : i,
).length; ).length;
@ -446,7 +451,7 @@ Calendar.Filter = observer((props: any) => {
// }); // });
}} }}
> >
{t('Submit')}
</Submit> </Submit>
</FormButtonGroup> </FormButtonGroup>
</FormProvider> </FormProvider>
@ -454,7 +459,7 @@ Calendar.Filter = observer((props: any) => {
} }
> >
<Button icon={<IconPicker type={icon} />}> <Button icon={<IconPicker type={icon} />}>
{count > 0 ? `${count} 个筛选项` : schema.title} {count > 0 ? t('{{count}} filter items', { count }) : compile(schema.title)}
<DesignableBar /> <DesignableBar />
</Button> </Button>
</Popover> </Popover>
@ -462,6 +467,7 @@ Calendar.Filter = observer((props: any) => {
}); });
Calendar.Filter.DesignableBar = () => { Calendar.Filter.DesignableBar = () => {
const { t } = useTranslation();
const { schema, remove, refresh, insertAfter } = useDesignable(); const { schema, remove, refresh, insertAfter } = useDesignable();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const displayed = useDisplayedMapContext(); const displayed = useDisplayedMapContext();
@ -489,7 +495,7 @@ Calendar.Filter.DesignableBar = () => {
}} }}
overlay={ overlay={
<Menu> <Menu>
<Menu.ItemGroup title={'可筛选字段'}> <Menu.ItemGroup title={t('Filterable fields')}>
{fields {fields
.filter((collectionField) => { .filter((collectionField) => {
const option = interfaces.get(collectionField.interface); const option = interfaces.get(collectionField.interface);
@ -523,7 +529,7 @@ Calendar.Filter.DesignableBar = () => {
<Menu.Item <Menu.Item
onClick={async (e) => { onClick={async (e) => {
setVisible(false); setVisible(false);
const values = await FormDialog('编辑按钮', () => { const values = await FormDialog(t('Edit button'), () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
<SchemaField <SchemaField
@ -532,14 +538,14 @@ Calendar.Filter.DesignableBar = () => {
properties: { properties: {
title: { title: {
type: 'string', type: 'string',
title: '按钮名称', title: t('Display name'),
required: true, required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
icon: { icon: {
type: 'string', type: 'string',
title: '按钮图标', title: t('Icon'),
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'IconPicker', 'x-component': 'IconPicker',
}, },
@ -562,7 +568,7 @@ Calendar.Filter.DesignableBar = () => {
refresh(); refresh();
}} }}
> >
{t('Edit button')}
</Menu.Item> </Menu.Item>
<Menu.Divider /> <Menu.Divider />
<Menu.Item <Menu.Item
@ -577,7 +583,7 @@ Calendar.Filter.DesignableBar = () => {
setVisible(false); setVisible(false);
}} }}
> >
{t('Hide')}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }

View File

@ -8,7 +8,7 @@ import {
useFieldSchema, useFieldSchema,
mapReadPretty, mapReadPretty,
} from '@formily/react'; } from '@formily/react';
import { Button, Dropdown, Input as AntdInput, Menu, Space } from 'antd'; import { Button, Dropdown, Input as AntdInput, Menu, Modal, Space } from 'antd';
import { LoadingOutlined, MenuOutlined, DragOutlined } from '@ant-design/icons'; import { LoadingOutlined, MenuOutlined, DragOutlined } from '@ant-design/icons';
import { useDesignable } from '../../components/schema-renderer'; import { useDesignable } from '../../components/schema-renderer';
import { useState } from 'react'; import { useState } from 'react';
@ -21,6 +21,7 @@ import { isGridRowOrCol } from '../grid';
import G2Plot from './G2Plot'; import G2Plot from './G2Plot';
import { Column, Line, Pie, Bar } from '@antv/g2plot'; import { Column, Line, Pie, Bar } from '@antv/g2plot';
import { DragHandle } from '../../components/Sortable'; import { DragHandle } from '../../components/Sortable';
import { useTranslation } from 'react-i18next';
export const Chart: any = {}; export const Chart: any = {};
@ -41,6 +42,7 @@ Chart.Bar = observer((props: any) => {
}); });
Chart.DesignableBar = observer((props) => { Chart.DesignableBar = observer((props) => {
const { t } = useTranslation();
const field = useField(); const field = useField();
const { designable, schema, refresh, deepRemove } = useDesignable(); const { designable, schema, refresh, deepRemove } = useDesignable();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
@ -73,20 +75,27 @@ Chart.DesignableBar = observer((props) => {
field.readPretty = false; field.readPretty = false;
setVisible(false); setVisible(false);
}} }}
disabled
> >
{t('Edit')}
</Menu.Item> </Menu.Item>
<Menu.Divider /> <Menu.Divider />
<Menu.Item <Menu.Item
key={'delete'} key={'delete'}
onClick={async () => { onClick={async () => {
const removed = deepRemove(); Modal.confirm({
// console.log({ removed }) title: t(`Delete block`),
const last = removed.pop(); content: t('Are you sure you want to delete it?'),
await removeSchema(last); onOk: async () => {
const removed = deepRemove();
// console.log({ removed })
const last = removed.pop();
await removeSchema(last);
},
});
}} }}
> >
{t('Delete')}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }

View File

@ -2,29 +2,31 @@ import React from 'react';
import { connect, mapProps, mapReadPretty } from '@formily/react'; import { connect, mapProps, mapReadPretty } from '@formily/react';
import { Tag, Select } from 'antd'; import { Tag, Select } from 'antd';
import { LoadingOutlined } from '@ant-design/icons'; import { LoadingOutlined } from '@ant-design/icons';
import { useCompile } from '../../hooks/useCompile';
const colors = { const colors = {
red: '薄暮', red: '{{t("Red")}}',
magenta: '法式洋红', magenta: '{{t("Magenta")}}',
volcano: '火山', volcano: '{{t("Volcano")}}',
orange: '日暮', orange: '{{t("Orange")}}',
gold: '金盏花', gold: '{{t("Gold")}}',
lime: '青柠', lime: '{{t("Lime")}}',
green: '极光绿', green: '{{t("Green")}}',
cyan: '明青', cyan: '{{t("Cyan")}}',
blue: '拂晓蓝', blue: '{{t("Blue")}}',
geekblue: '极客蓝', geekblue: '{{t("Geek blue")}}',
purple: '酱紫', purple: '{{t("Purple")}}',
default: '默认', default: '{{t("Default")}}',
}; };
export const ColorSelect = connect( export const ColorSelect = connect(
(props) => { (props) => {
const compile = useCompile();
return ( return (
<Select {...props}> <Select {...props}>
{Object.keys(colors).map((color) => ( {Object.keys(colors).map((color) => (
<Select.Option value={color}> <Select.Option value={color}>
<Tag color={color}>{colors[color]}</Tag> <Tag color={color}>{compile(colors[color] || colors.default)}</Tag>
</Select.Option> </Select.Option>
))} ))}
</Select> </Select>
@ -45,11 +47,12 @@ export const ColorSelect = connect(
}; };
}), }),
mapReadPretty((props) => { mapReadPretty((props) => {
const compile = useCompile();
const { value } = props; const { value } = props;
if (!colors[value]) { if (!colors[value]) {
return null; return null;
} }
return <Tag color={value}>{colors[value]}</Tag>; return <Tag color={value}>{compile(colors[value] || colors.default)}</Tag>;
}), }),
); );

View File

@ -6,7 +6,7 @@ export const attachment: FieldOptions = {
name: 'attachment', name: 'attachment',
type: 'object', type: 'object',
group: 'media', group: 'media',
title: '附件', title: '{{t("Attachment")}}',
isAssociation: true, isAssociation: true,
default: { default: {
dataType: 'belongsToMany', dataType: 'belongsToMany',
@ -41,7 +41,7 @@ export const attachment: FieldOptions = {
...defaultProps, ...defaultProps,
'uiSchema.x-component-props.multiple': { 'uiSchema.x-component-props.multiple': {
type: 'boolean', type: 'boolean',
'x-content': '允许上传多个文件', 'x-content': "{{t('Allow uploading multiple files')}}",
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Checkbox', 'x-component': 'Checkbox',
default: true, default: true,

View File

@ -6,7 +6,7 @@ export const checkbox: FieldOptions = {
type: 'object', type: 'object',
group: 'choices', group: 'choices',
order: 1, order: 1,
title: '勾选', title: '{{t("Checkbox")}}',
default: { default: {
dataType: 'boolean', dataType: 'boolean',
// name, // name,
@ -22,7 +22,7 @@ export const checkbox: FieldOptions = {
...defaultProps, ...defaultProps,
}, },
operations: [ operations: [
{ label: '', value: '$isTruly', selected: true, noValue: true }, { label: '{{t("Yes")}}', value: '$isTruly', selected: true, noValue: true },
{ label: '', value: '$isFalsy', noValue: true }, { label: '{{t("No")}}', value: '$isFalsy', noValue: true },
], ],
}; };

View File

@ -7,7 +7,7 @@ export const checkboxGroup: FieldOptions = {
type: 'object', type: 'object',
group: 'choices', group: 'choices',
order: 5, order: 5,
title: '复选框', title: '{{t("Checkbox group")}}',
default: { default: {
interface: 'checkboxGroup', interface: 'checkboxGroup',
dataType: 'json', dataType: 'json',

View File

@ -7,7 +7,7 @@ export const chinaRegion: FieldOptions = {
type: 'object', type: 'object',
group: 'choices', group: 'choices',
order: 7, order: 7,
title: '中国行政区划', title: '{{t("China region")}}',
isAssociation: true, isAssociation: true,
default: { default: {
dataType: 'belongsToMany', dataType: 'belongsToMany',
@ -60,22 +60,22 @@ export const chinaRegion: FieldOptions = {
type: 'number', type: 'number',
'x-component': 'Radio.Group', 'x-component': 'Radio.Group',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
title: '可选择的层级', title: '{{t("Select level")}}',
default: 3, default: 3,
enum: [ enum: [
{ value: 1, label: '' }, { value: 1, label: '{{t("Province")}}' },
{ value: 2, label: '' }, { value: 2, label: '{{t("City")}}' },
{ value: 3, label: '区/县' }, { value: 3, label: '{{t("Area")}}' },
{ value: 4, label: '乡镇/街道' }, { value: 4, label: '{{t("Street")}}' },
{ value: 5, label: '村/居委会' }, { value: 5, label: '{{t("Village")}}' },
], ],
}, },
'uiSchema.x-component-props.changeOnSelectLast': { 'uiSchema.x-component-props.changeOnSelectLast': {
type: 'boolean', type: 'boolean',
'x-component': 'Checkbox', 'x-component': 'Checkbox',
'x-content': '必须选到最后一级', 'x-content': '{{t("Must select to the last level")}}',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
}, },
}, },
operations: [{ label: '等于', value: 'code.in' }], operations: [{ label: '{{t("is")}}', value: 'code.in' }],
}; };

View File

@ -7,7 +7,7 @@ export const createdAt: FieldOptions = {
type: 'object', type: 'object',
group: 'systemInfo', group: 'systemInfo',
order: 1, order: 1,
title: '添加时间', title: '{{t("Created at")}}',
sortable: true, sortable: true,
default: { default: {
dataType: 'date', dataType: 'date',
@ -15,7 +15,7 @@ export const createdAt: FieldOptions = {
// name, // name,
uiSchema: { uiSchema: {
type: 'datetime', type: 'datetime',
title: '添加时间', title: '{{t("Created at")}}',
'x-component': 'DatePicker', 'x-component': 'DatePicker',
'x-component-props': {}, 'x-component-props': {},
'x-read-pretty': true, 'x-read-pretty': true,

View File

@ -7,7 +7,7 @@ export const createdBy: FieldOptions = {
type: 'object', type: 'object',
group: 'systemInfo', group: 'systemInfo',
order: 3, order: 3,
title: '添加人', title: '{{t("Created by")}}',
isAssociation: true, isAssociation: true,
default: { default: {
dataType: 'belongsTo', dataType: 'belongsTo',
@ -16,7 +16,7 @@ export const createdBy: FieldOptions = {
// name, // name,
uiSchema: { uiSchema: {
type: 'object', type: 'object',
title: '添加人', title: '{{t("Created by")}}',
'x-component': 'Select.Drawer', 'x-component': 'Select.Drawer',
'x-component-props': { 'x-component-props': {
fieldNames: { fieldNames: {

View File

@ -7,7 +7,7 @@ export const datetime: FieldOptions = {
type: 'object', type: 'object',
group: 'datetime', group: 'datetime',
order: 1, order: 1,
title: '日期', title: '{{t("Datetime")}}',
sortable: true, sortable: true,
default: { default: {
dataType: 'date', dataType: 'date',
@ -28,13 +28,13 @@ export const datetime: FieldOptions = {
...dateTimeProps, ...dateTimeProps,
}, },
operations: [ operations: [
{ label: '等于', value: '$dateOn', selected: true }, { label: "{{ t('is') }}", value: '$dateOn', selected: true },
{ label: '不等于', value: '$dateNotOn' }, { label: "{{ t('is not') }}", value: '$dateNotOn' },
{ label: '早于', value: '$dateBefore' }, { label: "{{ t('is before') }}", value: '$dateBefore' },
{ label: '晚于', value: '$dateAfter' }, { label: "{{ t('is after') }}", value: '$dateAfter' },
{ label: '不早于', value: '$dateNotBefore' }, { label: "{{ t('is on or after') }}", value: '$dateNotBefore' },
{ label: '不晚于', value: '$dateNotAfter' }, { label: "{{ t('is on or before') }}", value: '$dateNotAfter' },
{ label: '非空', value: '$notNull', noValue: true }, { label: "{{ t('is empty') }}", value: '$null', noValue: true },
{ label: '为空', value: '$null', noValue: true }, { label: "{{ t('is not empty') }}", value: '$notNull', noValue: true },
], ],
}; };

View File

@ -7,7 +7,7 @@ export const email: FieldOptions = {
type: 'object', type: 'object',
group: 'basic', group: 'basic',
order: 4, order: 4,
title: '电子邮箱', title: '{{t("Email")}}',
sortable: true, sortable: true,
default: { default: {
dataType: 'string', dataType: 'string',

View File

@ -6,7 +6,7 @@ export const icon: FieldOptions = {
type: 'object', type: 'object',
group: 'basic', group: 'basic',
order: 8, order: 8,
title: '图标', title: '{{t("Icon")}}',
default: { default: {
dataType: 'string', dataType: 'string',
// name, // name,

View File

@ -47,13 +47,13 @@ Object.keys(types).forEach((type) => {
registerField(schema.group || 'others', type, { order: 0, ...schema }); registerField(schema.group || 'others', type, { order: 0, ...schema });
}); });
registerGroupLabel('basic', '基本类型'); registerGroupLabel('basic', '{{t("Basic")}}');
registerGroupLabel('choices', '选择类型'); registerGroupLabel('choices', '{{t("Choices")}}');
registerGroupLabel('media', '多媒体类型'); registerGroupLabel('media', '{{t("Media")}}');
registerGroupLabel('datetime', '日期和时间'); registerGroupLabel('datetime', '{{t("Date & Time")}}');
registerGroupLabel('relation', '关系类型'); registerGroupLabel('relation', '{{t("Relation")}}');
registerGroupLabel('systemInfo', '系统信息'); registerGroupLabel('systemInfo', '{{t("System info")}}');
registerGroupLabel('others', '其他类型'); registerGroupLabel('others', '{{t("Others")}}');
export const options = Object.keys(groupLabels).map((groupName) => { export const options = Object.keys(groupLabels).map((groupName) => {
return { return {

View File

@ -7,7 +7,7 @@ export const linkTo: FieldOptions = {
type: 'object', type: 'object',
group: 'relation', group: 'relation',
order: 1, order: 1,
title: '关联字段', title: '{{t("Link to")}}',
isAssociation: true, isAssociation: true,
default: { default: {
dataType: 'belongsToMany', dataType: 'belongsToMany',
@ -44,7 +44,7 @@ export const linkTo: FieldOptions = {
...defaultProps, ...defaultProps,
target: { target: {
type: 'string', type: 'string',
title: '要关联的数据表', title: '{{t("Related collection")}}',
required: true, required: true,
'x-reactions': ['{{useAsyncDataSource(loadCollections)}}'], 'x-reactions': ['{{useAsyncDataSource(loadCollections)}}'],
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
@ -60,7 +60,7 @@ export const linkTo: FieldOptions = {
// }, // },
'uiSchema.x-component-props.multiple': { 'uiSchema.x-component-props.multiple': {
type: 'boolean', type: 'boolean',
'x-content': '允许关联多条记录', 'x-content': '{{t("Allow linking to multiple records")}}',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Checkbox', 'x-component': 'Checkbox',
}, },

View File

@ -6,7 +6,7 @@ export const multipleSelect: FieldOptions = {
type: 'object', type: 'object',
group: 'choices', group: 'choices',
order: 3, order: 3,
title: '下拉选择(多选)', title: '{{t("Multiple select")}}',
default: { default: {
dataType: 'json', dataType: 'json',
// name, // name,
@ -28,19 +28,27 @@ export const multipleSelect: FieldOptions = {
}, },
operations: [ operations: [
{ {
label: '等于', label: '{{t("is")}}',
value: '$match', value: '$match',
selected: true, selected: true,
schema: { 'x-component': 'Select' }, schema: { 'x-component': 'Select' },
}, },
{ {
label: '不等于', label: '{{t("is not")}}',
value: '$notMatch', value: '$notMatch',
schema: { 'x-component': 'Select' }, schema: { 'x-component': 'Select' },
}, },
{ label: '包含', value: '$anyOf', schema: { 'x-component': 'Select' } }, {
{ label: '不包含', value: '$noneOf', schema: { 'x-component': 'Select' } }, label: '{{t("contains")}}',
{ label: '非空', value: '$notNull', noValue: true }, value: '$anyOf',
{ label: '为空', value: '$null', noValue: true }, schema: { 'x-component': 'Select' },
},
{
label: '{{t("does not contain")}}',
value: '$noneOf',
schema: { 'x-component': 'Select' },
},
{ label: '{{t("is empty")}}', value: '$null', noValue: true },
{ label: '{{t("is not empty")}}', value: '$notNull', noValue: true },
], ],
}; };

View File

@ -6,7 +6,7 @@ export const number: FieldOptions = {
type: 'object', type: 'object',
group: 'basic', group: 'basic',
order: 5, order: 5,
title: '数字', title: '{{t("Number")}}',
sortable: true, sortable: true,
default: { default: {
dataType: 'float', dataType: 'float',
@ -27,7 +27,7 @@ export const number: FieldOptions = {
...defaultProps, ...defaultProps,
'uiSchema.x-component-props.step': { 'uiSchema.x-component-props.step': {
type: 'string', type: 'string',
title: '精度', title: '{{t("Precision")}}',
'x-component': 'Select', 'x-component': 'Select',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
default: '0', default: '0',
@ -42,14 +42,14 @@ export const number: FieldOptions = {
}, },
}, },
operations: [ operations: [
{ label: '等于', value: 'eq', selected: true }, { label: '{{t("=")}}', value: 'eq', selected: true },
{ label: '不等于', value: 'ne' }, { label: '{{t("≠")}}', value: 'ne' },
{ label: '大于', value: 'gt' }, { label: '{{t(">")}}', value: 'gt' },
{ label: '大于等于', value: 'gte' }, { label: '{{t("≥")}}', value: 'gte' },
{ label: '小于', value: 'lt' }, { label: '{{t("<")}}', value: 'lt' },
{ label: '小于等于', value: 'lte' }, { label: '{{t("≤")}}', value: 'lte' },
// {label: '介于', value: 'between'}, // {label: '介于', value: 'between'},
{ label: '非空', value: '$notNull', noValue: true }, { label: '{{t("is empty")}}', value: '$null', noValue: true },
{ label: '为空', value: '$null', noValue: true }, { label: '{{t("is not empty")}}', value: '$notNull', noValue: true },
], ],
}; };

View File

@ -6,7 +6,7 @@ export const password: FieldOptions = {
type: 'object', type: 'object',
group: 'basic', group: 'basic',
order: 7, order: 7,
title: '密码', title: '{{t("Password")}}',
default: { default: {
dataType: 'password', dataType: 'password',
// name, // name,

View File

@ -7,7 +7,7 @@ export const percent: FieldOptions = {
type: 'object', type: 'object',
group: 'basic', group: 'basic',
order: 6, order: 6,
title: '百分比', title: '{{t("Percent")}}',
sortable: true, sortable: true,
default: { default: {
dataType: 'float', dataType: 'float',
@ -29,7 +29,7 @@ export const percent: FieldOptions = {
...defaultProps, ...defaultProps,
'uiSchema.x-component-props.step': { 'uiSchema.x-component-props.step': {
type: 'string', type: 'string',
title: '精度', title: '{{t("Precision")}}',
'x-component': 'Select', 'x-component': 'Select',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
default: '0', default: '0',

View File

@ -7,7 +7,7 @@ export const phone: FieldOptions = {
type: 'object', type: 'object',
group: 'basic', group: 'basic',
order: 3, order: 3,
title: '手机号码', title: '{{t("Phone")}}',
sortable: true, sortable: true,
default: { default: {
dataType: 'string', dataType: 'string',

View File

@ -2,7 +2,7 @@ import { ISchema } from '@formily/react';
export const dataType: ISchema = { export const dataType: ISchema = {
type: 'string', type: 'string',
title: '数据类型', title: '{{t("Storage type")}}',
required: true, required: true,
'x-disabled': true, 'x-disabled': true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
@ -30,21 +30,21 @@ export const dataType: ISchema = {
export const dateTimeProps: { [key: string]: ISchema } = { export const dateTimeProps: { [key: string]: ISchema } = {
'uiSchema.x-component-props.dateFormat': { 'uiSchema.x-component-props.dateFormat': {
type: 'string', type: 'string',
title: '日期格式', title: '{{t("Date format")}}',
'x-component': 'Radio.Group', 'x-component': 'Radio.Group',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
default: 'YYYY-MM-DD', default: 'YYYY-MM-DD',
enum: [ enum: [
{ {
label: '年/月/日', label: '{{t("Year/Month/Day")}}',
value: 'YYYY/MM/DD', value: 'YYYY/MM/DD',
}, },
{ {
label: '年-月-日', label: '{{t("Year-Month-Day")}}',
value: 'YYYY-MM-DD', value: 'YYYY-MM-DD',
}, },
{ {
label: '日/月/年', label: '{{t("Day/Month/Year")}}',
value: 'DD/MM/YYYY', value: 'DD/MM/YYYY',
}, },
], ],
@ -53,7 +53,7 @@ export const dateTimeProps: { [key: string]: ISchema } = {
type: 'boolean', type: 'boolean',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Checkbox', 'x-component': 'Checkbox',
'x-content': '显示时间', 'x-content': '{{t("Show time")}}',
'x-reactions': [ 'x-reactions': [
`{{(field) => { `{{(field) => {
field.query('..[].timeFormat').take(f => { field.query('..[].timeFormat').take(f => {
@ -64,18 +64,18 @@ export const dateTimeProps: { [key: string]: ISchema } = {
}, },
'uiSchema.x-component-props.timeFormat': { 'uiSchema.x-component-props.timeFormat': {
type: 'string', type: 'string',
title: '时间格式', title: '{{t("Time format")}}',
'x-component': 'Radio.Group', 'x-component': 'Radio.Group',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
default: 'HH:mm:ss', default: 'HH:mm:ss',
enum: [ enum: [
{ {
label: '24小时制', label: '{{t("12 hour")}}',
value: 'HH:mm:ss', value: 'hh:mm:ss a',
}, },
{ {
label: '12小时制', label: '{{t("24 hour")}}',
value: 'hh:mm:ss a', value: 'HH:mm:ss',
}, },
], ],
}, },
@ -83,7 +83,7 @@ export const dateTimeProps: { [key: string]: ISchema } = {
export const dataSource: ISchema = { export const dataSource: ISchema = {
type: 'array', type: 'array',
title: '可选项', title: '{{t("Options")}}',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'ArrayTable', 'x-component': 'ArrayTable',
'x-component-props': { 'x-component-props': {
@ -107,7 +107,7 @@ export const dataSource: ISchema = {
column2: { column2: {
type: 'void', type: 'void',
'x-component': 'ArrayTable.Column', 'x-component': 'ArrayTable.Column',
'x-component-props': { title: '选项值' }, 'x-component-props': { title: '{{t("Option value")}}' },
'x-hidden': true, 'x-hidden': true,
properties: { properties: {
value: { value: {
@ -120,7 +120,7 @@ export const dataSource: ISchema = {
column3: { column3: {
type: 'void', type: 'void',
'x-component': 'ArrayTable.Column', 'x-component': 'ArrayTable.Column',
'x-component-props': { title: '选项' }, 'x-component-props': { title: '{{t("Option label")}}' },
properties: { properties: {
label: { label: {
type: 'string', type: 'string',
@ -132,7 +132,7 @@ export const dataSource: ISchema = {
column4: { column4: {
type: 'void', type: 'void',
'x-component': 'ArrayTable.Column', 'x-component': 'ArrayTable.Column',
'x-component-props': { title: '颜色' }, 'x-component-props': { title: '{{t("Color")}}' },
properties: { properties: {
color: { color: {
type: 'string', type: 'string',
@ -171,7 +171,7 @@ export const dataSource: ISchema = {
'x-component-props': { 'x-component-props': {
randomValue: true, randomValue: true,
}, },
title: '添加可选项', title: "{{t('Add option')}}",
}, },
}, },
}; };
@ -179,19 +179,20 @@ export const dataSource: ISchema = {
export const defaultProps = { export const defaultProps = {
'uiSchema.title': { 'uiSchema.title': {
type: 'string', type: 'string',
title: '字段名称', title: '{{t("Field display name")}}',
required: true, required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
name: { name: {
type: 'string', type: 'string',
title: '字段标识', title: '{{t("Field name")}}',
required: true, required: true,
// 'x-disabled': true, // 'x-disabled': true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
description: '随机生成,可修改。支持英文、数字和下划线,必须以英文字母开头', description:
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.')}}",
}, },
dataType, dataType,
// 'uiSchema.required': { // 'uiSchema.required': {

View File

@ -7,7 +7,7 @@ export const radioGroup: FieldOptions = {
type: 'object', type: 'object',
group: 'choices', group: 'choices',
order: 4, order: 4,
title: '单选框', title: '{{t("Radio group")}}',
default: { default: {
dataType: 'string', dataType: 'string',
// name, // name,

View File

@ -6,7 +6,7 @@ export const select: FieldOptions = {
type: 'object', type: 'object',
group: 'choices', group: 'choices',
order: 2, order: 2,
title: '下拉选择(单选)', title: '{{t("Single select")}}',
default: { default: {
dataType: 'string', dataType: 'string',
// name, // name,
@ -25,14 +25,18 @@ export const select: FieldOptions = {
}, },
operations: [ operations: [
{ {
label: '等于', label: '{{t("is")}}',
value: 'eq', value: 'eq',
selected: true, selected: true,
schema: { 'x-component': 'Select' }, schema: { 'x-component': 'Select' },
}, },
{ label: '不等于', value: 'ne', schema: { 'x-component': 'Select' } },
{ {
label: '包含', label: '{{t("is not")}}',
value: 'ne',
schema: { 'x-component': 'Select' },
},
{
label: '{{t("contains")}}',
value: 'in', value: 'in',
schema: { schema: {
'x-component': 'Select', 'x-component': 'Select',
@ -40,14 +44,14 @@ export const select: FieldOptions = {
}, },
}, },
{ {
label: '不包含', label: '{{t("does not contain")}}',
value: 'notIn', value: 'notIn',
schema: { schema: {
'x-component': 'Select', 'x-component': 'Select',
'x-component-props': { mode: 'tags' }, 'x-component-props': { mode: 'tags' },
}, },
}, },
{ label: '非空', value: '$notNull', noValue: true }, { label: '{{t("is empty")}}', value: '$null', noValue: true },
{ label: '为空', value: '$null', noValue: true }, { label: '{{t("is not empty")}}', value: '$notNull', noValue: true },
], ],
}; };

View File

@ -6,7 +6,7 @@ export const string: FieldOptions = {
type: 'object', type: 'object',
group: 'basic', group: 'basic',
order: 1, order: 1,
title: '单行文本', title: '{{t("Single line text")}}',
sortable: true, sortable: true,
default: { default: {
interface: 'string', interface: 'string',
@ -24,11 +24,11 @@ export const string: FieldOptions = {
...defaultProps, ...defaultProps,
}, },
operations: [ operations: [
{ label: '包含', value: '$includes', selected: true }, { label: '{{t("contains")}}', value: '$includes', selected: true },
{ label: '不包含', value: '$notIncludes' }, { label: '{{t("does not contain")}}', value: '$notIncludes' },
{ label: '等于', value: 'eq' }, { label: '{{t("is")}}', value: 'eq' },
{ label: '不等于', value: 'ne' }, { label: '{{t("is not")}}', value: 'ne' },
{ label: '非空', value: '$notNull', noValue: true }, { label: '{{t("is empty")}}', value: '$null', noValue: true },
{ label: '为空', value: '$null', noValue: true }, { label: '{{t("is not empty")}}', value: '$notNull', noValue: true },
], ],
}; };

View File

@ -7,7 +7,7 @@ export const subTable: FieldOptions = {
type: 'object', type: 'object',
group: 'relation', group: 'relation',
order: 2, order: 2,
title: '子表格', title: '{{t("Sub-table")}}',
isAssociation: true, isAssociation: true,
disabled: true, disabled: true,
default: { default: {
@ -35,7 +35,7 @@ export const subTable: FieldOptions = {
...defaultProps, ...defaultProps,
children: { children: {
type: 'array', type: 'array',
title: '子表格字段', title: '{{t("Sub-table fields")}}',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'DatabaseField', 'x-component': 'DatabaseField',
}, },

View File

@ -6,7 +6,7 @@ export const textarea: FieldOptions = {
type: 'object', type: 'object',
group: 'basic', group: 'basic',
order: 2, order: 2,
title: '多行文本', title: '{{t("Long text")}}',
default: { default: {
dataType: 'text', dataType: 'text',
// name, // name,

View File

@ -6,7 +6,7 @@ export const time: FieldOptions = {
type: 'object', type: 'object',
group: 'datetime', group: 'datetime',
order: 2, order: 2,
title: '时间', title: '{{t("Time")}}',
sortable: true, sortable: true,
default: { default: {
dataType: 'time', dataType: 'time',
@ -23,30 +23,30 @@ export const time: FieldOptions = {
...defaultProps, ...defaultProps,
'uiSchema.x-component-props.format': { 'uiSchema.x-component-props.format': {
type: 'string', type: 'string',
title: '时间格式', title: '{{t("Time format")}}',
'x-component': 'Radio.Group', 'x-component': 'Radio.Group',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
default: 'HH:mm:ss', default: 'HH:mm:ss',
enum: [ enum: [
{ {
label: '24小时制', label: '{{t("12 hour")}}',
value: 'HH:mm:ss', value: 'hh:mm:ss a',
}, },
{ {
label: '12小时制', label: '{{t("24 hour")}}',
value: 'hh:mm:ss a', value: 'HH:mm:ss',
}, },
], ],
}, },
}, },
operations: [ operations: [
{ label: '等于', value: 'eq', selected: true }, { label: '{{t("is")}}', value: 'eq', selected: true },
{ label: '不等于', value: 'neq' }, { label: '{{t("is not")}}', value: 'neq' },
{ label: '大于', value: 'gt' }, // { label: '大于', value: 'gt' },
{ label: '大于等于', value: 'gte' }, // { label: '大于等于', value: 'gte' },
{ label: '小于', value: 'lt' }, // { label: '小于', value: 'lt' },
{ label: '小于等于', value: 'lte' }, // { label: '小于等于', value: 'lte' },
{ label: '非空', value: '$notNull', noValue: true }, { label: '{{t("is empty")}}', value: '$null', noValue: true },
{ label: '为空', value: '$null', noValue: true }, { label: '{{t("is not empty")}}', value: '$notNull', noValue: true },
], ],
}; };

View File

@ -7,7 +7,7 @@ export const updatedAt: FieldOptions = {
type: 'object', type: 'object',
group: 'systemInfo', group: 'systemInfo',
order: 2, order: 2,
title: '最后更新时间', title: '{{t("Last updated at")}}',
sortable: true, sortable: true,
default: { default: {
dataType: 'date', dataType: 'date',
@ -15,7 +15,7 @@ export const updatedAt: FieldOptions = {
// name, // name,
uiSchema: { uiSchema: {
type: 'datetime', type: 'datetime',
title: '最后更新时间', title: '{{t("Last updated at")}}',
'x-component': 'DatePicker', 'x-component': 'DatePicker',
'x-component-props': {}, 'x-component-props': {},
'x-read-pretty': true, 'x-read-pretty': true,

View File

@ -6,7 +6,7 @@ export const updatedBy: FieldOptions = {
type: 'object', type: 'object',
group: 'systemInfo', group: 'systemInfo',
order: 4, order: 4,
title: '最后修改人', title: '{{t("Last updated by")}}',
isAssociation: true, isAssociation: true,
default: { default: {
dataType: 'belongsTo', dataType: 'belongsTo',
@ -15,7 +15,7 @@ export const updatedBy: FieldOptions = {
// name, // name,
uiSchema: { uiSchema: {
type: 'object', type: 'object',
title: '最后修改人', title: '{{t("Last updated by")}}',
'x-component': 'Select.Drawer', 'x-component': 'Select.Drawer',
'x-component-props': { 'x-component-props': {
fieldNames: { fieldNames: {

View File

@ -19,6 +19,7 @@ import {
} from '@formily/antd/esm/__builtins__'; } from '@formily/antd/esm/__builtins__';
import { FullscreenOutlined } from '@ant-design/icons'; import { FullscreenOutlined } from '@ant-design/icons';
import moment from 'moment'; import moment from 'moment';
import { useCompile } from '../../hooks/useCompile';
const PlaceholderContext = createContext<string>(''); const PlaceholderContext = createContext<string>('');
@ -31,11 +32,12 @@ const usePlaceholder = (value?: any) => {
const Input: React.FC<InputProps> = (props) => { const Input: React.FC<InputProps> = (props) => {
const prefixCls = usePrefixCls('description-input', props); const prefixCls = usePrefixCls('description-input', props);
const compile = useCompile();
return ( return (
<div className={cls(prefixCls, props.className)} style={props.style}> <div className={cls(prefixCls, props.className)} style={props.style}>
{props.addonBefore} {props.addonBefore}
{props.prefix} {props.prefix}
{usePlaceholder(props.value)} {compile(usePlaceholder(props.value))}
{props.suffix} {props.suffix}
{props.addonAfter} {props.addonAfter}
</div> </div>

View File

@ -24,6 +24,7 @@ import { SchemaField, SchemaRenderer } from '../../components/schema-renderer';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { createForm, onFormValuesChange } from '@formily/core'; import { createForm, onFormValuesChange } from '@formily/core';
import deepmerge from 'deepmerge'; import deepmerge from 'deepmerge';
import { Trans, useTranslation } from 'react-i18next';
const toValue = (value) => { const toValue = (value) => {
if (!value) { if (!value) {
@ -62,21 +63,23 @@ export function FilterGroup(props) {
</a> </a>
)} )}
<div style={{ marginBottom: 14 }}> <div style={{ marginBottom: 14 }}>
{' '} <Trans>
<Select {'Meet '}
style={{ width: 80 }} <Select
onChange={(logical) => { style={{ width: 80 }}
onChange && onChange={(logical) => {
onChange({ onChange &&
[logical]: value.list, onChange({
}); [logical]: value.list,
}} });
defaultValue={value.logical} }}
> defaultValue={value.logical}
<Select.Option value={'and'}></Select.Option> >
<Select.Option value={'or'}></Select.Option> <Select.Option value={'and'}>All</Select.Option>
</Select>{' '} <Select.Option value={'or'}>Any</Select.Option>
</Select>
{' conditions in the group'}
</Trans>
</div> </div>
<FilterList <FilterList
initialValue={value.list} initialValue={value.list}
@ -97,6 +100,8 @@ export function FilterGroup(props) {
export function FilterList(props) { export function FilterList(props) {
const { initialValue = [] } = props; const { initialValue = [] } = props;
const { t } = useTranslation();
const [map, { set, setAll, remove, reset, get }] = useMap<string, any>( const [map, { set, setAll, remove, reset, get }] = useMap<string, any>(
initialValue.map((item, index) => { initialValue.map((item, index) => {
return [`index-${index}`, item]; return [`index-${index}`, item];
@ -137,7 +142,7 @@ export function FilterList(props) {
set(uid(), {}); set(uid(), {});
}} }}
> >
{t('Add filter')}
</a> </a>
<a <a
style={{ marginLeft: 16 }} style={{ marginLeft: 16 }}
@ -147,7 +152,7 @@ export function FilterList(props) {
}); });
}} }}
> >
{t('Add filter group')}
</a> </a>
</div> </div>
</div> </div>

View File

@ -30,6 +30,8 @@ import { useCollection, useCollectionContext } from '../../constate';
import { useTable } from '../table'; import { useTable } from '../table';
import { BlockSchemaContext } from '../../context'; import { BlockSchemaContext } from '../../context';
import { DragHandle } from '../../components/Sortable'; import { DragHandle } from '../../components/Sortable';
import { useTranslation } from 'react-i18next';
import { useCompile } from '../../hooks/useCompile';
export const DesignableBar = observer((props) => { export const DesignableBar = observer((props) => {
const field = useField(); const field = useField();
@ -40,11 +42,13 @@ export const DesignableBar = observer((props) => {
const blockSchema = useContext(BlockSchemaContext); const blockSchema = useContext(BlockSchemaContext);
const collectionName = blockSchema['x-component-props']?.collectionName; const collectionName = blockSchema['x-component-props']?.collectionName;
const { collection } = useCollection({ collectionName }); const { collection } = useCollection({ collectionName });
const { t } = useTranslation();
const compile = useCompile();
return ( return (
<div className={cls('designable-bar', { active: visible })}> <div className={cls('designable-bar', { active: visible })}>
{collection && ( {collection && (
<div className={'designable-info'}> <div className={'designable-info'}>
{collection?.title || collection?.name} {compile(collection?.title || collection?.name)}
</div> </div>
)} )}
<span <span
@ -77,8 +81,8 @@ export const DesignableBar = observer((props) => {
key={'delete'} key={'delete'}
onClick={async () => { onClick={async () => {
Modal.confirm({ Modal.confirm({
title: '删除区块', title: t('Delete block'),
content: '删除后无法恢复,确定要删除吗?', content: t('Are you sure you want to delete it?'),
onOk: async () => { onOk: async () => {
const removed = deepRemove(); const removed = deepRemove();
// console.log({ removed }) // console.log({ removed })
@ -88,7 +92,7 @@ export const DesignableBar = observer((props) => {
}); });
}} }}
> >
{t('Delete')}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }

View File

@ -41,8 +41,10 @@ import {
import SwitchMenuItem from '../../components/SwitchMenuItem'; import SwitchMenuItem from '../../components/SwitchMenuItem';
import { DragHandle } from '../../components/Sortable'; import { DragHandle } from '../../components/Sortable';
import { set } from 'lodash'; import { set } from 'lodash';
import { useTranslation } from 'react-i18next';
export const FieldDesignableBar = observer((props) => { export const FieldDesignableBar = observer((props) => {
const { t } = useTranslation();
const field = useField(); const field = useField();
const { schema, deepRemove, refresh } = useDesignable(); const { schema, deepRemove, refresh } = useDesignable();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
@ -84,7 +86,7 @@ export const FieldDesignableBar = observer((props) => {
<Menu.Item <Menu.Item
key={'update'} key={'update'}
onClick={async () => { onClick={async () => {
const values = await FormDialog('自定义字段名称', () => { const values = await FormDialog(t('Custom field display name'), () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
<SchemaField <SchemaField
@ -93,14 +95,14 @@ export const FieldDesignableBar = observer((props) => {
properties: { properties: {
fieldName: { fieldName: {
type: 'string', type: 'string',
title: '原字段名称', title: t('Original name'),
'x-read-pretty': true, 'x-read-pretty': true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
title: { title: {
type: 'string', type: 'string',
title: '自定义名称', title: t('Custom name'),
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
@ -128,7 +130,7 @@ export const FieldDesignableBar = observer((props) => {
}); });
}} }}
> >
{t('Custom field display name')}
</Menu.Item> </Menu.Item>
{collectionField?.interface === 'linkTo' && ( {collectionField?.interface === 'linkTo' && (
<Menu.Item> <Menu.Item>
@ -138,14 +140,14 @@ export const FieldDesignableBar = observer((props) => {
justifyContent: 'space-between', justifyContent: 'space-between',
}} }}
> >
{' '} {t('Label field')}{' '}
<Select <Select
value={ value={
schema?.['x-component-props']?.['fieldNames']?.[ schema?.['x-component-props']?.['fieldNames']?.[
'label' 'label'
] ]
} }
placeholder={'默认为 ID 字段'} placeholder={t('Default is the ID field')}
onChange={async (value) => { onChange={async (value) => {
set( set(
schema['x-component-props'], schema['x-component-props'],
@ -186,7 +188,7 @@ export const FieldDesignableBar = observer((props) => {
<Menu.Item <Menu.Item
style={{ minWidth: 150 }} style={{ minWidth: 150 }}
onClick={async () => { onClick={async () => {
const values = await FormDialog('编辑描述', () => { const values = await FormDialog(t('Edit description'), () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
<SchemaField <SchemaField
@ -217,10 +219,10 @@ export const FieldDesignableBar = observer((props) => {
}); });
}} }}
> >
{t('Edit description')}
</Menu.Item> </Menu.Item>
<SwitchMenuItem <SwitchMenuItem
title={'必填'} title={t('Required')}
checked={schema.required as boolean} checked={schema.required as boolean}
onChange={(checked) => { onChange={(checked) => {
field field
@ -245,7 +247,7 @@ export const FieldDesignableBar = observer((props) => {
await removeSchema(last); await removeSchema(last);
}} }}
> >
{t('Hide')}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }

View File

@ -45,6 +45,7 @@ import { useResource as useGeneralResource } from '../../hooks/useResource';
import { Resource } from '../../resource'; import { Resource } from '../../resource';
import { BaseResult } from '@ahooksjs/use-request/lib/types'; import { BaseResult } from '@ahooksjs/use-request/lib/types';
import { CollectionFieldContext } from '../table'; import { CollectionFieldContext } from '../table';
import { useTranslation } from 'react-i18next';
export interface DescriptionsContextProps { export interface DescriptionsContextProps {
resource?: Resource; resource?: Resource;
@ -54,6 +55,7 @@ export interface DescriptionsContextProps {
export const DescriptionsContext = createContext<DescriptionsContextProps>({}); export const DescriptionsContext = createContext<DescriptionsContextProps>({});
const FormMain = (props: any) => { const FormMain = (props: any) => {
const { t } = useTranslation();
const { const {
useResource = useGeneralResource, useResource = useGeneralResource,
showDefaultButtons = false, showDefaultButtons = false,
@ -123,19 +125,19 @@ const FormMain = (props: any) => {
const values = await form.submit(); const values = await form.submit();
console.log({ values }); console.log({ values });
await resource.save(values); await resource.save(values);
message.success('保存成功'); message.success(t('Saved successfully'));
await form.reset(); await form.reset();
}} }}
type={'primary'} type={'primary'}
> >
{t('Submit')}
</Button> </Button>
<Button <Button
onClick={async () => { onClick={async () => {
await form.reset(); await form.reset();
}} }}
> >
{t('Reset')}
</Button> </Button>
</Space> </Space>
)} )}

View File

@ -29,6 +29,7 @@ import { getSchemaPath } from '../../components/schema-renderer';
import { useCollection, useCollectionContext } from '../../constate'; import { useCollection, useCollectionContext } from '../../constate';
import { useTable } from '../table'; import { useTable } from '../table';
import { DragHandle } from '../../components/Sortable'; import { DragHandle } from '../../components/Sortable';
import { useCompile } from '../../hooks/useCompile';
export const DesignableBar = observer((props) => { export const DesignableBar = observer((props) => {
const field = useField(); const field = useField();
@ -39,10 +40,11 @@ export const DesignableBar = observer((props) => {
const collectionName = const collectionName =
field.componentProps?.collectionName || tableProps?.collectionName; field.componentProps?.collectionName || tableProps?.collectionName;
const { collection } = useCollection({ collectionName }); const { collection } = useCollection({ collectionName });
const compile = useCompile();
return ( return (
<div className={cls('designable-bar', { active: visible })}> <div className={cls('designable-bar', { active: visible })}>
<div className={'designable-info'}> <div className={'designable-info'}>
{collection?.title || collection?.name} {compile(collection?.title || collection?.name)}
</div> </div>
<span <span
onClick={(e) => { onClick={(e) => {

View File

@ -8,10 +8,12 @@ import {
IconPicker as Icon, IconPicker as Icon,
} from '../../components/icon-picker'; } from '../../components/icon-picker';
import { isValid } from '@formily/shared'; import { isValid } from '@formily/shared';
import { useTranslation } from 'react-i18next';
function IconField(props: any) { function IconField(props: any) {
const { value, onChange } = props; const { value, onChange } = props;
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const { t } = useTranslation();
return ( return (
<div> <div>
<Input.Group compact> <Input.Group compact>
@ -38,10 +40,10 @@ function IconField(props: any) {
))} ))}
</div> </div>
} }
title="图标" title={t('Icon')}
trigger="click" trigger="click"
> >
<Button>{hasIcon(value) ? <Icon type={value} /> : '选择图标'}</Button> <Button>{hasIcon(value) ? <Icon type={value} /> : t('Select icon')}</Button>
</Popover> </Popover>
{value && ( {value && (
<Button <Button

View File

@ -35,8 +35,10 @@ import { useTable } from '../table';
import SwitchMenuItem from '../../components/SwitchMenuItem'; import SwitchMenuItem from '../../components/SwitchMenuItem';
import { useKanban } from '.'; import { useKanban } from '.';
import { isAssociation } from '../database-field/interfaces'; import { isAssociation } from '../database-field/interfaces';
import { useTranslation } from 'react-i18next';
export const CardDesignableBar = observer((props) => { export const CardDesignableBar = observer((props) => {
const { t } = useTranslation();
const field = useField(); const field = useField();
const { schema, appendChild, deepRemove } = useDesignable(); const { schema, appendChild, deepRemove } = useDesignable();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
@ -62,7 +64,7 @@ export const CardDesignableBar = observer((props) => {
}} }}
overlay={ overlay={
<Menu> <Menu>
<Menu.ItemGroup className={'display-fields'} title={`显示字段`}> <Menu.ItemGroup className={'display-fields'} title={t('Display fields')}>
{fields?.map((field) => ( {fields?.map((field) => (
<SwitchMenuItem <SwitchMenuItem
key={field.key} key={field.key}

View File

@ -36,9 +36,12 @@ import { useCollection, useCollectionContext } from '../../constate';
import { useTable } from '../table'; import { useTable } from '../table';
import { DragHandle } from '../../components/Sortable'; import { DragHandle } from '../../components/Sortable';
import { fieldsToFilterColumns } from '../calendar'; import { fieldsToFilterColumns } from '../calendar';
import { useTranslation } from 'react-i18next';
import { useCompile } from '../../hooks/useCompile';
export const DesignableBar = observer((props) => { export const DesignableBar = observer((props) => {
const field = useField(); const field = useField();
const { t } = useTranslation();
const { designable, schema, refresh, deepRemove } = useDesignable(); const { designable, schema, refresh, deepRemove } = useDesignable();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const { dragRef } = useContext(DraggableBlockContext); const { dragRef } = useContext(DraggableBlockContext);
@ -46,10 +49,11 @@ export const DesignableBar = observer((props) => {
const collectionName = const collectionName =
field.componentProps?.collectionName || tableProps?.collectionName; field.componentProps?.collectionName || tableProps?.collectionName;
const { collection, fields } = useCollection({ collectionName }); const { collection, fields } = useCollection({ collectionName });
const compile = useCompile();
return ( return (
<div className={cls('designable-bar', { active: visible })}> <div className={cls('designable-bar', { active: visible })}>
<div className={'designable-info'}> <div className={'designable-info'}>
{collection?.title || collection?.name} {compile(collection?.title || collection?.name)}
</div> </div>
<span <span
onClick={(e) => { onClick={(e) => {
@ -80,7 +84,7 @@ export const DesignableBar = observer((props) => {
key={'defaultFilter'} key={'defaultFilter'}
onClick={async () => { onClick={async () => {
const { defaultFilter } = await FormDialog( const { defaultFilter } = await FormDialog(
'设置数据范围', t('Set the data scope'),
() => { () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
@ -111,15 +115,15 @@ export const DesignableBar = observer((props) => {
await updateSchema(schema); await updateSchema(schema);
}} }}
> >
{t('Set the data scope')}
</Menu.Item> </Menu.Item>
<Menu.Divider /> <Menu.Divider />
<Menu.Item <Menu.Item
key={'delete'} key={'delete'}
onClick={async () => { onClick={async () => {
Modal.confirm({ Modal.confirm({
title: '删除区块', title: t('Delete block'),
content: '删除后无法恢复,确定要删除吗?', content: t('Are you sure you want to delete it?'),
onOk: async () => { onOk: async () => {
const removed = deepRemove(); const removed = deepRemove();
// console.log({ removed }) // console.log({ removed })
@ -129,7 +133,7 @@ export const DesignableBar = observer((props) => {
}); });
}} }}
> >
{t('Delete')}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }

View File

@ -36,9 +36,11 @@ import { RandomNameContext } from '../form';
import { useCollectionContext, useDisplayedMapContext } from '../../constate'; import { useCollectionContext, useDisplayedMapContext } from '../../constate';
import SwitchMenuItem from '../../components/SwitchMenuItem'; import SwitchMenuItem from '../../components/SwitchMenuItem';
import { DragHandle } from '../../components/Sortable'; import { DragHandle } from '../../components/Sortable';
import { useTranslation } from 'react-i18next';
export const FieldDesignableBar = observer((props) => { export const FieldDesignableBar = observer((props) => {
const field = useField(); const field = useField();
const { t } = useTranslation();
const { schema, deepRemove } = useDesignable(); const { schema, deepRemove } = useDesignable();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const { dragRef } = useContext(DraggableBlockContext); const { dragRef } = useContext(DraggableBlockContext);
@ -78,7 +80,7 @@ export const FieldDesignableBar = observer((props) => {
<Menu.Item <Menu.Item
key={'update'} key={'update'}
onClick={async () => { onClick={async () => {
const values = await FormDialog('自定义字段名称', () => { const values = await FormDialog(t('Custom field display name'), () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
<SchemaField <SchemaField
@ -87,14 +89,14 @@ export const FieldDesignableBar = observer((props) => {
properties: { properties: {
fieldName: { fieldName: {
type: 'string', type: 'string',
title: '原字段名称', title: t('Original name'),
'x-read-pretty': true, 'x-read-pretty': true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
title: { title: {
type: 'string', type: 'string',
title: '自定义名称', title: t('Custom name'),
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
@ -122,15 +124,15 @@ export const FieldDesignableBar = observer((props) => {
}); });
}} }}
> >
{t('Custom field display name')}
</Menu.Item> </Menu.Item>
<Menu.Divider /> <Menu.Divider />
<Menu.Item <Menu.Item
key={'delete'} key={'delete'}
onClick={async () => { onClick={async () => {
Modal.confirm({ Modal.confirm({
title: '删除区块', title: t('Delete block'),
content: '删除后无法恢复,确定要删除吗?', content: t('Are you sure you want to delete it?'),
onOk: async () => { onOk: async () => {
const removed = deepRemove(); const removed = deepRemove();
const fieldName = const fieldName =
@ -142,7 +144,7 @@ export const FieldDesignableBar = observer((props) => {
}); });
}} }}
> >
{t('Delete')}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }

View File

@ -22,6 +22,7 @@ import { removeSchema, updateSchema } from '..';
import { isGridRowOrCol } from '../grid'; import { isGridRowOrCol } from '../grid';
import './style.less'; import './style.less';
import { DragHandle } from '../../components/Sortable'; import { DragHandle } from '../../components/Sortable';
import { useTranslation } from 'react-i18next';
export const Markdown: any = connect( export const Markdown: any = connect(
AntdInput.TextArea, AntdInput.TextArea,
@ -52,6 +53,7 @@ export const Markdown: any = connect(
); );
function MarkdownTextArea(props: any) { function MarkdownTextArea(props: any) {
const { t } = useTranslation();
const [value, setValue] = useState(props.defaultValue); const [value, setValue] = useState(props.defaultValue);
return ( return (
<div className={'mb-markdown'} style={{ position: 'relative' }}> <div className={'mb-markdown'} style={{ position: 'relative' }}>
@ -69,7 +71,7 @@ function MarkdownTextArea(props: any) {
props.onCancel && props.onCancel(e); props.onCancel && props.onCancel(e);
}} }}
> >
{t('Cancel')}
</Button> </Button>
<Button <Button
type={'primary'} type={'primary'}
@ -77,7 +79,7 @@ function MarkdownTextArea(props: any) {
props.onSubmit && props.onSubmit(value); props.onSubmit && props.onSubmit(value);
}} }}
> >
{t('Save')}
</Button> </Button>
</Space> </Space>
</div> </div>
@ -116,6 +118,7 @@ Markdown.Void = observer((props: any) => {
}); });
Markdown.Void.DesignableBar = observer((props) => { Markdown.Void.DesignableBar = observer((props) => {
const { t } = useTranslation();
const field = useField(); const field = useField();
const { designable, schema, refresh, deepRemove } = useDesignable(); const { designable, schema, refresh, deepRemove } = useDesignable();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
@ -149,15 +152,15 @@ Markdown.Void.DesignableBar = observer((props) => {
setVisible(false); setVisible(false);
}} }}
> >
{t('Edit')}
</Menu.Item> </Menu.Item>
<Menu.Divider /> <Menu.Divider />
<Menu.Item <Menu.Item
key={'delete'} key={'delete'}
onClick={async () => { onClick={async () => {
Modal.confirm({ Modal.confirm({
title: '删除区块', title: t('Delete block'),
content: '删除后无法恢复,确定要删除吗?', content: t('Are you sure you want to delete it?'),
onOk: async () => { onOk: async () => {
const removed = deepRemove(); const removed = deepRemove();
const last = removed.pop(); const last = removed.pop();
@ -167,7 +170,7 @@ Markdown.Void.DesignableBar = observer((props) => {
}); });
}} }}
> >
{t('Delete')}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }

View File

@ -1,3 +1,12 @@
.nb-markdown > *:last-child { .nb-markdown > *:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
.ant-description-textarea,
.ant-description-input {
line-height: 1.612;
}
.field-interface-datetime {
min-width: 100px;
}

View File

@ -71,6 +71,7 @@ import { DndContext, DragOverlay } from '@dnd-kit/core';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { Resource } from '../../resource'; import { Resource } from '../../resource';
import { useClient } from '../../constate'; import { useClient } from '../../constate';
import { useTranslation } from 'react-i18next';
export interface MenuContextProps { export interface MenuContextProps {
schema?: Schema; schema?: Schema;
@ -82,6 +83,7 @@ export const MenuContext = createContext<MenuContextProps>(null);
const SideMenu = (props: any) => { const SideMenu = (props: any) => {
const { selectedKey, defaultSelectedKeys, onSelect, path } = props; const { selectedKey, defaultSelectedKeys, onSelect, path } = props;
const { t } = useTranslation();
const { schema } = useDesignable(); const { schema } = useDesignable();
const [selectedKeys, setSelectedKeys] = useState(defaultSelectedKeys); const [selectedKeys, setSelectedKeys] = useState(defaultSelectedKeys);
useEffect(() => { useEffect(() => {
@ -114,7 +116,7 @@ const SideMenu = (props: any) => {
icon={<PlusOutlined />} icon={<PlusOutlined />}
className={`nb-add-new-menu-item menu-mode-inline designable-btn designable-btn-dash`} className={`nb-add-new-menu-item menu-mode-inline designable-btn designable-btn-dash`}
> >
{t('Add menu item')}
</Button> </Button>
</Menu.AddNew> </Menu.AddNew>
</AntdMenu> </AntdMenu>
@ -160,6 +162,7 @@ export const Menu: any = observer((props: any) => {
const child = schema.properties && schema.properties[selectedKey]; const child = schema.properties && schema.properties[selectedKey];
const isSubMenu = child && child['x-component'] === 'Menu.SubMenu'; const isSubMenu = child && child['x-component'] === 'Menu.SubMenu';
const { updateSchema } = useClient(); const { updateSchema } = useClient();
const { t } = useTranslation();
useMount(() => { useMount(() => {
if (mode !== 'mix') { if (mode !== 'mix') {
@ -238,7 +241,7 @@ export const Menu: any = observer((props: any) => {
icon={<PlusOutlined />} icon={<PlusOutlined />}
type={mode == 'inline' ? 'dashed' : 'primary'} type={mode == 'inline' ? 'dashed' : 'primary'}
> >
{t('Add menu item')}
</Button> </Button>
</Menu.AddNew> </Menu.AddNew>
</AntdMenu> </AntdMenu>
@ -437,26 +440,27 @@ Menu.SubMenu = observer((props: any) => {
Menu.AddNew = observer((props: any) => { Menu.AddNew = observer((props: any) => {
const { designable, appendChild } = useDesignable(props.path); const { designable, appendChild } = useDesignable(props.path);
const { createSchema, removeSchema, updateSchema } = useClient(); const { createSchema, removeSchema, updateSchema } = useClient();
const { t } = useTranslation();
if (!designable) { if (!designable) {
return null; return null;
} }
const schemas = { const schemas = {
'Menu.Link': { 'Menu.Link': {
icon: <MenuOutlined />, icon: <MenuOutlined />,
title: '添加页面', title: t('Add page'),
schema: { schema: {
type: 'object', type: 'object',
properties: { properties: {
title: { title: {
type: 'string', type: 'string',
title: '名称', title: t('Name'),
required: true, required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
'x-component-props.icon': { 'x-component-props.icon': {
type: 'string', type: 'string',
title: '图标', title: t('Icon'),
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'IconPicker', 'x-component': 'IconPicker',
}, },
@ -465,20 +469,20 @@ Menu.AddNew = observer((props: any) => {
}, },
'Menu.SubMenu': { 'Menu.SubMenu': {
icon: <GroupOutlined />, icon: <GroupOutlined />,
title: '添加分组', title: t('Add group'),
schema: { schema: {
type: 'object', type: 'object',
properties: { properties: {
title: { title: {
type: 'string', type: 'string',
title: '名称', title: t('Name'),
required: true, required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
'x-component-props.icon': { 'x-component-props.icon': {
type: 'string', type: 'string',
title: '图标', title: t('Icon'),
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'IconPicker', 'x-component': 'IconPicker',
}, },
@ -487,26 +491,26 @@ Menu.AddNew = observer((props: any) => {
}, },
'Menu.URL': { 'Menu.URL': {
icon: <LinkOutlined />, icon: <LinkOutlined />,
title: '添加链接', title: t('Add link'),
schema: { schema: {
type: 'object', type: 'object',
properties: { properties: {
title: { title: {
type: 'string', type: 'string',
title: '名称', title: t('Name'),
required: true, required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
'x-component-props.icon': { 'x-component-props.icon': {
type: 'string', type: 'string',
title: '图标', title: t('Icon'),
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'IconPicker', 'x-component': 'IconPicker',
}, },
'x-component-props.href': { 'x-component-props.href': {
type: 'string', type: 'string',
title: '链接', title: t('Link'),
required: true, required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
@ -539,13 +543,13 @@ Menu.AddNew = observer((props: any) => {
}} }}
> >
<AntdMenu.Item key={'Menu.SubMenu'} icon={<GroupOutlined />}> <AntdMenu.Item key={'Menu.SubMenu'} icon={<GroupOutlined />}>
{t('Group')}
</AntdMenu.Item> </AntdMenu.Item>
<AntdMenu.Item key={'Menu.Link'} icon={<MenuOutlined />}> <AntdMenu.Item key={'Menu.Link'} icon={<MenuOutlined />}>
{t('Page')}
</AntdMenu.Item> </AntdMenu.Item>
<AntdMenu.Item key={'Menu.URL'} icon={<LinkOutlined />}> <AntdMenu.Item key={'Menu.URL'} icon={<LinkOutlined />}>
{t('Link')}
</AntdMenu.Item> </AntdMenu.Item>
</AntdMenu> </AntdMenu>
} }
@ -558,23 +562,24 @@ Menu.AddNew = observer((props: any) => {
}); });
Menu.DesignableBar = (props) => { Menu.DesignableBar = (props) => {
const { t } = useTranslation();
const schemas = { const schemas = {
'Menu.Action': { 'Menu.Action': {
icon: <MenuOutlined />, icon: <MenuOutlined />,
title: '页面', title: t('Page'),
schema: { schema: {
type: 'object', type: 'object',
properties: { properties: {
title: { title: {
type: 'string', type: 'string',
title: '名称', title: t('Name'),
required: true, required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
'x-component-props.icon': { 'x-component-props.icon': {
type: 'string', type: 'string',
title: '图标', title: t('Icon'),
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'IconPicker', 'x-component': 'IconPicker',
}, },
@ -583,20 +588,20 @@ Menu.DesignableBar = (props) => {
}, },
'Menu.Item': { 'Menu.Item': {
icon: <MenuOutlined />, icon: <MenuOutlined />,
title: '页面', title: t('Page'),
schema: { schema: {
type: 'object', type: 'object',
properties: { properties: {
title: { title: {
type: 'string', type: 'string',
title: '名称', title: t('Name'),
required: true, required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
'x-component-props.icon': { 'x-component-props.icon': {
type: 'string', type: 'string',
title: '图标', title: t('Icon'),
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'IconPicker', 'x-component': 'IconPicker',
}, },
@ -605,20 +610,20 @@ Menu.DesignableBar = (props) => {
}, },
'Menu.Link': { 'Menu.Link': {
icon: <MenuOutlined />, icon: <MenuOutlined />,
title: '页面', title: t('Page'),
schema: { schema: {
type: 'object', type: 'object',
properties: { properties: {
title: { title: {
type: 'string', type: 'string',
title: '名称', title: t('Name'),
required: true, required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
'x-component-props.icon': { 'x-component-props.icon': {
type: 'string', type: 'string',
title: '图标', title: t('Icon'),
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'IconPicker', 'x-component': 'IconPicker',
}, },
@ -627,20 +632,20 @@ Menu.DesignableBar = (props) => {
}, },
'Menu.SubMenu': { 'Menu.SubMenu': {
icon: <GroupOutlined />, icon: <GroupOutlined />,
title: '分组', title: t('Group'),
schema: { schema: {
type: 'object', type: 'object',
properties: { properties: {
title: { title: {
type: 'string', type: 'string',
title: '名称', title: t('Name'),
required: true, required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
'x-component-props.icon': { 'x-component-props.icon': {
type: 'string', type: 'string',
title: '图标', title: t('Icon'),
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'IconPicker', 'x-component': 'IconPicker',
}, },
@ -649,26 +654,26 @@ Menu.DesignableBar = (props) => {
}, },
'Menu.URL': { 'Menu.URL': {
icon: <LinkOutlined />, icon: <LinkOutlined />,
title: '链接', title: 'Link',
schema: { schema: {
type: 'object', type: 'object',
properties: { properties: {
title: { title: {
type: 'string', type: 'string',
title: '名称', title: t('Name'),
required: true, required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
'x-component-props.icon': { 'x-component-props.icon': {
type: 'string', type: 'string',
title: '图标', title: t('Icon'),
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'IconPicker', 'x-component': 'IconPicker',
}, },
'x-component-props.href': { 'x-component-props.href': {
type: 'string', type: 'string',
title: '链接', title: t('Link'),
required: true, required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
@ -718,9 +723,9 @@ Menu.DesignableBar = (props) => {
return; return;
} }
const methodLabels = { const methodLabels = {
insertBefore: '之前', insertBefore: 'before',
insertAfter: '之后', insertAfter: 'after',
appendChild: '', appendChild: 'in',
}; };
const keys = info.key.split('.'); const keys = info.key.split('.');
const method = keys.shift(); const method = keys.shift();
@ -730,7 +735,8 @@ Menu.DesignableBar = (props) => {
return; return;
} }
const values = await FormDialog( const values = await FormDialog(
`在「${schema.title}${methodLabels[method]}插入${config.title}`, t(`Add {{type}} ${methodLabels[method]} "{{title}}"`, { type: (config.title as string).toLowerCase(), title: schema.title }),
// `在「${schema.title}」${methodLabels[method]}插入${config.title}`,
() => { () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
@ -758,7 +764,7 @@ Menu.DesignableBar = (props) => {
_.set(initialValues, name, get(schema, name)); _.set(initialValues, name, get(schema, name));
}, },
); );
const values = await FormDialog(`编辑菜单项`, () => { const values = await FormDialog(t('Edit menu item'), () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
<SchemaField schema={formConfig.schema} /> <SchemaField schema={formConfig.schema} />
@ -780,7 +786,7 @@ Menu.DesignableBar = (props) => {
await updateSchema(schema); await updateSchema(schema);
}} }}
> >
<EditOutlined /> <EditOutlined /> {t('Edit')}
</AntdMenu.Item> </AntdMenu.Item>
<AntdMenu.Item <AntdMenu.Item
key={'move'} key={'move'}
@ -825,7 +831,7 @@ Menu.DesignableBar = (props) => {
const dataSource = toTreeData(menuSchema); const dataSource = toTreeData(menuSchema);
const values = await FormDialog( const values = await FormDialog(
`将「${schema.title}」移动到`, t(`Move {{title}} to`, { title: schema.title }),
() => { () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
@ -835,8 +841,9 @@ Menu.DesignableBar = (props) => {
properties: { properties: {
path: { path: {
type: 'string', type: 'string',
title: '目标位置', title: t('Target position'),
enum: dataSource, enum: dataSource,
required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'TreeSelect', 'x-component': 'TreeSelect',
}, },
@ -844,8 +851,8 @@ Menu.DesignableBar = (props) => {
type: 'string', type: 'string',
default: 'insertAfter', default: 'insertAfter',
enum: [ enum: [
{ label: '之后', value: 'insertAfter' }, { label: t('After'), value: 'insertAfter' },
{ label: '之前', value: 'insertBefore' }, { label: t('Before'), value: 'insertBefore' },
], ],
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Radio.Group', 'x-component': 'Radio.Group',
@ -872,13 +879,13 @@ Menu.DesignableBar = (props) => {
f.dataSource = f.dataSource =
target['x-component'] === 'Menu.SubMenu' target['x-component'] === 'Menu.SubMenu'
? [ ? [
{ label: '之后', value: 'insertAfter' }, { label: t('After'), value: 'insertAfter' },
{ label: '之前', value: 'insertBefore' }, { label: t('Before'), value: 'insertBefore' },
{ label: '组里', value: 'appendChild' }, { label: t('Inner'), value: 'appendChild' },
] ]
: [ : [
{ label: '之后', value: 'insertAfter' }, { label: t('After'), value: 'insertAfter' },
{ label: '之前', value: 'insertBefore' }, { label: t('Before'), value: 'insertBefore' },
]; ];
}); });
}); });
@ -895,80 +902,80 @@ Menu.DesignableBar = (props) => {
await updateSchema(source); await updateSchema(source);
}} }}
> >
<DragOutlined /> <DragOutlined /> {t('Move to')}
</AntdMenu.Item> </AntdMenu.Item>
<AntdMenu.Divider /> <AntdMenu.Divider />
<AntdMenu.SubMenu <AntdMenu.SubMenu
key={'insertBefore'} key={'insertBefore'}
icon={<ArrowUpOutlined />} icon={<ArrowUpOutlined />}
title={`${mode == 'inline' ? '上' : '左'}插入`} title={t(`Insert ${mode == 'inline' ? 'above' : 'left'}`)}
> >
<AntdMenu.Item <AntdMenu.Item
key={'insertBefore.Menu.SubMenu'} key={'insertBefore.Menu.SubMenu'}
icon={<GroupOutlined />} icon={<GroupOutlined />}
> >
{t('Group')}
</AntdMenu.Item> </AntdMenu.Item>
<AntdMenu.Item <AntdMenu.Item
key={'insertBefore.Menu.Link'} key={'insertBefore.Menu.Link'}
icon={<MenuOutlined />} icon={<MenuOutlined />}
> >
{t('Page')}
</AntdMenu.Item> </AntdMenu.Item>
<AntdMenu.Item <AntdMenu.Item
key={'insertBefore.Menu.URL'} key={'insertBefore.Menu.URL'}
icon={<LinkOutlined />} icon={<LinkOutlined />}
> >
{t('Link')}
</AntdMenu.Item> </AntdMenu.Item>
</AntdMenu.SubMenu> </AntdMenu.SubMenu>
<AntdMenu.SubMenu <AntdMenu.SubMenu
key={'insertAfter'} key={'insertAfter'}
icon={<ArrowDownOutlined />} icon={<ArrowDownOutlined />}
title={`${mode == 'inline' ? '下' : '右'}插入`} title={t(`Insert ${mode == 'inline' ? 'below' : 'right'}`)}
> >
<AntdMenu.Item <AntdMenu.Item
key={'insertAfter.Menu.SubMenu'} key={'insertAfter.Menu.SubMenu'}
icon={<GroupOutlined />} icon={<GroupOutlined />}
> >
{t('Group')}
</AntdMenu.Item> </AntdMenu.Item>
<AntdMenu.Item <AntdMenu.Item
key={'insertAfter.Menu.Link'} key={'insertAfter.Menu.Link'}
icon={<MenuOutlined />} icon={<MenuOutlined />}
> >
{t('Page')}
</AntdMenu.Item> </AntdMenu.Item>
<AntdMenu.Item <AntdMenu.Item
key={'insertAfter.Menu.URL'} key={'insertAfter.Menu.URL'}
icon={<LinkOutlined />} icon={<LinkOutlined />}
> >
{t('Link')}
</AntdMenu.Item> </AntdMenu.Item>
</AntdMenu.SubMenu> </AntdMenu.SubMenu>
{isSubMenu && ( {isSubMenu && (
<AntdMenu.SubMenu <AntdMenu.SubMenu
key={'appendChild'} key={'appendChild'}
icon={<ArrowRightOutlined />} icon={<ArrowRightOutlined />}
title={'向里插入'} title={t('Insert inner')}
> >
<AntdMenu.Item <AntdMenu.Item
key={'appendChild.Menu.SubMenu'} key={'appendChild.Menu.SubMenu'}
icon={<GroupOutlined />} icon={<GroupOutlined />}
> >
{t('Group')}
</AntdMenu.Item> </AntdMenu.Item>
<AntdMenu.Item <AntdMenu.Item
key={'appendChild.Menu.Link'} key={'appendChild.Menu.Link'}
icon={<MenuOutlined />} icon={<MenuOutlined />}
> >
{t('Page')}
</AntdMenu.Item> </AntdMenu.Item>
<AntdMenu.Item <AntdMenu.Item
key={'appendChild.Menu.URL'} key={'appendChild.Menu.URL'}
icon={<LinkOutlined />} icon={<LinkOutlined />}
> >
{t('Link')}
</AntdMenu.Item> </AntdMenu.Item>
</AntdMenu.SubMenu> </AntdMenu.SubMenu>
)} )}
@ -1035,8 +1042,8 @@ Menu.DesignableBar = (props) => {
key={'delete'} key={'delete'}
onClick={() => { onClick={() => {
Modal.confirm({ Modal.confirm({
title: `删除${formConfig.title}`, title: t(`Delete menu item`),
content: '删除后无法恢复,确定要删除吗?', content: t('Are you sure you want to delete it?'),
onOk: async () => { onOk: async () => {
const target = remove(); const target = remove();
await removeSchema(target); await removeSchema(target);
@ -1045,7 +1052,7 @@ Menu.DesignableBar = (props) => {
}); });
}} }}
> >
<DeleteOutlined /> <DeleteOutlined /> {t('Delete')}
</AntdMenu.Item> </AntdMenu.Item>
</AntdMenu> </AntdMenu>
} }

View File

@ -3,14 +3,16 @@ import { PageHeader as AntdPageHeader } from 'antd';
import { observer } from '@formily/react'; import { observer } from '@formily/react';
import { usePageTitleContext } from '../../constate/PageTitle'; import { usePageTitleContext } from '../../constate/PageTitle';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { useCompile } from '../../hooks/useCompile';
export const Page = observer((props) => { export const Page = observer((props) => {
const { children, ...others } = props; const { children, ...others } = props;
const { documentTitle, pageTitle } = usePageTitleContext(); const { documentTitle, pageTitle } = usePageTitleContext();
const compile = useCompile();
return ( return (
<> <>
<Helmet> <Helmet>
<title>{documentTitle}</title> <title>{compile(documentTitle)}</title>
</Helmet> </Helmet>
<AntdPageHeader ghost={false} title={pageTitle} {...others} /> <AntdPageHeader ghost={false} title={pageTitle} {...others} />
<div style={{ margin: 24 }}>{children}</div> <div style={{ margin: 24 }}>{children}</div>

View File

@ -34,8 +34,10 @@ import { uid } from '@formily/shared';
import { getSchemaPath } from '../../components/schema-renderer'; import { getSchemaPath } from '../../components/schema-renderer';
import { set } from 'lodash'; import { set } from 'lodash';
import { DragHandle } from '../../components/Sortable'; import { DragHandle } from '../../components/Sortable';
import { Trans, useTranslation } from 'react-i18next';
export const SimpleDesignableBar = observer((props) => { export const SimpleDesignableBar = observer((props) => {
const { t } = useTranslation();
const field = useField(); const field = useField();
const { designable, schema, refresh, deepRemove } = useDesignable(); const { designable, schema, refresh, deepRemove } = useDesignable();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
@ -68,7 +70,7 @@ export const SimpleDesignableBar = observer((props) => {
key={'defaultFilter'} key={'defaultFilter'}
onClick={async () => { onClick={async () => {
const { defaultFilter } = await FormDialog( const { defaultFilter } = await FormDialog(
'设置数据范围', t('Set the data scope'),
() => { () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
@ -82,12 +84,12 @@ export const SimpleDesignableBar = observer((props) => {
properties: { properties: {
column1: { column1: {
type: 'void', type: 'void',
title: '操作类型', title: t('Action type'),
'x-component': 'Filter.Column', 'x-component': 'Filter.Column',
'x-component-props': { 'x-component-props': {
operations: [ operations: [
{ {
label: '等于', label: '{{t("is")}}',
value: 'eq', value: 'eq',
selected: true, selected: true,
schema: { schema: {
@ -95,14 +97,14 @@ export const SimpleDesignableBar = observer((props) => {
}, },
}, },
{ {
label: '不等于', label: '{{t("is not")}}',
value: 'ne', value: 'ne',
schema: { schema: {
'x-component': 'Select', 'x-component': 'Select',
}, },
}, },
{ {
label: '包含', label: '{{t("contains")}}',
value: 'in', value: 'in',
schema: { schema: {
'x-component': 'Select', 'x-component': 'Select',
@ -112,7 +114,7 @@ export const SimpleDesignableBar = observer((props) => {
}, },
}, },
{ {
label: '不包含', label: '{{t("does not contain")}}',
value: 'notIn', value: 'notIn',
schema: { schema: {
'x-component': 'Select', 'x-component': 'Select',
@ -122,13 +124,13 @@ export const SimpleDesignableBar = observer((props) => {
}, },
}, },
{ {
label: '非空', label: '{{t("is empty")}}',
value: '$notNull', value: '$null',
noValue: true, noValue: true,
}, },
{ {
label: '为空', label: '{{t("is not empty")}}',
value: '$null', value: '$notNull',
noValue: true, noValue: true,
}, },
], ],
@ -139,15 +141,15 @@ export const SimpleDesignableBar = observer((props) => {
'x-component': 'Select', 'x-component': 'Select',
enum: [ enum: [
{ {
label: '添加数据', label: '{{t("Insert")}}',
value: 'create', value: 'create',
}, },
{ {
label: '更新数据', label: '{{t("Update")}}',
value: 'update', value: 'update',
}, },
{ {
label: '删除数据', label: '{{t("Delete")}}',
value: 'destroy', value: 'destroy',
}, },
], ],
@ -175,40 +177,42 @@ export const SimpleDesignableBar = observer((props) => {
setVisible(false); setVisible(false);
}} }}
> >
{t('Set the data scope')}
</Menu.Item> </Menu.Item>
<Menu.Item key={'defaultPageSize'}> <Menu.Item key={'defaultPageSize'}>
{' '} <Trans>
<Select {'Display '}
bordered={false} <Select
size={'small'} bordered={false}
onChange={(value) => { size={'small'}
const componentProps = schema['x-component-props'] || {}; onChange={(value) => {
set(componentProps, 'pagination.defaultPageSize', value); const componentProps = schema['x-component-props'] || {};
set(componentProps, 'pagination.pageSize', value); set(componentProps, 'pagination.defaultPageSize', value);
schema['x-component-props'] = componentProps; set(componentProps, 'pagination.pageSize', value);
field.componentProps.pagination.pageSize = value; schema['x-component-props'] = componentProps;
field.componentProps.pagination.defaultPageSize = value; field.componentProps.pagination.pageSize = value;
refresh(); field.componentProps.pagination.defaultPageSize = value;
updateSchema(schema); refresh();
setVisible(false); updateSchema(schema);
}} setVisible(false);
defaultValue={defaultPageSize} }}
> defaultValue={defaultPageSize}
<Select.Option value={10}>10</Select.Option> >
<Select.Option value={20}>20</Select.Option> <Select.Option value={10}>10</Select.Option>
<Select.Option value={50}>50</Select.Option> <Select.Option value={20}>20</Select.Option>
<Select.Option value={100}>100</Select.Option> <Select.Option value={50}>50</Select.Option>
</Select>{' '} <Select.Option value={100}>100</Select.Option>
</Select>
{' items per page'}
</Trans>
</Menu.Item> </Menu.Item>
<Menu.Divider /> <Menu.Divider />
<Menu.Item <Menu.Item
key={'delete'} key={'delete'}
onClick={async () => { onClick={async () => {
Modal.confirm({ Modal.confirm({
title: '删除区块', title: t('Delete block'),
content: '删除后无法恢复,确定要删除吗?', content: t('Are you sure you want to delete it?'),
onOk: async () => { onOk: async () => {
const removed = deepRemove(); const removed = deepRemove();
// console.log({ removed }) // console.log({ removed })
@ -219,7 +223,7 @@ export const SimpleDesignableBar = observer((props) => {
}); });
}} }}
> >
{t('Delete')}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }

View File

@ -72,6 +72,8 @@ import IconPicker from '../../components/icon-picker';
import { DescriptionsContext } from '../form'; import { DescriptionsContext } from '../form';
import { VisibleContext } from '../../context'; import { VisibleContext } from '../../context';
import { SimpleDesignableBar } from './SimpleDesignableBar'; import { SimpleDesignableBar } from './SimpleDesignableBar';
import { Trans, useTranslation } from 'react-i18next';
import { useCompile } from '../../hooks/useCompile';
export interface ITableContext { export interface ITableContext {
props: any; props: any;
@ -401,6 +403,7 @@ const useTableColumns = () => {
}; };
function AddColumn() { function AddColumn() {
const { t } = useTranslation();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const { appendChild, remove } = useDesignable(); const { appendChild, remove } = useDesignable();
const { loadCollections } = useCollectionsContext(); const { loadCollections } = useCollectionsContext();
@ -408,7 +411,6 @@ function AddColumn() {
const displayed = useDisplayedMapContext(); const displayed = useDisplayedMapContext();
const { service } = useTable(); const { service } = useTable();
const { createSchema, removeSchema, updateSchema } = useClient(); const { createSchema, removeSchema, updateSchema } = useClient();
return ( return (
<Dropdown <Dropdown
trigger={['hover']} trigger={['hover']}
@ -416,7 +418,10 @@ function AddColumn() {
onVisibleChange={setVisible} onVisibleChange={setVisible}
overlay={ overlay={
<Menu> <Menu>
<Menu.ItemGroup className={'display-fields'} title={'显示字段'}> <Menu.ItemGroup
className={'display-fields'}
title={t('Display fields')}
>
{fields.map((field) => ( {fields.map((field) => (
<SwitchMenuItem <SwitchMenuItem
title={field?.uiSchema?.title} title={field?.uiSchema?.title}
@ -445,7 +450,7 @@ function AddColumn() {
'x-component-props': { 'x-component-props': {
useOkAction: '{{ Select.useOkAction }}', useOkAction: '{{ Select.useOkAction }}',
}, },
title: '关联数据', title: "{{t('Select record')}}",
properties: { properties: {
table: { table: {
type: 'array', type: 'array',
@ -479,7 +484,7 @@ function AddColumn() {
properties: { properties: {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '筛选', title: "{{t('Filter')}}",
'x-decorator': 'AddNew.Displayed', 'x-decorator': 'AddNew.Displayed',
'x-decorator-props': { 'x-decorator-props': {
displayName: 'filter', displayName: 'filter',
@ -504,7 +509,7 @@ function AddColumn() {
properties: { properties: {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '查看数据', title: "{{t('View record')}}",
'x-component': 'Action.Drawer', 'x-component': 'Action.Drawer',
'x-component-props': { 'x-component-props': {
bodyStyle: { bodyStyle: {
@ -519,7 +524,7 @@ function AddColumn() {
properties: { properties: {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '详情', title: "{{t('Details')}}",
'x-designable-bar': 'x-designable-bar':
'Tabs.TabPane.DesignableBar', 'Tabs.TabPane.DesignableBar',
'x-component': 'Tabs.TabPane', 'x-component': 'Tabs.TabPane',
@ -581,7 +586,7 @@ function AddColumn() {
<Menu.SubMenu <Menu.SubMenu
disabled disabled
popupClassName={'add-new-fields-popup'} popupClassName={'add-new-fields-popup'}
title={'添加字段'} title={t('Add field')}
> >
{options.map((option) => ( {options.map((option) => (
<Menu.ItemGroup title={option.label}> <Menu.ItemGroup title={option.label}>
@ -591,7 +596,7 @@ function AddColumn() {
key={item.name} key={item.name}
onClick={async () => { onClick={async () => {
setVisible(false); setVisible(false);
const values = await FormDialog(`添加字段`, () => { const values = await FormDialog(t('Add field'), () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
<SchemaField <SchemaField
@ -635,7 +640,7 @@ function AddColumn() {
className={'designable-btn designable-btn-dash'} className={'designable-btn designable-btn-dash'}
icon={<SettingOutlined />} icon={<SettingOutlined />}
> >
{t('Configure fields')}
</Button> </Button>
</Dropdown> </Dropdown>
); );
@ -903,8 +908,8 @@ const TableProvider = (props: any) => {
useEffect(() => { useEffect(() => {
service.run(getDefaultParams()); service.run(getDefaultParams());
}, [ }, [
pagination.pageSize, pagination?.pageSize,
pagination.page, pagination?.page,
props.dragSort, props.dragSort,
props.defaultSort, props.defaultSort,
props.defaultFilter, props.defaultFilter,
@ -1001,7 +1006,7 @@ function generateActionSchema(type) {
key: uid(), key: uid(),
name: uid(), name: uid(),
type: 'void', type: 'void',
title: '筛选', title: "{{ t('Filter') }}",
'x-align': 'left', 'x-align': 'left',
'x-decorator': 'AddNew.Displayed', 'x-decorator': 'AddNew.Displayed',
'x-decorator-props': { 'x-decorator-props': {
@ -1017,7 +1022,7 @@ function generateActionSchema(type) {
key: uid(), key: uid(),
type: 'void', type: 'void',
name: uid(), name: uid(),
title: '导出', title: "{{ t('Export') }}",
'x-align': 'right', 'x-align': 'right',
'x-decorator': 'AddNew.Displayed', 'x-decorator': 'AddNew.Displayed',
'x-decorator-props': { 'x-decorator-props': {
@ -1036,7 +1041,7 @@ function generateActionSchema(type) {
key: uid(), key: uid(),
type: 'void', type: 'void',
name: uid(), name: uid(),
title: '添加', title: "{{ t('Add new') }}",
'x-align': 'right', 'x-align': 'right',
'x-decorator': 'AddNew.Displayed', 'x-decorator': 'AddNew.Displayed',
'x-decorator-props': { 'x-decorator-props': {
@ -1051,7 +1056,7 @@ function generateActionSchema(type) {
properties: { properties: {
modal: { modal: {
type: 'void', type: 'void',
title: '添加数据', title: "{{ t('Add record') }}",
'x-decorator': 'Form', 'x-decorator': 'Form',
'x-component': 'Action.Drawer', 'x-component': 'Action.Drawer',
'x-component-props': { 'x-component-props': {
@ -1073,7 +1078,7 @@ function generateActionSchema(type) {
key: uid(), key: uid(),
type: 'void', type: 'void',
name: uid(), name: uid(),
title: '删除', title: "{{ t('Delete') }}",
'x-align': 'right', 'x-align': 'right',
'x-decorator': 'AddNew.Displayed', 'x-decorator': 'AddNew.Displayed',
'x-decorator-props': { 'x-decorator-props': {
@ -1084,8 +1089,8 @@ function generateActionSchema(type) {
'x-designable-bar': 'Table.Action.DesignableBar', 'x-designable-bar': 'Table.Action.DesignableBar',
'x-component-props': { 'x-component-props': {
confirm: { confirm: {
title: '删除数据', title: "{{ t('Delete record') }}",
content: '删除后无法恢复,确定要删除吗?', content: "{{ t('Are you sure you want to delete it?') }}",
}, },
useAction: '{{ Table.useTableDestroyAction }}', useAction: '{{ Table.useTableDestroyAction }}',
}, },
@ -1102,7 +1107,7 @@ function generateMenuActionSchema(type) {
key: uid(), key: uid(),
name: uid(), name: uid(),
type: 'void', type: 'void',
title: '查看', title: "{{ t('View') }}",
'x-component': 'Action', 'x-component': 'Action',
'x-component-props': { 'x-component-props': {
type: 'link', type: 'link',
@ -1112,7 +1117,7 @@ function generateMenuActionSchema(type) {
properties: { properties: {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '查看数据', title: "{{ t('View record') }}",
'x-component': 'Action.Drawer', 'x-component': 'Action.Drawer',
'x-component-props': { 'x-component-props': {
bodyStyle: { bodyStyle: {
@ -1128,7 +1133,7 @@ function generateMenuActionSchema(type) {
properties: { properties: {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '详情', title: "{{ t('Details') }}",
'x-designable-bar': 'Tabs.TabPane.DesignableBar', 'x-designable-bar': 'Tabs.TabPane.DesignableBar',
'x-component': 'Tabs.TabPane', 'x-component': 'Tabs.TabPane',
'x-component-props': {}, 'x-component-props': {},
@ -1152,7 +1157,7 @@ function generateMenuActionSchema(type) {
key: uid(), key: uid(),
name: uid(), name: uid(),
type: 'void', type: 'void',
title: '编辑', title: "{{ t('Edit') }}",
'x-component': 'Action', 'x-component': 'Action',
'x-component-props': { 'x-component-props': {
type: 'link', type: 'link',
@ -1162,7 +1167,7 @@ function generateMenuActionSchema(type) {
properties: { properties: {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '编辑数据', title: "{{ t('Edit record') }}",
'x-decorator': 'Form', 'x-decorator': 'Form',
'x-decorator-props': { 'x-decorator-props': {
useResource: '{{ Table.useResource }}', useResource: '{{ Table.useResource }}',
@ -1188,7 +1193,7 @@ function generateMenuActionSchema(type) {
key: uid(), key: uid(),
name: uid(), name: uid(),
type: 'void', type: 'void',
title: '删除', title: "{{ t('Delete') }}",
'x-component': 'Action', 'x-component': 'Action',
'x-designable-bar': 'Table.Action.DesignableBar', 'x-designable-bar': 'Table.Action.DesignableBar',
'x-action-type': 'destroy', 'x-action-type': 'destroy',
@ -1196,8 +1201,8 @@ function generateMenuActionSchema(type) {
useAction: '{{ Table.useTableDestroyAction }}', useAction: '{{ Table.useTableDestroyAction }}',
type: 'link', type: 'link',
confirm: { confirm: {
title: '删除数据', title: "{{ t('Delete record') }}",
content: '删除后无法恢复,确定要删除吗?', content: "{{ t('Are you sure you want to delete it?') }}",
}, },
}, },
}, },
@ -1206,6 +1211,7 @@ function generateMenuActionSchema(type) {
} }
function AddActionButton() { function AddActionButton() {
const { t } = useTranslation();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const displayed = useDisplayedMapContext(); const displayed = useDisplayedMapContext();
const { appendChild, remove } = useDesignable(); const { appendChild, remove } = useDesignable();
@ -1222,12 +1228,12 @@ function AddActionButton() {
onVisibleChange={setVisible} onVisibleChange={setVisible}
overlay={ overlay={
<Menu> <Menu>
<Menu.ItemGroup title={'启用操作'}> <Menu.ItemGroup title={t('Enable actions')}>
{[ {[
{ title: '筛选', name: 'filter' }, { title: t('Filter'), name: 'filter' },
{ title: '导出', name: 'export' }, { title: t('Export'), name: 'export' },
{ title: '添加', name: 'create' }, { title: t('Add new'), name: 'create' },
{ title: '删除', name: 'destroy' }, { title: t('Delete'), name: 'destroy' },
].map((item) => ( ].map((item) => (
<SwitchMenuItem <SwitchMenuItem
key={item.name} key={item.name}
@ -1250,10 +1256,10 @@ function AddActionButton() {
))} ))}
</Menu.ItemGroup> </Menu.ItemGroup>
<Menu.Divider /> <Menu.Divider />
<Menu.SubMenu title={'自定义'}> <Menu.SubMenu disabled title={t('Customize')}>
<Menu.Item style={{ minWidth: 120 }}></Menu.Item> <Menu.Item style={{ minWidth: 120 }}>{t('Function')}</Menu.Item>
<Menu.Item></Menu.Item> <Menu.Item>{t('Popup form')}</Menu.Item>
<Menu.Item></Menu.Item> <Menu.Item>{t('Flexible popup')}</Menu.Item>
</Menu.SubMenu> </Menu.SubMenu>
</Menu> </Menu>
} }
@ -1264,7 +1270,7 @@ function AddActionButton() {
type={'dashed'} type={'dashed'}
icon={<SettingOutlined />} icon={<SettingOutlined />}
> >
{t('Configure actions')}
</Button> </Button>
</Dropdown> </Dropdown>
); );
@ -1451,6 +1457,8 @@ const fieldsToSortColumns = (fields: any[]) => {
Table.Filter = observer((props: any) => { Table.Filter = observer((props: any) => {
const { service } = useTable(); const { service } = useTable();
const { fieldNames = [] } = props; const { fieldNames = [] } = props;
const compile = useCompile();
const { t } = useTranslation();
const { schema, DesignableBar } = useDesignable(); const { schema, DesignableBar } = useDesignable();
const form = useMemo(() => createForm(), []); const form = useMemo(() => createForm(), []);
const { fields = [] } = useCollectionContext(); const { fields = [] } = useCollectionContext();
@ -1498,7 +1506,7 @@ Table.Filter = observer((props: any) => {
}); });
}} }}
> >
{t('Submit')}
</Submit> </Submit>
</FormButtonGroup> </FormButtonGroup>
</FormProvider> </FormProvider>
@ -1506,7 +1514,7 @@ Table.Filter = observer((props: any) => {
} }
> >
<Button icon={<IconPicker type={icon} />}> <Button icon={<IconPicker type={icon} />}>
{count > 0 ? `${count} 个筛选项` : schema.title} {count > 0 ? t('{{count}} filter items', { count }) : compile(schema.title)}
<DesignableBar /> <DesignableBar />
</Button> </Button>
</Popover> </Popover>
@ -1514,6 +1522,7 @@ Table.Filter = observer((props: any) => {
}); });
Table.Filter.DesignableBar = () => { Table.Filter.DesignableBar = () => {
const { t } = useTranslation();
const { schema, remove, refresh, insertAfter } = useDesignable(); const { schema, remove, refresh, insertAfter } = useDesignable();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const displayed = useDisplayedMapContext(); const displayed = useDisplayedMapContext();
@ -1542,7 +1551,7 @@ Table.Filter.DesignableBar = () => {
}} }}
overlay={ overlay={
<Menu> <Menu>
<Menu.ItemGroup title={'可筛选字段'}> <Menu.ItemGroup title={t('Filterable fields')}>
{fields {fields
.filter((collectionField) => { .filter((collectionField) => {
const option = interfaces.get(collectionField.interface); const option = interfaces.get(collectionField.interface);
@ -1576,7 +1585,7 @@ Table.Filter.DesignableBar = () => {
<Menu.Item <Menu.Item
onClick={async (e) => { onClick={async (e) => {
setVisible(false); setVisible(false);
const values = await FormDialog('编辑按钮', () => { const values = await FormDialog(t('Edit button'), () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
<SchemaField <SchemaField
@ -1585,14 +1594,14 @@ Table.Filter.DesignableBar = () => {
properties: { properties: {
title: { title: {
type: 'string', type: 'string',
title: '按钮名称', title: t('Display name'),
required: true, required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
icon: { icon: {
type: 'string', type: 'string',
title: '按钮图标', title: t('Icon'),
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'IconPicker', 'x-component': 'IconPicker',
}, },
@ -1615,7 +1624,7 @@ Table.Filter.DesignableBar = () => {
refresh(); refresh();
}} }}
> >
{t('Edit button')}
</Menu.Item> </Menu.Item>
<Menu.Divider /> <Menu.Divider />
<Menu.Item <Menu.Item
@ -1630,7 +1639,7 @@ Table.Filter.DesignableBar = () => {
setVisible(false); setVisible(false);
}} }}
> >
{t('Hide')}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }
@ -1644,6 +1653,7 @@ Table.Filter.DesignableBar = () => {
}; };
Table.ExportActionDesignableBar = () => { Table.ExportActionDesignableBar = () => {
const { t } = useTranslation();
const { schema, remove, refresh, insertAfter } = useDesignable(); const { schema, remove, refresh, insertAfter } = useDesignable();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const displayed = useDisplayedMapContext(); const displayed = useDisplayedMapContext();
@ -1672,7 +1682,7 @@ Table.ExportActionDesignableBar = () => {
}} }}
overlay={ overlay={
<Menu> <Menu>
<Menu.ItemGroup title={'导出字段'}> <Menu.ItemGroup title={t('Export fields')}>
{fields.map((collectionField) => ( {fields.map((collectionField) => (
<SwitchMenuItem <SwitchMenuItem
title={collectionField?.uiSchema?.title} title={collectionField?.uiSchema?.title}
@ -1700,7 +1710,7 @@ Table.ExportActionDesignableBar = () => {
<Menu.Item <Menu.Item
onClick={async (e) => { onClick={async (e) => {
setVisible(false); setVisible(false);
const values = await FormDialog('编辑按钮', () => { const values = await FormDialog(t('Edit button'), () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
<SchemaField <SchemaField
@ -1709,14 +1719,14 @@ Table.ExportActionDesignableBar = () => {
properties: { properties: {
title: { title: {
type: 'string', type: 'string',
title: '按钮名称', title: t('Display name'),
required: true, required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
icon: { icon: {
type: 'string', type: 'string',
title: '按钮图标', title: t('Icon'),
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'IconPicker', 'x-component': 'IconPicker',
}, },
@ -1739,7 +1749,7 @@ Table.ExportActionDesignableBar = () => {
refresh(); refresh();
}} }}
> >
{t('Edit button')}
</Menu.Item> </Menu.Item>
<Menu.Divider /> <Menu.Divider />
<Menu.Item <Menu.Item
@ -1754,7 +1764,7 @@ Table.ExportActionDesignableBar = () => {
setVisible(false); setVisible(false);
}} }}
> >
{t('Hide')}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }
@ -1769,9 +1779,10 @@ Table.ExportActionDesignableBar = () => {
Table.Operation = observer((props: any) => { Table.Operation = observer((props: any) => {
const { designable, schema } = useDesignable(); const { designable, schema } = useDesignable();
const { t } = useTranslation();
return ( return (
<div className={'nb-table-column'}> <div className={'nb-table-column'}>
{t('Operations')}
<Table.Operation.DesignableBar path={props.path} /> <Table.Operation.DesignableBar path={props.path} />
</div> </div>
); );
@ -1788,6 +1799,7 @@ Table.Operation.Cell = observer((props: any) => {
}); });
Table.Operation.DesignableBar = () => { Table.Operation.DesignableBar = () => {
const { t } = useTranslation();
const { schema: columnSchema } = useDesignable(); const { schema: columnSchema } = useDesignable();
const groupSchema = Object.values(columnSchema.properties || {}).shift(); const groupSchema = Object.values(columnSchema.properties || {}).shift();
const groupPath = getSchemaPath(groupSchema); const groupPath = getSchemaPath(groupSchema);
@ -1821,11 +1833,11 @@ Table.Operation.DesignableBar = () => {
}} }}
overlay={ overlay={
<Menu> <Menu>
<Menu.ItemGroup title={'启用操作'}> <Menu.ItemGroup title={t('Enable actions')}>
{[ {[
{ title: '查看', name: 'view' }, { title: t('View'), name: 'view' },
{ title: '编辑', name: 'update' }, { title: t('Edit'), name: 'update' },
{ title: '删除', name: 'destroy' }, { title: t('Delete'), name: 'destroy' },
].map((item) => ( ].map((item) => (
<SwitchMenuItem <SwitchMenuItem
key={item.name} key={item.name}
@ -1845,10 +1857,12 @@ Table.Operation.DesignableBar = () => {
))} ))}
</Menu.ItemGroup> </Menu.ItemGroup>
<Menu.Divider /> <Menu.Divider />
<Menu.SubMenu disabled title={'自定义'}> <Menu.SubMenu disabled title={t('Customize')}>
<Menu.Item style={{ minWidth: 120 }}></Menu.Item> <Menu.Item style={{ minWidth: 120 }}>
<Menu.Item></Menu.Item> {t('Function')}
<Menu.Item></Menu.Item> </Menu.Item>
<Menu.Item>{t('Popup form')}</Menu.Item>
<Menu.Item>{t('Flexible popup')}</Menu.Item>
</Menu.SubMenu> </Menu.SubMenu>
</Menu> </Menu>
} }
@ -1864,6 +1878,7 @@ Table.Operation.DesignableBar = () => {
Table.Action = () => null; Table.Action = () => null;
Table.Action.DesignableBar = () => { Table.Action.DesignableBar = () => {
const { t } = useTranslation();
const { schema, remove, refresh, insertAfter } = useDesignable(); const { schema, remove, refresh, insertAfter } = useDesignable();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const isPopup = Object.keys(schema.properties || {}).length > 0; const isPopup = Object.keys(schema.properties || {}).length > 0;
@ -1894,7 +1909,7 @@ Table.Action.DesignableBar = () => {
<Menu.Item <Menu.Item
onClick={async (e) => { onClick={async (e) => {
setVisible(false); setVisible(false);
const values = await FormDialog('编辑按钮', () => { const values = await FormDialog(t('Edit button'), () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
<SchemaField <SchemaField
@ -1903,14 +1918,14 @@ Table.Action.DesignableBar = () => {
properties: { properties: {
title: { title: {
type: 'string', type: 'string',
title: '按钮名称', title: t('Display name'),
required: true, required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
icon: { icon: {
type: 'string', type: 'string',
title: '按钮图标', title: t('Icon'),
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'IconPicker', 'x-component': 'IconPicker',
}, },
@ -1933,41 +1948,42 @@ Table.Action.DesignableBar = () => {
refresh(); refresh();
}} }}
> >
{t('Edit button')}
</Menu.Item> </Menu.Item>
{isPopup && ( {isPopup && (
<Menu.Item> <Menu.Item>
{' '} <Trans t={t}>
<Select Open in
bordered={false} <Select
size={'small'} bordered={false}
defaultValue={popupComponent} size={'small'}
style={{ width: 100 }} defaultValue={popupComponent}
onChange={(value) => { style={{ width: 100 }}
const s = Object.values(schema.properties).shift(); onChange={(value) => {
s['x-component'] = value; const s = Object.values(schema.properties).shift();
refresh(); s['x-component'] = value;
updateSchema(s); refresh();
// const f = field.query(getSchemaPath(s)).take() updateSchema(s);
// console.log('fffffff', { schema, f }); // const f = field.query(getSchemaPath(s)).take()
}} // console.log('fffffff', { schema, f });
> }}
<Select.Option value={'Action.Modal'}> >
<Select.Option value={'Action.Modal'}>
</Select.Option> Modal
<Select.Option value={'Action.Drawer'}> </Select.Option>
<Select.Option value={'Action.Drawer'}>
</Select.Option> Drawer
<Select.Option value={'Action.Window'}> </Select.Option>
<Select.Option value={'Action.Window'}>
</Select.Option> Window
</Select>{' '} </Select.Option>
</Select>
</Trans>
</Menu.Item> </Menu.Item>
)} )}
{!inActionBar && ( {!inActionBar && (
<Menu.Item> <Menu.Item>
&nbsp;&nbsp; {t('Trigger on click the table row')} &nbsp;&nbsp;
<Switch size={'small'} defaultChecked /> <Switch size={'small'} defaultChecked />
</Menu.Item> </Menu.Item>
)} )}
@ -1984,7 +2000,7 @@ Table.Action.DesignableBar = () => {
setVisible(false); setVisible(false);
}} }}
> >
{t('Hide')}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }
@ -2052,6 +2068,7 @@ Table.Cell = observer((props: any) => {
Table.Column = observer((props: any) => { Table.Column = observer((props: any) => {
const collectionField = useContext(CollectionFieldContext); const collectionField = useContext(CollectionFieldContext);
const { schema, DesignableBar } = useDesignable(); const { schema, DesignableBar } = useDesignable();
const compile = useCompile();
const displayed = useDisplayedMapContext(); const displayed = useDisplayedMapContext();
useEffect(() => { useEffect(() => {
if (collectionField?.name) { if (collectionField?.name) {
@ -2060,7 +2077,7 @@ Table.Column = observer((props: any) => {
}, [collectionField, schema]); }, [collectionField, schema]);
return ( return (
<div className={'nb-table-column'}> <div className={'nb-table-column'}>
{schema.title || collectionField?.uiSchema?.title} {compile(schema.title || collectionField?.uiSchema?.title)}
<DesignableBar /> <DesignableBar />
</div> </div>
); );
@ -2068,6 +2085,7 @@ Table.Column = observer((props: any) => {
Table.Column.DesignableBar = () => { Table.Column.DesignableBar = () => {
const field = useField(); const field = useField();
const { t } = useTranslation();
const { service, refresh: refreshTable } = useTable(); const { service, refresh: refreshTable } = useTable();
// const fieldSchema = useFieldSchema(); // const fieldSchema = useFieldSchema();
const { schema, remove, refresh, insertAfter } = useDesignable(); const { schema, remove, refresh, insertAfter } = useDesignable();
@ -2098,7 +2116,7 @@ Table.Column.DesignableBar = () => {
<Menu.Item <Menu.Item
onClick={async (e) => { onClick={async (e) => {
setVisible(false); setVisible(false);
const values = await FormDialog('自定义列名称', () => { const values = await FormDialog(t('Custom name'), () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
<SchemaField <SchemaField
@ -2107,14 +2125,14 @@ Table.Column.DesignableBar = () => {
properties: { properties: {
fieldName: { fieldName: {
type: 'string', type: 'string',
title: '原字段名称', title: t('Original name'),
'x-read-pretty': true, 'x-read-pretty': true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
title: { title: {
type: 'string', type: 'string',
title: '自定义列名称', title: t('Custom name'),
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
@ -2139,7 +2157,7 @@ Table.Column.DesignableBar = () => {
}); });
}} }}
> >
{t('Custom column name')}
</Menu.Item> </Menu.Item>
{collectionField?.interface === 'linkTo' && ( {collectionField?.interface === 'linkTo' && (
<Menu.Item> <Menu.Item>
@ -2149,14 +2167,14 @@ Table.Column.DesignableBar = () => {
justifyContent: 'space-between', justifyContent: 'space-between',
}} }}
> >
{' '} {t('Label field')}{' '}
<Select <Select
value={ value={
schema?.['x-component-props']?.['fieldNames']?.[ schema?.['x-component-props']?.['fieldNames']?.[
'label' 'label'
] ]
} }
placeholder={'默认为 ID 字段'} placeholder={t('Default is the ID field')}
onChange={async (value) => { onChange={async (value) => {
set( set(
schema['x-component-props'], schema['x-component-props'],
@ -2203,7 +2221,7 @@ Table.Column.DesignableBar = () => {
// await removeSchema(s); // await removeSchema(s);
}} }}
> >
{t('Hide')}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }
@ -2226,6 +2244,8 @@ Table.SortHandle = observer((props: any) => {
}); });
Table.DesignableBar = observer((props) => { Table.DesignableBar = observer((props) => {
const { t } = useTranslation();
const compile = useCompile();
const field = useField(); const field = useField();
const { schema, refresh, deepRemove } = useDesignable(); const { schema, refresh, deepRemove } = useDesignable();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
@ -2239,7 +2259,7 @@ Table.DesignableBar = observer((props) => {
return ( return (
<div className={cls('designable-bar', { active: visible })}> <div className={cls('designable-bar', { active: visible })}>
<div className={'designable-info'}> <div className={'designable-info'}>
{collection?.title || collection?.name} {compile(collection?.title || collection?.name)}
</div> </div>
<span <span
onClick={(e) => { onClick={(e) => {
@ -2269,7 +2289,7 @@ Table.DesignableBar = observer((props) => {
}} }}
> >
<div className={'nb-space-between'}> <div className={'nb-space-between'}>
{' '} {t('Display order number')}{' '}
<Switch <Switch
size={'small'} size={'small'}
checked={field.componentProps.showIndex} checked={field.componentProps.showIndex}
@ -2289,7 +2309,8 @@ Table.DesignableBar = observer((props) => {
}} }}
> >
<div className={'nb-space-between'}> <div className={'nb-space-between'}>
{' '} {t('Enable drag and drop sorting')}
&nbsp;&nbsp;
<Switch <Switch
size={'small'} size={'small'}
checked={field.componentProps.dragSort} checked={field.componentProps.dragSort}
@ -2314,80 +2335,88 @@ Table.DesignableBar = observer((props) => {
}; };
}, },
); );
const values = await FormDialog('设置默认排序', () => { const values = await FormDialog(
return ( t('Set default sorting rules'),
<FormLayout layout={'vertical'}> () => {
<SchemaField return (
schema={{ <FormLayout layout={'vertical'}>
type: 'object', <SchemaField
properties: { schema={{
defaultSort: { type: 'object',
type: 'array', properties: {
'x-component': 'ArrayItems', defaultSort: {
'x-decorator': 'FormItem', type: 'array',
items: { 'x-component': 'ArrayItems',
type: 'object', 'x-decorator': 'FormItem',
properties: { items: {
space: { type: 'object',
type: 'void', properties: {
'x-component': 'Space', space: {
properties: { type: 'void',
sort: { 'x-component': 'Space',
type: 'void', properties: {
'x-decorator': 'FormItem', sort: {
'x-component': type: 'void',
'ArrayItems.SortHandle', 'x-decorator': 'FormItem',
}, 'x-component':
field: { 'ArrayItems.SortHandle',
type: 'string', },
'x-decorator': 'FormItem', field: {
'x-component': 'Select', type: 'string',
enum: fieldsToSortColumns(fields), 'x-decorator': 'FormItem',
'x-component-props': { 'x-component': 'Select',
style: { enum: fieldsToSortColumns(
width: 260, fields,
),
'x-component-props': {
style: {
width: 260,
},
}, },
}, },
}, direction: {
direction: { type: 'string',
type: 'string', 'x-decorator': 'FormItem',
'x-decorator': 'FormItem', 'x-component': 'Radio.Group',
'x-component': 'Radio.Group', 'x-component-props': {
'x-component-props': { optionType: 'button',
optionType: 'button',
},
enum: [
{ label: '正序', value: 'asc' },
{
label: '倒序',
value: 'desc',
}, },
], enum: [
}, {
remove: { label: t('ASC'),
type: 'void', value: 'asc',
'x-decorator': 'FormItem', },
'x-component': {
'ArrayItems.Remove', label: t('DESC'),
value: 'desc',
},
],
},
remove: {
type: 'void',
'x-decorator': 'FormItem',
'x-component':
'ArrayItems.Remove',
},
}, },
}, },
}, },
}, },
}, properties: {
properties: { add: {
add: { type: 'void',
type: 'void', title: t('Add sort field'),
title: '添加排序字段', 'x-component': 'ArrayItems.Addition',
'x-component': 'ArrayItems.Addition', },
}, },
}, },
}, },
}, }}
}} />
/> </FormLayout>
</FormLayout> );
); },
}).open({ ).open({
initialValues: { initialValues: {
defaultSort, defaultSort,
}, },
@ -2404,14 +2433,14 @@ Table.DesignableBar = observer((props) => {
console.log('defaultSort', sort); console.log('defaultSort', sort);
}} }}
> >
{t('Set default sorting rules')}
</Menu.Item> </Menu.Item>
)} )}
<Menu.Item <Menu.Item
key={'defaultFilter'} key={'defaultFilter'}
onClick={async () => { onClick={async () => {
const { defaultFilter } = await FormDialog( const { defaultFilter } = await FormDialog(
'设置数据范围', t('Set the data scope'),
() => { () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
@ -2443,40 +2472,47 @@ Table.DesignableBar = observer((props) => {
setVisible(false); setVisible(false);
}} }}
> >
{t('Set the data scope')}
</Menu.Item> </Menu.Item>
<Menu.Item key={'defaultPageSize'}> <Menu.Item key={'defaultPageSize'}>
{' '} <Trans>
<Select {'Display '}
bordered={false} <Select
size={'small'} bordered={false}
onChange={(value) => { size={'small'}
const componentProps = schema['x-component-props'] || {}; onChange={(value) => {
set(componentProps, 'pagination.defaultPageSize', value); const componentProps =
set(componentProps, 'pagination.pageSize', value); schema['x-component-props'] || {};
schema['x-component-props'] = componentProps; set(
field.componentProps.pagination.pageSize = value; componentProps,
field.componentProps.pagination.defaultPageSize = value; 'pagination.defaultPageSize',
refresh(); value,
updateSchema(schema); );
setVisible(false); set(componentProps, 'pagination.pageSize', value);
}} schema['x-component-props'] = componentProps;
defaultValue={defaultPageSize} field.componentProps.pagination.pageSize = value;
> field.componentProps.pagination.defaultPageSize = value;
<Select.Option value={10}>10</Select.Option> refresh();
<Select.Option value={20}>20</Select.Option> updateSchema(schema);
<Select.Option value={50}>50</Select.Option> setVisible(false);
<Select.Option value={100}>100</Select.Option> }}
</Select>{' '} defaultValue={defaultPageSize}
>
<Select.Option value={10}>10</Select.Option>
<Select.Option value={20}>20</Select.Option>
<Select.Option value={50}>50</Select.Option>
<Select.Option value={100}>100</Select.Option>
</Select>
{' items per page'}
</Trans>
</Menu.Item> </Menu.Item>
<Menu.Divider /> <Menu.Divider />
<Menu.Item <Menu.Item
key={'delete'} key={'delete'}
onClick={async () => { onClick={async () => {
Modal.confirm({ Modal.confirm({
title: '删除区块', title: t('Delete block'),
content: '删除后无法恢复,确定要删除吗?', content: t('Are you sure you want to delete it?'),
onOk: async () => { onOk: async () => {
const removed = deepRemove(); const removed = deepRemove();
// console.log({ removed }) // console.log({ removed })
@ -2487,7 +2523,7 @@ Table.DesignableBar = observer((props) => {
}); });
}} }}
> >
{t('Delete')}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }

View File

@ -26,6 +26,8 @@ import { DragHandle, SortableItem } from '../../components/Sortable';
import { DndContext, DragOverlay } from '@dnd-kit/core'; import { DndContext, DragOverlay } from '@dnd-kit/core';
import { FormDialog, FormLayout } from '@formily/antd'; import { FormDialog, FormLayout } from '@formily/antd';
import IconPicker from '../../components/icon-picker'; import IconPicker from '../../components/icon-picker';
import { useCompile } from '../../hooks/useCompile';
import { useTranslation } from 'react-i18next';
const useTabs = ({ singleton }) => { const useTabs = ({ singleton }) => {
const tabsField = useField(); const tabsField = useField();
@ -65,6 +67,7 @@ export const Tabs: any = observer((props: any) => {
const tabs = useTabs({ singleton }); const tabs = useTabs({ singleton });
const [dragOverlayContent, setDragOverlayContent] = useState(''); const [dragOverlayContent, setDragOverlayContent] = useState('');
const { createSchema, removeSchema, updateSchema } = useClient(); const { createSchema, removeSchema, updateSchema } = useClient();
const { t } = useTranslation();
const moveToAfter = (path1, path2) => { const moveToAfter = (path1, path2) => {
if (!path1 || !path2) { if (!path1 || !path2) {
@ -126,7 +129,7 @@ export const Tabs: any = observer((props: any) => {
type={'dashed'} type={'dashed'}
icon={<PlusOutlined />} icon={<PlusOutlined />}
onClick={async () => { onClick={async () => {
const values = await FormDialog('添加标签页', () => { const values = await FormDialog('Add tab', () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
<SchemaField <SchemaField
@ -135,14 +138,14 @@ export const Tabs: any = observer((props: any) => {
properties: { properties: {
title: { title: {
type: 'string', type: 'string',
title: '标签名称', title: t('Display name'),
required: true, required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
icon: { icon: {
type: 'string', type: 'string',
title: '标签图标', title: t('Icon'),
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'IconPicker', 'x-component': 'IconPicker',
}, },
@ -179,7 +182,7 @@ export const Tabs: any = observer((props: any) => {
await createSchema(data); await createSchema(data);
}} }}
> >
{t('Add tab')}
</Button> </Button>
) )
} }
@ -207,17 +210,19 @@ export const Tabs: any = observer((props: any) => {
Tabs.TabPane = observer((props: any) => { Tabs.TabPane = observer((props: any) => {
const { schema, DesignableBar } = useDesignable(); const { schema, DesignableBar } = useDesignable();
const compile = useCompile();
const title = compile(schema.title);
return ( return (
<SortableItem <SortableItem
id={schema.name} id={schema.name}
data={{ data={{
title: schema.title, title,
path: getSchemaPath(schema), path: getSchemaPath(schema),
}} }}
> >
<div className={'nb-tab-pane'}> <div className={'nb-tab-pane'}>
<IconPicker type={props.icon} /> <IconPicker type={props.icon} />
{schema.title} <DesignableBar /> {title} <DesignableBar />
</div> </div>
</SortableItem> </SortableItem>
); );
@ -228,6 +233,7 @@ Tabs.DesignableBar = () => {
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const field = useField(); const field = useField();
const { createSchema, removeSchema, updateSchema } = useClient(); const { createSchema, removeSchema, updateSchema } = useClient();
const { t } = useTranslation();
return ( return (
<div className={cls('designable-bar', { active: visible })}> <div className={cls('designable-bar', { active: visible })}>
<span <span
@ -256,7 +262,7 @@ Tabs.DesignableBar = () => {
updateSchema(schema); updateSchema(schema);
}} }}
> >
<span style={{ marginRight: 24 }}></span>{' '} {t('Disable tabs')} <span style={{ marginRight: 24 }}></span>{' '}
<Switch <Switch
size={'small'} size={'small'}
checked={!!field.componentProps.singleton} checked={!!field.componentProps.singleton}
@ -278,6 +284,7 @@ Tabs.TabPane.DesignableBar = () => {
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const field = useField(); const field = useField();
const { createSchema, removeSchema, updateSchema } = useClient(); const { createSchema, removeSchema, updateSchema } = useClient();
const { t } = useTranslation();
return ( return (
<div className={cls('designable-bar', { active: visible })}> <div className={cls('designable-bar', { active: visible })}>
<span <span
@ -299,7 +306,7 @@ Tabs.TabPane.DesignableBar = () => {
<Menu.Item <Menu.Item
onClick={async () => { onClick={async () => {
setVisible(false); setVisible(false);
const values = await FormDialog('编辑标签页', () => { const values = await FormDialog(t('Edit tab'), () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
<SchemaField <SchemaField
@ -308,14 +315,14 @@ Tabs.TabPane.DesignableBar = () => {
properties: { properties: {
title: { title: {
type: 'string', type: 'string',
title: '标签名称', title: t('Display name'),
required: true, required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
icon: { icon: {
type: 'string', type: 'string',
title: '标签图标', title: t('Icon'),
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'IconPicker', 'x-component': 'IconPicker',
}, },
@ -339,15 +346,15 @@ Tabs.TabPane.DesignableBar = () => {
updateSchema(schema); updateSchema(schema);
}} }}
> >
{t('Edit tab')}
</Menu.Item> </Menu.Item>
<Menu.Divider /> <Menu.Divider />
<Menu.Item <Menu.Item
onClick={async () => { onClick={async () => {
setVisible(false); setVisible(false);
Modal.confirm({ Modal.confirm({
title: '删除标签页', title: t('Delete tab'),
content: '删除后无法恢复,确定要删除吗?', content: t('Are you sure you want to delete it?'),
onOk: async () => { onOk: async () => {
const data = remove(); const data = remove();
await removeSchema(data); await removeSchema(data);
@ -355,7 +362,7 @@ Tabs.TabPane.DesignableBar = () => {
}); });
}} }}
> >
{t('Delete')}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
} }

View File

@ -28,6 +28,7 @@ import DownloadOutlined from '@ant-design/icons/DownloadOutlined';
import './style.less'; import './style.less';
import cls from 'classnames'; import cls from 'classnames';
import { saveAs } from 'file-saver'; import { saveAs } from 'file-saver';
import { useTranslation } from 'react-i18next';
const toArr = (value) => { const toArr = (value) => {
if (!isValid(value)) { if (!isValid(value)) {
@ -301,6 +302,7 @@ Upload.Attachment = connect(
const images = fileList; const images = fileList;
const [photoIndex, setPhotoIndex] = useState(0); const [photoIndex, setPhotoIndex] = useState(0);
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const { t } = useTranslation();
useEffect(() => { useEffect(() => {
if (sync) { if (sync) {
setFileList(toFileList(value)); setFileList(toFileList(value));
@ -346,7 +348,7 @@ Upload.Attachment = connect(
onClick={handleClick} onClick={handleClick}
> >
{file.status === 'uploading' {file.status === 'uploading'
? '上传中...' ? t('Uploading')
: file.title} : file.title}
</a> </a>
</span> </span>
@ -427,7 +429,7 @@ Upload.Attachment = connect(
{!disabled && (multiple || toArr(value).length < 1) && ( {!disabled && (multiple || toArr(value).length < 1) && (
<span> <span>
<PlusOutlined /> <PlusOutlined />
<br /> <br /> {t('Upload')}
</span> </span>
)} )}
</AntdUpload> </AntdUpload>

View File

@ -11,11 +11,12 @@ const program = new commander.Command(packageJson.name)
.version(packageJson.version) .version(packageJson.version)
.option('--simple', 'create nocobase app without install dependencies') .option('--simple', 'create nocobase app without install dependencies')
.option('--quickstart', 'create quickstart nocobase app') .option('--quickstart', 'create quickstart nocobase app')
.option('--lang [lang]', 'language settings')
.arguments('<project-directory>') .arguments('<project-directory>')
.usage(`${chalk.green('<project-directory>')}`) .usage(`${chalk.green('<project-directory>')}`)
.action(async (directory, options) => { .action(async (directory, options) => {
console.log( console.log(
`Creating a new Nocobase application at ${chalk.green(directory)}.`, `Creating a new NocoBase application at ${chalk.green(directory)}.`,
); );
console.log('Creating files.'); console.log('Creating files.');
@ -65,7 +66,7 @@ const program = new commander.Command(packageJson.name)
if (options.quickstart) { if (options.quickstart) {
// Using Sqlite as Database // Using Sqlite as Database
const prefix = chalk.yellow('Nocobase init'); const prefix = chalk.yellow('NocoBase init');
const initLoader = ora(prefix).start(); const initLoader = ora(prefix).start();
try { try {
@ -76,7 +77,7 @@ const program = new commander.Command(packageJson.name)
.join(' ')}`; .join(' ')}`;
}; };
const init = runInit(fullPath); const init = runInit(fullPath, ['--import-demo', `--lang=${options.lang||'en-US'}`]);
init.stderr.on('data', initLog); init.stderr.on('data', initLog);
init.stdout.on('data', initLog); init.stdout.on('data', initLog);
await init; await init;

View File

@ -23,6 +23,10 @@ export function runStart(path) {
return runYarn(path, ['run', 'start']); return runYarn(path, ['run', 'start']);
} }
export function runInit(path) { export function runInit(path, args: any[] = []) {
return runYarn(path, ['run', 'nocobase', 'init', hasYarn() ? '--import-demo' : '-- --import-demo']); if (!hasYarn()) {
args.unshift('--');
}
console.log('run init', args);
return runYarn(path, ['run', 'nocobase', 'init', ...args]);
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
DELETE FROM "collections";
INSERT INTO "collections" ("name", "logging", "title", "privilege", "sortable", "options", "sort", "created_at", "updated_at", "ui_schema_key") VALUES
('users', '1', '{{t("Users")}}', 'undelete', '"sort"', '{"createdBy":false,"updatedBy":false}', 2, '2021-09-02 15:07:56.914+00', '2021-09-15 00:59:20.676+00', NULL),
('t_fsveob6p269', '1', 'Customers', NULL, '"sort"', '{}', 5, '2021-09-12 01:05:52.722+00', '2021-09-18 04:15:23.113+00', NULL),
('t_geso7fru7a9', '1', 'Orders', NULL, '"sort"', '{}', 4, '2021-09-12 01:06:05.19+00', '2021-09-18 04:15:42.566+00', NULL),
('t_2uhu4szs1kq', '1', 'Tasks', NULL, '"sort"', '{}', 3, '2021-09-03 08:17:30.495+00', '2021-09-18 04:15:42.572+00', NULL);

View File

@ -0,0 +1,52 @@
DELETE FROM "fields";
INSERT INTO "fields" ("key", "name", "interface", "data_type", "privilege", "state", "options", "sort", "created_at", "updated_at", "parent_key", "collection_name", "ui_schema_key", "reverse_key") VALUES
('aun85p76s0v', 'f_l8uuiwcnlw9', 'textarea', 'text', NULL, 1, '{}', 8, '2021-09-12 08:18:46.92+00', '2021-09-12 08:18:46.935+00', NULL, 't_2uhu4szs1kq', 'c2pallk0lge', NULL),
('sj5p2y4ac06', '70enejazumv', 'updatedAt', 'date', 'undelete', 1, '{"field":"updated_at"}', 2, '2021-09-03 08:17:30.543+00', '2021-09-03 08:17:30.555+00', NULL, 't_2uhu4szs1kq', 'lchrxtjm3d5', NULL),
('1fvb1qre4ut', 'baa90jd887d', 'updatedBy', 'belongsTo', 'undelete', 1, '{"target":"users","foreignKey":"updated_by_id"}', 3, '2021-09-03 08:17:30.579+00', '2021-09-18 04:16:11.834+00', NULL, 't_2uhu4szs1kq', 'd3zwndj0gdt', NULL),
('78h1q25dt1h', 'wtojfhp8nwl', 'createdBy', 'belongsTo', 'undelete', 1, '{"target":"users","foreignKey":"created_by_id"}', 2, '2021-09-03 08:17:30.563+00', '2021-09-18 04:16:11.834+00', NULL, 't_2uhu4szs1kq', 'uxcvj4473q9', NULL),
('s30o2rkxcht', 'f_hwenour8ara', 'string', 'string', NULL, 1, '{}', 5, '2021-09-03 08:18:44.958+00', '2021-09-03 08:18:44.975+00', NULL, 't_2uhu4szs1kq', 'c6qmyf4uzl9', NULL),
('e76mvcf7a1r', 'f_hznqtmqljb2', 'datetime', 'date', NULL, 1, '{}', 6, '2021-09-03 08:18:44.984+00', '2021-09-03 08:18:45+00', NULL, 't_2uhu4szs1kq', '7u0cu6nfu44', NULL),
('c385a9rhd6d', 'f_u4i0jrp4uo6', 'radioGroup', 'string', NULL, 1, '{}', 7, '2021-09-03 08:18:45.009+00', '2021-09-03 08:18:45.028+00', NULL, 't_2uhu4szs1kq', '5de6xmem8vw', NULL),
('8ms3ivkxaku', '8ke6k4er30w', 'createdAt', 'date', 'undelete', 1, '{"field":"created_at"}', 1, '2021-09-03 08:17:30.522+00', '2021-09-03 08:17:30.536+00', NULL, 't_2uhu4szs1kq', 'y9y4mgjy6ot', NULL),
('ymwwuuje5cf', 'vygajfyejo0', 'updatedBy', 'belongsTo', 'undelete', 1, '{"target":"users","foreignKey":"updated_by_id"}', 3, '2021-09-12 01:05:52.791+00', '2021-09-18 04:16:11.834+00', NULL, 't_fsveob6p269', 'g6fgjhxwqdt', NULL),
('kru7yomwffj', 'f_kk4nspj1i28', 'textarea', 'text', NULL, 1, '{}', 13, '2021-09-12 01:13:43.315+00', '2021-09-14 02:50:54.757+00', NULL, 't_fsveob6p269', 'ic1crmml3ww', NULL),
('xjnnqtckv6o', 'f_x9kmuovlm79', 'radioGroup', 'string', NULL, 1, '{}', 11, '2021-09-12 01:13:43.241+00', '2021-09-14 02:50:54.738+00', NULL, 't_fsveob6p269', 'cntdgyegzk1', NULL),
('hfry2ook2w4', 'f_n6snaop9d4y', 'textarea', 'string', NULL, 1, '{}', 10, '2021-09-12 01:17:01.062+00', '2021-09-14 02:50:54.728+00', NULL, 't_fsveob6p269', 'o21p57oi4ci', NULL),
('zobi3o3dqzp', 'f_61znsfh307m', 'select', 'string', NULL, 1, '{}', 8, '2021-09-12 01:17:01.042+00', '2021-09-14 02:50:54.697+00', NULL, 't_fsveob6p269', 'zncs2fmi3md', NULL),
('gjc3mks1xvj', 'f_bysgds7j36p', 'datetime', 'date', NULL, 1, '{}', 9, '2021-09-12 01:13:43.26+00', '2021-09-14 02:50:54.713+00', NULL, 't_fsveob6p269', 'wy9l25k1k8n', NULL),
('lie1tta3peq', 'f_3det6kr3vn8', 'phone', 'string', NULL, 1, '{}', 7, '2021-09-12 01:13:43.221+00', '2021-09-14 02:50:54.686+00', NULL, 't_fsveob6p269', 'd0vxh9zspr0', NULL),
('9cpd377df7t', 'f_tdoe0q00xc6', 'attachment', 'belongsToMany', NULL, 1, '{"target":"attachments","through":"t_jh7a28dsfzi","foreignKey":"f_xg3mysbjfra","otherKey":"f_gc7ppj0b7n1","sourceKey":"id","targetKey":"id"}', 12, '2021-09-12 01:13:43.292+00', '2021-09-12 08:41:20.155+00', NULL, 't_fsveob6p269', 'mz5lwuvl3ne', NULL),
('mnglxpxjqs4', 'f_vgw4f62h16e', 'string', 'string', NULL, 1, '{}', 6, '2021-09-12 01:13:43.204+00', '2021-09-12 01:13:43.215+00', NULL, 't_fsveob6p269', 'r9i0nuz6gaa', NULL),
('f_c8mes0kp1zt', 'f_c8mes0kp1zt', 'linkTo', 'belongsToMany', NULL, 1, '{"through":"t_bphcojtl8bx","foreignKey":"f_cn6a3p0wq2p","otherKey":"f_4x6u1waa8r6","sourceKey":"id","targetKey":"id","target":"t_geso7fru7a9"}', 5, '2021-09-12 01:09:26.259+00', '2021-09-12 01:13:43.195+00', NULL, 't_fsveob6p269', 'h73wygfixrg', 'nb09uq85geu'),
('bz701wfev96', 'xkllcgbvvmt', 'createdBy', 'belongsTo', 'undelete', 1, '{"target":"users","foreignKey":"created_by_id"}', 2, '2021-09-12 01:05:52.77+00', '2021-09-18 04:16:11.834+00', NULL, 't_fsveob6p269', 'si4x29vvkeb', NULL),
('mqcm1jb1ziw', '77trwzoaaei', 'createdAt', 'date', 'undelete', 1, '{"field":"created_at"}', 1, '2021-09-12 01:05:52.734+00', '2021-09-12 01:05:52.746+00', NULL, 't_fsveob6p269', '6zt8a6vfmeg', NULL),
('2abol7bhbmj', 'uahfxi6okcs', 'updatedAt', 'date', 'undelete', 1, '{"field":"updated_at"}', 2, '2021-09-12 01:05:52.753+00', '2021-09-12 01:05:52.765+00', NULL, 't_fsveob6p269', 'q1bwolkkq5w', NULL),
('19r6wt5b4m3', 'f_nlgk67tpdql', 'select', 'string', NULL, 1, '{}', 7, '2021-09-12 01:09:26.235+00', '2021-09-12 01:09:26.245+00', NULL, 't_geso7fru7a9', 'e5r226qqi20', NULL),
('nb09uq85geu', 'f_kkopbnfdd0v', 'linkTo', 'belongsToMany', NULL, 1, '{"through":"t_bphcojtl8bx","foreignKey":"f_4x6u1waa8r6","otherKey":"f_cn6a3p0wq2p","sourceKey":"id","targetKey":"id","target":"t_fsveob6p269"}', 6, '2021-09-12 01:09:26.221+00', '2021-09-12 01:09:26.278+00', NULL, 't_geso7fru7a9', 'kmx147xwkrp', 'f_c8mes0kp1zt'),
('nv0iw8wdxmz', '5bjnabydou3', 'createdAt', 'date', 'undelete', 1, '{"field":"created_at"}', 1, '2021-09-12 01:06:05.199+00', '2021-09-12 01:06:05.21+00', NULL, 't_geso7fru7a9', 'u1767w73vdn', NULL),
('lu1ibrb0yi2', '44fpkg5ga5n', 'updatedAt', 'date', 'undelete', 1, '{"field":"updated_at"}', 2, '2021-09-12 01:06:05.215+00', '2021-09-12 01:06:05.225+00', NULL, 't_geso7fru7a9', 'fmweeu589m9', NULL),
('fp2hcdcmxzm', 'qbe1q17p9kh', 'updatedBy', 'belongsTo', 'undelete', 1, '{"target":"users","foreignKey":"updated_by_id"}', 3, '2021-09-12 01:06:05.245+00', '2021-09-18 04:16:11.834+00', NULL, 't_geso7fru7a9', 'dzc486mzccr', NULL),
('qpme60wf4lt', '1jkpjg7vkgi', 'createdBy', 'belongsTo', 'undelete', 1, '{"target":"users","foreignKey":"created_by_id"}', 2, '2021-09-12 01:06:05.23+00', '2021-09-18 04:16:11.834+00', NULL, 't_geso7fru7a9', 'bi9n4qdvvk2', NULL),
('0qx4mudmft8', 'f_umjewufbyhj', 'datetime', 'date', NULL, 1, '{}', 5, '2021-09-12 01:09:26.202+00', '2021-09-12 01:09:26.215+00', NULL, 't_geso7fru7a9', 'yo6wjwgsfx2', NULL),
('ckyhgqq0jru', 'f_niymyj0no38', 'string', 'string', NULL, 1, '{}', 8, '2021-09-12 07:36:53.506+00', '2021-09-12 07:36:53.518+00', NULL, 't_geso7fru7a9', 'x6j2o6xw1by', NULL),
('f_80zv5suy9t6', 'email', 'email', 'string', 'undelete', 1, '{"unique":true}', 4, '2021-09-02 15:07:57.001+00', '2021-09-18 04:16:11.839+00', NULL, 'users', '5nqs5glgn5c', NULL),
('f_dpll75sxhu6', 'token', 'password', 'string', 'undelete', 0, '{"unique":true,"hidden":true}', 5, '2021-09-02 15:07:57.052+00', '2021-09-02 15:07:57.052+00', NULL, 'users', NULL, NULL),
('f_v3pog2zsalc', 'reset_token', 'password', 'string', 'undelete', 0, '{"unique":true,"hidden":true}', 6, '2021-09-02 15:07:57.059+00', '2021-09-02 15:07:57.059+00', NULL, 'users', NULL, NULL),
('f_6rhh0scyw9k', 'nickname', 'string', 'string', NULL, 1, '{}', 1, '2021-09-02 15:07:56.974+00', '2021-09-12 00:56:41.776+00', NULL, 'users', 'dtu7erjj0q8', NULL),
('f_j367murpzm9', 'password', 'password', 'password', 'undelete', 1, '{}', 2, '2021-09-02 15:07:57.029+00', '2021-09-18 04:16:11.834+00', NULL, 'users', 'a65exd8qcuo', NULL),
('lmls9t34zne', 'f_xmjn5cfl9co', 'textarea', 'text', NULL, 1, '{}', 10, '2021-09-02 22:28:32.838+00', '2021-09-02 22:28:32.855+00', NULL, NULL, 'hej1gpzthf3', NULL),
('sq3hj75dnmk', 'f_jdlw4uf6vfv', 'number', 'float', NULL, 1, '{}', 9, '2021-09-02 22:28:32.82+00', '2021-09-02 22:28:32.83+00', NULL, NULL, 'e5t9w7t3rxu', NULL),
('xwhnxsil7b1', 'f_n0snwtdes3r', 'chinaRegion', 'belongsToMany', NULL, 1, '{"target":"china_regions","targetKey":"code","through":"t_by1bsepp8p4","foreignKey":"f_vqbqkbjmucq","otherKey":"f_ux2lh2en547","sourceKey":"id"}', 8, '2021-09-02 22:28:32.8+00', '2021-09-02 22:28:32.814+00', NULL, NULL, '4an73nd4bxh', NULL),
('wolxy2l6xp2', 'f_jbqopur80lk', 'attachment', 'belongsToMany', NULL, 1, '{"target":"attachments","through":"t_mrsootcimgg","foreignKey":"f_moakulo9ak1","otherKey":"f_27xvqwj1o9s","sourceKey":"id","targetKey":"id"}', 7, '2021-09-02 22:28:32.777+00', '2021-09-02 22:28:32.792+00', NULL, NULL, '4blpztuxkha', NULL),
('2wpjada7upn', 'f_lov82rwvxgk', 'multipleSelect', 'json', NULL, 1, '{}', 6, '2021-09-02 22:27:03.764+00', '2021-09-02 22:27:03.776+00', NULL, NULL, 'exjytl34657', NULL),
('ybbxmw9ifwf', 'f_lwyvh77c122', 'string', 'string', NULL, 1, '{}', 5, '2021-09-02 22:27:03.741+00', '2021-09-02 22:27:03.758+00', NULL, NULL, 'xe5j6tk6y52', NULL),
('cy37tmd1aa5', '2e5im9hsrau', 'updatedAt', 'date', 'undelete', 1, '{"field":"updated_at"}', 2, '2021-09-02 22:24:30.263+00', '2021-09-02 22:24:30.282+00', NULL, NULL, 'ue90wlv7r26', NULL),
('9zmg4gsrbs0', 'ccdop3v4iqw', 'createdAt', 'date', 'undelete', 1, '{"field":"created_at"}', 1, '2021-09-02 22:24:30.239+00', '2021-09-02 22:24:30.255+00', NULL, NULL, 'qc0b0d2xfo4', NULL),
('f_1eni2gp5w3g', 'action_logs', 'linkTo', 'hasMany', NULL, 0, '{"target":"action_logs","title":"数据动态","foreignKey":"index","scope":{"collection_name":"t_n93qitmhzty"},"constraints":false}', 7, '2021-09-02 22:24:30.219+00', '2021-09-02 22:24:30.219+00', NULL, NULL, NULL, NULL),
('exfseozu257', '3s4pr34nuvz', 'updatedBy', 'belongsTo', 'undelete', 1, '{"target":"users","foreignKey":"updated_by_id"}', 3, '2021-09-12 01:06:55.172+00', '2021-09-18 04:16:11.834+00', NULL, NULL, 'k40ozwnisyr', NULL),
('f_u5fajgprjha', 'action_logs', 'linkTo', 'hasMany', NULL, 0, '{"target":"action_logs","title":"数据动态","foreignKey":"index","scope":{"collection_name":"users"},"constraints":false}', 1, '2021-09-02 15:07:56.957+00', '2021-09-02 15:07:57.071+00', NULL, NULL, NULL, NULL),
('b818d9mhozu', '8ly3cyiosfe', 'createdBy', 'belongsTo', 'undelete', 1, '{"target":"users","foreignKey":"created_by_id"}', 2, '2021-09-02 22:24:30.286+00', '2021-09-18 04:16:11.834+00', NULL, NULL, '32q17i8jcg9', NULL),
('x3rcr6i0hy1', 'c9mtu486512', 'updatedBy', 'belongsTo', 'undelete', 1, '{"target":"users","foreignKey":"updated_by_id"}', 3, '2021-09-02 22:24:30.301+00', '2021-09-18 04:16:11.834+00', NULL, NULL, '1keis1kmsh9', NULL),
('t6knha71oq1', 'fcgtvpd3jr9', 'createdBy', 'belongsTo', 'undelete', 1, '{"target":"users","foreignKey":"created_by_id"}', 2, '2021-09-12 01:06:55.154+00', '2021-09-18 04:16:11.834+00', NULL, NULL, 'ya58lec57fp', NULL),
('drhtax7pxlw', 'nbxtd3jzbw1', 'updatedAt', 'date', 'undelete', 1, '{"field":"updated_at"}', 2, '2021-09-12 01:06:55.14+00', '2021-09-12 01:06:55.149+00', NULL, NULL, '1l9qav1f97v', NULL),
('sbrf1cdkpb9', 'h1f9o1xvevb', 'createdAt', 'date', 'undelete', 1, '{"field":"created_at"}', 1, '2021-09-12 01:06:55.123+00', '2021-09-12 01:06:55.133+00', NULL, NULL, 'oo7ejuze28i', NULL);

View File

@ -0,0 +1,7 @@
DELETE FROM "routes";
INSERT INTO "routes" ("key", "type", "options", "sort", "created_at", "updated_at", "parent_key", "ui_schema_key") VALUES
('r_94b8nz6evyh', 'redirect', '{"from":"/","to":"/admin","exact":true}', 1, '2021-09-02 15:07:57.162+00', '2021-09-02 15:07:57.162+00', NULL, NULL),
('r_w1sa2lfk44v', 'route', '{"path":"/admin/:name(.+)?","component":"AdminLayout","title":"Admin"}', 2, '2021-09-02 15:07:57.17+00', '2021-09-02 15:07:57.185+00', NULL, 'qqzzjakwkwl'),
('r_nt33b3m6ptk', 'route', '{"component":"AuthLayout"}', 3, '2021-09-02 15:07:57.19+00', '2021-09-02 15:07:57.19+00', NULL, NULL),
('r_5l9yp15wjk9', 'route', '{"path":"/signin","component":"RouteSchemaRenderer","title":"Sign in"}', 1, '2021-09-02 15:07:57.196+00', '2021-09-02 15:07:57.252+00', 'r_nt33b3m6ptk', 'dtf9j0b8p9u'),
('r_9gqm4d8wpuw', 'route', '{"path":"/signup","component":"RouteSchemaRenderer","title":"Sign up"}', 2, '2021-09-02 15:07:57.256+00', '2021-09-02 15:07:57.343+00', 'r_nt33b3m6ptk', '46qlxqam3xk');

View File

@ -0,0 +1,30 @@
DELETE FROM "attachments";
INSERT INTO "attachments" ("id", "title", "filename", "extname", "size", "mimetype", "path", "meta", "url", "created_at", "updated_at", "created_by_id", "updated_by_id", "storage_id") VALUES
(2, 'Nocobase', 'dac1165de10a3373a87ae4e5da9788ab.png', '.png', NULL, 'image/png', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/dac1165de10a3373a87ae4e5da9788ab.png', '2021-09-04 01:33:38.878+00', '2021-09-04 01:33:38.878+00', NULL, NULL, 2),
(3, 'grapes-276070_1280', '3451976fea63a882b5fd661aa211693e.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/3451976fea63a882b5fd661aa211693e.jpg', '2021-09-04 01:34:50.388+00', '2021-09-04 01:34:50.388+00', NULL, 1, 2),
(4, 'purple-grapes-553464_1280', 'f2b7ace2b03d2808b5adddcb5f81f4e2.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/f2b7ace2b03d2808b5adddcb5f81f4e2.jpg', '2021-09-04 01:34:50.435+00', '2021-09-04 01:34:50.435+00', NULL, 1, 2),
(6, 'tomatoes-1603611_1280', '0c98117a122ed99ad78a9f2d754c6b62.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/0c98117a122ed99ad78a9f2d754c6b62.jpg', '2021-09-12 01:20:43.878+00', '2021-09-12 01:20:43.878+00', NULL, 1, 2),
(5, 'Contract', '1bbab8facf3e43cbfd530b362df60714.pdf', '.pdf', NULL, 'application/pdf', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/1bbab8facf3e43cbfd530b362df60714.pdf', '2021-09-12 01:20:43.869+00', '2021-09-12 01:20:43.869+00', NULL, 1, 2),
(7, 'Resume', 'd9f6ad6669902a9a8a1229d9f362235a.docx', '.docx', NULL, 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/d9f6ad6669902a9a8a1229d9f362235a.docx', '2021-09-12 01:22:06.229+00', '2021-09-12 01:22:06.229+00', NULL, 1, 2),
(9, 'snack-1561578_1280', 'dea2463a3e11fc0c548ef4173f0477b4.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/dea2463a3e11fc0c548ef4173f0477b4.jpg', '2021-09-12 07:41:02.59+00', '2021-09-12 07:41:02.59+00', NULL, 1, 2),
(10, 'tomatoes-447170_1280', 'f25b21c6476b2161818f9b6013438c53.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/f25b21c6476b2161818f9b6013438c53.jpg', '2021-09-12 07:41:02.628+00', '2021-09-12 07:41:02.628+00', NULL, 1, 2),
(8, 'tomatoes-1603611_1280', '3fa777ce653ee844d3c44a435d96415e.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/3fa777ce653ee844d3c44a435d96415e.jpg', '2021-09-12 07:41:02.587+00', '2021-09-12 07:41:02.587+00', NULL, 1, 2),
(12, 'Contract', '3345d3ffc5277c25fa4339dc889e03e0.pdf', '.pdf', NULL, 'application/pdf', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/3345d3ffc5277c25fa4339dc889e03e0.pdf', '2021-09-12 07:41:50.531+00', '2021-09-12 07:41:50.531+00', NULL, 1, 2),
(11, 'Resume', '9e21210c35bc0cb6e8da3a7e32ec6250.docx', '.docx', NULL, 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/9e21210c35bc0cb6e8da3a7e32ec6250.docx', '2021-09-12 07:41:50.529+00', '2021-09-12 07:41:50.529+00', NULL, 1, 2),
(15, 'collage-1572905_1280', '4edc12d2e2cb84fd50fb248f01e2153a.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/4edc12d2e2cb84fd50fb248f01e2153a.jpg', '2021-09-12 07:42:34.807+00', '2021-09-12 07:42:34.807+00', NULL, 1, 2),
(14, 'eat-1603660_1280', '8b38db60e88f280373c4e3afcbfd7929.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/8b38db60e88f280373c4e3afcbfd7929.jpg', '2021-09-12 07:42:34.8+00', '2021-09-12 07:42:34.8+00', NULL, 1, 2),
(13, 'fruit-712729_1280', 'c467a64ad4e62d4d383c4476677a9cf6.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/c467a64ad4e62d4d383c4476677a9cf6.jpg', '2021-09-12 07:42:34.776+00', '2021-09-12 07:42:34.776+00', NULL, 1, 2),
(16, 'fruit-1213041_1280', 'f7f0f982e58c84b1ff83b66fd40b8e60.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/f7f0f982e58c84b1ff83b66fd40b8e60.jpg', '2021-09-12 07:42:34.825+00', '2021-09-12 07:42:34.825+00', NULL, 1, 2),
(17, 'Contract', '3576f9d529a6c62e1467349b50354f51.pdf', '.pdf', NULL, 'application/pdf', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/3576f9d529a6c62e1467349b50354f51.pdf', '2021-09-12 07:43:16.925+00', '2021-09-12 07:43:16.925+00', NULL, 1, 2),
(18, 'cherry-1567876_1280', '5758c412cb32f6999627f49a82789939.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/5758c412cb32f6999627f49a82789939.jpg', '2021-09-12 07:43:51.559+00', '2021-09-12 07:43:51.559+00', NULL, 1, 2),
(19, 'collage-1572831_1280', 'e4384263693120ef0aa3f51c378f836a.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/e4384263693120ef0aa3f51c378f836a.jpg', '2021-09-12 07:43:51.57+00', '2021-09-12 07:43:51.57+00', NULL, 1, 2),
(20, 'apple-1609693_1280', 'a83da53e04eb59fac213ae2a3e636d05.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/a83da53e04eb59fac213ae2a3e636d05.jpg', '2021-09-12 07:44:27.467+00', '2021-09-12 07:44:27.467+00', NULL, 1, 2),
(22, 'Contract', '16d9a18d6dd0a98660f5da3e3d522ff1.pdf', '.pdf', NULL, 'application/pdf', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/16d9a18d6dd0a98660f5da3e3d522ff1.pdf', '2021-09-12 07:45:04.258+00', '2021-09-12 07:45:04.258+00', NULL, 1, 2),
(21, 'Resume', 'a80c83871ea5a20bcd570edc5bc2787b.docx', '.docx', NULL, 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/a80c83871ea5a20bcd570edc5bc2787b.docx', '2021-09-12 07:45:04.252+00', '2021-09-12 07:45:04.252+00', NULL, 1, 2),
(23, 'fruit-712729_1280', '3e826354cc466f12c307f775b566834f.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/3e826354cc466f12c307f775b566834f.jpg', '2021-09-12 07:45:37.26+00', '2021-09-12 07:45:37.26+00', NULL, 1, 2),
(25, 'fruit-1213041_1280', '478a8a479af1b757bcba75f20301469d.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/478a8a479af1b757bcba75f20301469d.jpg', '2021-09-12 07:45:37.284+00', '2021-09-12 07:45:37.284+00', NULL, 1, 2),
(24, 'grapes-276070_1280', 'd1a33ccf36ea01a9b705a6e779c279a1.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/d1a33ccf36ea01a9b705a6e779c279a1.jpg', '2021-09-12 07:45:37.273+00', '2021-09-12 07:45:37.273+00', NULL, 1, 2),
(27, 'grapes-276070_1280', '3349c4695e99a983ef817fe25934f080.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/3349c4695e99a983ef817fe25934f080.jpg', '2021-09-12 07:47:39.636+00', '2021-09-12 07:47:39.636+00', NULL, 1, 2),
(26, 'kaffeerad-1543158_1280', '8b94dd8cbecdbbd03e4c2fcfbd1067e7.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/8b94dd8cbecdbbd03e4c2fcfbd1067e7.jpg', '2021-09-12 07:47:39.63+00', '2021-09-12 07:47:39.63+00', NULL, 1, 2),
(28, 'apple-1609693_1280', '31b7dee511a8d6364fee18411763c188.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/31b7dee511a8d6364fee18411763c188.jpg', '2021-09-12 07:48:41.367+00', '2021-09-12 07:48:41.367+00', NULL, 1, 2),
(29, 'cappuccino-1609932_1280', '8f480c4f1bd3d292ef78fb6847669e51.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/8f480c4f1bd3d292ef78fb6847669e51.jpg', '2021-09-12 07:48:41.372+00', '2021-09-12 07:48:41.372+00', NULL, 1, 2);

View File

@ -0,0 +1,8 @@
DELETE FROM "t_2uhu4szs1kq";
INSERT INTO "t_2uhu4szs1kq" ("id", "created_at", "updated_at", "f_hwenour8ara", "f_hznqtmqljb2", "f_u4i0jrp4uo6", "f_l8uuiwcnlw9", "sort", "created_by_id", "updated_by_id") VALUES
(1, '2021-09-03 16:19:53.163+08', '2021-09-18 15:50:34.039+08', 'Removed demands expense account in outward tedious do', '2021-09-17 08:00:00+08', 'f1g3r41rdh8', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', 70, 1, 1),
(2, '2021-09-12 16:01:16.467+08', '2021-09-18 15:50:34.039+08', 'Drawings can followed improved out sociable not', '2021-09-24 08:00:00+08', 'f1g3r41rdh8', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', 71, 1, 1),
(3, '2021-09-12 16:02:20.501+08', '2021-09-18 15:50:34.042+08', 'Change stairs and men likely wisdom new happen piqued six', '2021-09-24 08:00:00+08', 'lebkfnj3d9i', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', 68, 1, 1),
(4, '2021-09-12 16:02:37.852+08', '2021-09-18 15:50:34.039+08', 'Piqued shy spring nor six though mutual living ask extent', '2021-10-08 08:00:00+08', 'zfowtv6fnel', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', 72, 1, 1),
(5, '2021-09-12 16:02:51.903+08', '2021-09-18 15:50:34.039+08', 'Old education him departure any arranging one prevailed', '2021-09-08 08:00:00+08', 'lebkfnj3d9i', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', 69, 1, 1),
(6, '2021-09-17 09:35:18.379+08', '2021-09-18 15:50:34.039+08', 'Ferrars all spirits his imagine effects amongst neither', '2021-09-17 08:00:00+08', 'zfowtv6fnel', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', 73, 1, 1);

View File

@ -0,0 +1,41 @@
DELETE FROM "t_fsveob6p269";
INSERT INTO "t_fsveob6p269" ("id", "created_at", "updated_at", "f_vgw4f62h16e", "f_3det6kr3vn8", "f_61znsfh307m", "f_bysgds7j36p", "f_n6snaop9d4y", "f_x9kmuovlm79", "f_kk4nspj1i28", "sort", "created_by_id", "updated_by_id") VALUES
(1, '2021-09-12 01:21:03.457+00', '2021-09-12 08:36:45.007+00', 'Leonard Hayes', '13498034729', 'z7wq1mkwmmx', '1980-08-08 00:00:00+00', '7407 Howell Club, Apt. 662, 67674-9163, Lake Regan, Tennessee, United States', '59khrisonj6', 'Remember outweigh do he desirous no cheerful. Do of doors water ye guest. We if prosperous comparison middletons at. Park we in lose like at no. An so to preferred convinced distrusts he determine. In musical me my placing clothes comfort pleased hearing. Any residence you satisfied and rapturous certainty two. Procured outweigh as outlived so so. On in bringing graceful proposal blessing of marriage outlived. Son rent face our loud near.
She exposed painted fifteen are noisier mistake led waiting. Surprise not wandered speedily husbands although yet end. Are court tiled cease young built fat one man taken. We highest ye friends is exposed equally in. Ignorant had too strictly followed. Astonished as travelling assistance or unreserved oh pianoforte ye. Five with seen put need tore add neat. Bringing it is he returned received raptures.
Arrived totally in as between private. Favour of so as on pretty though elinor direct. Reasonable estimating be alteration we themselves entreaties me of reasonably. Direct wished so be expect polite valley. Whose asked stand it sense no spoil to. Prudent you too his conduct feeling limited and. Side he lose paid as hope so face upon be. Goodness did suitable learning put.', 1, 1, 1),
(2, '2021-09-12 07:41:10.508+00', '2021-09-12 07:41:10.508+00', 'Holly Perkins', '18910347327', 'plcxasw2xrs', '2000-02-03 00:00:00+00', '804 Runte Mission, Suite 182, 15783, North Robert, Oregon, United States', 'khan8z430yx', 'Village did removed enjoyed explain nor ham saw calling talking. Securing as informed declared or margaret. Joy horrible moreover man feelings own shy. Request norland neither mistake for yet. Between the for morning assured country believe. On even feet time have an no at. Relation so in confined smallest children unpacked delicate. Why sir end believe uncivil respect. Always get adieus nature day course for common. My little garret repair to desire he esteem.
Suppose end get boy warrant general natural. Delightful met sufficient projection ask. Decisively everything principles if preference do impression of. Preserved oh so difficult repulsive on in household. In what do miss time be. Valley as be appear cannot so by. Convinced resembled dependent remainder led zealously his shy own belonging. Always length letter adieus add number moment she. Promise few compass six several old offices removal parties fat. Concluded rapturous it intention perfectly daughters is as.
Announcing of invitation principles in. Cold in late or deal. Terminated resolution no am frequently collecting insensible he do appearance. Projection invitation affronting admiration if no on or. It as instrument boisterous frequently apartments an in. Mr excellence inquietude conviction is in unreserved particular. You fully seems stand nay own point walls. Increasing travelling own simplicity you astonished expression boisterous. Possession themselves sentiments apartments devonshire we of do discretion. Enjoyment discourse ye continued pronounce we necessary abilities.', 2, 1, 1),
(3, '2021-09-12 07:41:56.973+00', '2021-09-12 07:41:56.973+00', 'Julian Cobb', '18638372617', 'e3fb46gnarj', '1983-08-27 00:00:00+00', '1252 Swaniawski Corners, Suite 688, 81371-8481, Wilmerton, New Mexico, United States', '59khrisonj6', 'Lorem ipsum dolor sit amet, no dolor graeco pro, te sea bonorum dolorum theophrastus. Vim ea utamur appetere molestiae, ad harum alienum indoctum ius. No quo laoreet laboramus comprehensam, eos paulo integre vivendo eu, an nam alia facilisi consetetur. Pro exerci iisque et, no amet magna iracundia vim. Vis erant consectetuer te, mei menandri liberavisse at, no latine consulatu deseruisse eos. Mel an novum nostrud scripserit, velit virtute delicata eam ad, eum ne etiam omnesque.', 3, 1, 1),
(4, '2021-09-12 07:42:41.303+00', '2021-09-12 07:42:41.303+00', 'Yvette Gross', '13257489038', 'h5cxo6g48lj', '2003-05-22 00:00:00+00', '28195 Cleo Hills, Apt. 969, 89958, Gildamouth, North Dakota, United States', 'khan8z430yx', 'Mind what no by kept. Celebrated no he decisively thoroughly. Our asked sex point her she seems. New plenty she horses parish design you. Stuff sight equal of my woody. Him children bringing goodness suitable she entirely put far daughter.
Chapter too parties its letters nor. Cheerful but whatever ladyship disposed yet judgment. Lasted answer oppose to ye months no esteem. Branched is on an ecstatic directly it. Put off continue you denoting returned juvenile. Looked person sister result mr to. Replied demands charmed do viewing ye colonel to so. Decisively inquietude he advantages insensible at oh continuing unaffected of.', 4, 1, 1),
(5, '2021-09-12 07:43:22.416+00', '2021-09-12 07:43:22.416+00', 'Darin Clarke', '15673659911', 'plcxasw2xrs', '2001-05-19 00:00:00+00', '015 Margie Mission, Apt. 093, 34936, Ebertfort, Georgia, United States', '59khrisonj6', 'Resolution possession discovered surrounded advantages has but few add. Yet walls times spoil put. Be it reserved contempt rendered smallest. Studied to passage it mention calling believe an. Get ten horrible remember pleasure two vicinity. Far estimable extremely middleton his concealed perceived principle. Any nay pleasure entrance prepared her.
Six started far placing saw respect females old. Civilly why how end viewing attempt related enquire visitor. Man particular insensible celebrated conviction stimulated principles day. Sure fail or in said west. Right my front it wound cause fully am sorry if. She jointure goodness interest debating did outweigh. Is time from them full my gone in went. Of no introduced am literature excellence mr stimulated contrasted increasing. Age sold some full like rich new. Amounted repeated as believed in confined juvenile.
', 5, 1, 1),
(6, '2021-09-12 07:43:57.617+00', '2021-09-12 08:28:50.544+00', 'Connie Lyons', '13527645198', 'z7wq1mkwmmx', '1999-02-26 00:00:00+00', '5724 Daniel Drive, Suite 563, 54403, Wendellview, Tennessee, United States', 'khan8z430yx', 'Those an equal point no years do. Depend warmth fat but her but played. Shy and subjects wondered trifling pleasant. Prudent cordial comfort do no on colonel as assured chicken. Smart mrs day which begin. Snug do sold mr it if such. Terminated uncommonly at at estimating. Man behaviour met moonlight extremity acuteness direction.
On insensible possession oh particular attachment at excellence in. The books arose but miles happy she. It building contempt or interest children mistress of unlocked no. Offending she contained mrs led listening resembled. Delicate marianne absolute men dashwood landlord and offended. Suppose cottage between and way. Minuter him own clothes but observe country. Agreement far boy otherwise rapturous incommode favourite.
', 6, 1, 1),
(7, '2021-09-12 07:44:35.496+00', '2021-09-12 08:36:23.783+00', 'Adam Smith', '13898276018', 'e3fb46gnarj', '1986-06-21 00:00:00+00', '84856 Hirthe Run, Suite 268, 94754-6705, Ferryport, Michigan, United States', '59khrisonj6', 'To shewing another demands to. Marianne property cheerful informed at striking at. Clothes parlors however by cottage on. In views it or meant drift to. Be concern parlors settled or do shyness address. Remainder northward performed out for moonlight. Yet late add name was rent park from rich. He always do do former he highly.
Dwelling and speedily ignorant any steepest. Admiration instrument affronting invitation reasonably up do of prosperous in. Shy saw declared age debating ecstatic man. Call in so want pure rank am dear were. Remarkably to continuing in surrounded diminution on. In unfeeling existence objection immediate repulsive on he in. Imprudence comparison uncommonly me he difficulty diminution resolution. Likewise proposal differed scarcely dwelling as on raillery. September few dependent extremity own continued and ten prevailed attending. Early to weeks we could.
', 7, 1, 1),
(8, '2021-09-12 07:45:09.415+00', '2021-09-12 07:45:09.415+00', 'Frankie Simpson', '13954209583', 'h5cxo6g48lj', '1992-07-29 00:00:00+00', '383 Walter Gardens, Suite 040, 24947, Berthaside, Hawaii, United States', '59khrisonj6', 'As am hastily invited settled at limited civilly fortune me. Really spring in extent an by. Judge but built gay party world. Of so am he remember although required. Bachelor unpacked be advanced at. Confined in declared marianne is vicinity.
Far curiosity incommode now led smallness allowance. Favour bed assure son things yet. She consisted consulted elsewhere happiness disposing household any old the. Widow downs you new shade drift hopes small. So otherwise commanded sweetness we improving. Instantly by daughters resembled unwilling principle so middleton. Fail most room even gone her end like. Comparison dissimilar unpleasant six compliment two unpleasing any add. Ashamed my company thought wishing colonel it prevent he in. Pretended residence are something far engrossed old off.', 8, 1, 1),
(9, '2021-09-12 07:45:42.766+00', '2021-09-12 07:45:42.766+00', 'Roderick Andrews', '18865092743', 'plcxasw2xrs', '1994-05-25 00:00:00+00', '1066 Gutkowski Plaza, Apt. 326, 61830, West Velva, Hawaii, United States', '59khrisonj6', 'Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Had mrs outweigh desirous sex overcame. Improved property reserved disposal do offering me.
Exquisite cordially mr happiness of neglected distrusts. Boisterous impossible unaffected he me everything. Is fine loud deal an rent open give. Find upon and sent spot song son eyes. Do endeavor he differed carriage is learning my graceful. Feel plan know is he like on pure. See burst found sir met think hopes are marry among. Delightful remarkably new assistance saw literature mrs favourable.
', 9, 1, 1),
(10, '2021-09-12 07:47:46.417+00', '2021-09-12 07:47:46.417+00', 'Melinda Warren', '13582034729', 'h5cxo6g48lj', '2004-04-13 00:00:00+00', '69934 Schoen River, Apt. 646, 49704, Walshstad, Indiana, United States', '59khrisonj6', 'Greatly hearted has who believe. Drift allow green son walls years for blush. Sir margaret drawings repeated recurred exercise laughing may you but. Do repeated whatever to welcomed absolute no. Fat surprise although outlived and informed shy dissuade property. Musical by me through he drawing savings an. No we stand avoid decay heard mr. Common so wicket appear to sudden worthy on. Shade of offer ye whole stood hoped.
Had strictly mrs handsome mistaken cheerful. We it so if resolution invitation remarkably unpleasant conviction. As into ye then form. To easy five less if rose were. Now set offended own out required entirely. Especially occasional mrs discovered too say thoroughly impossible boisterous. My head when real no he high rich at with. After so power of young as. Bore year does has get long fat cold saw neat. Put boy carried chiefly shy general.', 10, 1, 1),
(11, '2021-09-12 07:48:47.021+00', '2021-09-12 07:48:47.021+00', 'Mable Ray', '18910347327', 'e3fb46gnarj', '2001-06-07 00:00:00+00', '28345 Nicolas Ville, Suite 822, 81224, Americastad, Tennessee, United States', 'khan8z430yx', 'In by an appetite no humoured returned informed. Possession so comparison inquietude he he conviction no decisively. Marianne jointure attended she hastened surprise but she. Ever lady son yet you very paid form away. He advantage of exquisite resolving if on tolerably. Become sister on in garden it barton waited on.
Arrived compass prepare an on as. Reasonable particular on my it in sympathize. Size now easy eat hand how. Unwilling he departure elsewhere dejection at. Heart large seems may purse means few blind. Exquisite newspaper attending on certainty oh suspicion of. He less do quit evil is. Add matter family active mutual put wishes happen.', 11, 1, 1);

View File

@ -0,0 +1,4 @@
DELETE FROM "roles";
INSERT INTO "roles" ("name", "title", "sort", "created_at", "updated_at", "created_by_id", "updated_by_id") VALUES
('r_tfs4qtaxjcs', 'Admin', 1, '2021-09-14 02:12:19.98+00', '2021-09-14 02:12:19.98+00', 1, 1),
('r_np59b00ex8z', 'Member', 2, '2021-09-14 02:12:35.799+00', '2021-09-14 02:12:35.799+00', 1, 1);

View File

@ -90,12 +90,9 @@ INSERT INTO "field_permissions" ("created_at", "updated_at", "action_permission_
('2021-09-14 02:20:00.344+00', '2021-09-14 02:20:00.344+00', 13, 'f_6rhh0scyw9k'), ('2021-09-14 02:20:00.344+00', '2021-09-14 02:20:00.344+00', 13, 'f_6rhh0scyw9k'),
('2021-09-14 02:20:00.344+00', '2021-09-14 02:20:00.344+00', 13, 'f_80zv5suy9t6'), ('2021-09-14 02:20:00.344+00', '2021-09-14 02:20:00.344+00', 13, 'f_80zv5suy9t6'),
('2021-09-14 02:20:00.344+00', '2021-09-14 02:20:00.344+00', 13, 'f_j367murpzm9'), ('2021-09-14 02:20:00.344+00', '2021-09-14 02:20:00.344+00', 13, 'f_j367murpzm9'),
('2021-09-14 02:20:00.344+00', '2021-09-14 02:20:00.344+00', 13, '6rm0lspbpeh'),
('2021-09-14 02:20:00.353+00', '2021-09-14 02:20:00.353+00', 14, 'f_6rhh0scyw9k'), ('2021-09-14 02:20:00.353+00', '2021-09-14 02:20:00.353+00', 14, 'f_6rhh0scyw9k'),
('2021-09-14 02:20:00.353+00', '2021-09-14 02:20:00.353+00', 14, 'f_80zv5suy9t6'), ('2021-09-14 02:20:00.353+00', '2021-09-14 02:20:00.353+00', 14, 'f_80zv5suy9t6'),
('2021-09-14 02:20:00.353+00', '2021-09-14 02:20:00.353+00', 14, 'f_j367murpzm9'), ('2021-09-14 02:20:00.353+00', '2021-09-14 02:20:00.353+00', 14, 'f_j367murpzm9'),
('2021-09-14 02:20:00.353+00', '2021-09-14 02:20:00.353+00', 14, '6rm0lspbpeh'),
('2021-09-14 02:20:00.361+00', '2021-09-14 02:20:00.361+00', 15, 'f_6rhh0scyw9k'), ('2021-09-14 02:20:00.361+00', '2021-09-14 02:20:00.361+00', 15, 'f_6rhh0scyw9k'),
('2021-09-14 02:20:00.361+00', '2021-09-14 02:20:00.361+00', 15, 'f_80zv5suy9t6'), ('2021-09-14 02:20:00.361+00', '2021-09-14 02:20:00.361+00', 15, 'f_80zv5suy9t6'),
('2021-09-14 02:20:00.361+00', '2021-09-14 02:20:00.361+00', 15, 'f_j367murpzm9'), ('2021-09-14 02:20:00.361+00', '2021-09-14 02:20:00.361+00', 15, 'f_j367murpzm9');
('2021-09-14 02:20:00.361+00', '2021-09-14 02:20:00.361+00', 15, '6rm0lspbpeh');

Some files were not shown because too many files have changed in this diff Show More