This commit is contained in:
chenos 2021-05-23 08:38:08 +08:00
parent cb8edc7b75
commit 2cbcd087ce
684 changed files with 15405 additions and 39426 deletions

View File

@ -4,6 +4,7 @@
background-color: #fff; background-color: #fff;
border: 1px solid @c-border; border: 1px solid @c-border;
border-radius: 1px; border-radius: 1px;
margin-bottom: 24px;
&[data-debug] { &[data-debug] {
margin-top: 32px; margin-top: 32px;

View File

@ -76,7 +76,7 @@ const Previewer: React.FC<IPreviewerProps> = oProps => {
const isActive = history?.location.hash === `#${props.identifier}`; const isActive = history?.location.hash === `#${props.identifier}`;
const isSingleFile = Object.keys(props.sources).length === 1; const isSingleFile = Object.keys(props.sources).length === 1;
const openCSB = useCodeSandbox(props.hideActions?.includes('CSB') ? null : props); const openCSB = useCodeSandbox(props.hideActions?.includes('CSB') ? null : props);
const openRiddle = useRiddle(props.hideActions?.includes('RIDDLE') ? null : props); // const openRiddle = useRiddle(props.hideActions?.includes('RIDDLE') ? null : props);
const [execMotions, isMotionRunning] = useMotions(props.motions || [], demoRef.current); const [execMotions, isMotionRunning] = useMotions(props.motions || [], demoRef.current);
const [copyCode, copyStatus] = useCopy(); const [copyCode, copyStatus] = useCopy();
const [currentFile, setCurrentFile] = useState('_'); const [currentFile, setCurrentFile] = useState('_');
@ -158,14 +158,14 @@ const Previewer: React.FC<IPreviewerProps> = oProps => {
onClick={openCSB} onClick={openCSB}
/> />
)} )}
{openRiddle && ( {/* {openRiddle && (
<button <button
title="Open demo on Riddle" title="Open demo on Riddle"
className="__dumi-default-icon" className="__dumi-default-icon"
role="riddle" role="riddle"
onClick={openRiddle} onClick={openRiddle}
/> />
)} )} */}
{props.motions && ( {props.motions && (
<button <button
title="Execute motions" title="Execute motions"

View File

@ -100,7 +100,7 @@ const Layout: React.FC<IRouteComponentProps> = ({ children, location }) => {
{isCN ? `${repoPlatform} 上编辑此页` : `Edit this doc on ${repoPlatform}`} {isCN ? `${repoPlatform} 上编辑此页` : `Edit this doc on ${repoPlatform}`}
</Link> </Link>
)} )}
<span data-updated-text={isCN ? '最后更新时间:' : 'Last update: '}>{updatedTime}</span> {/* <span data-updated-text={isCN ? '最后更新时间:' : 'Last update: '}>{updatedTime}</span> */}
</div> </div>
)} )}
{(showHero || showFeatures) && meta.footer && ( {(showHero || showFeatures) && meta.footer && (

6
.editorconfig Normal file → Executable file
View File

@ -1,10 +1,14 @@
# EditorConfig is awesome: http://EditorConfig.org # EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true root = true
# Unix-style newlines with a newline ending every file
[*] [*]
charset = utf-8
end_of_line = lf end_of_line = lf
insert_final_newline = true insert_final_newline = true
indent_style = space indent_style = space
indent_size = 2 indent_size = 2
[*.gradle]
indent_size = 4

10
.eslintignore Executable file
View File

@ -0,0 +1,10 @@
node_modules
lib
dist
build
coverage
expected
website
gh-pages
weex
build.ts

0
.eslintrc Normal file → Executable file
View File

View File

@ -1,47 +0,0 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Node.js CI
on: [push]
jobs:
test:
strategy:
matrix:
node_version: ['12']
db_dialect: ['postgres']
pg_version: ['12']
runs-on: ubuntu-latest
container: node:${{ matrix.node_version }}
services:
# Label used to access the service container
postgres:
# Docker Hub image
image: postgres:${{ matrix.pg_version }}
# Provide the password for postgres
env:
POSTGRES_USER: nocobase
POSTGRES_PASSWORD: password
# Set health checks to wait until postgres has started
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v2
- run: npm i
- run: npm run bootstrap
- run: npm run build
- name: Test
run: npm test
env:
DB_DIALECT: ${{ matrix.db_dialect }}
DB_HOST: ${{ matrix.db_dialect }}
DB_PORT: 5432
DB_USER: nocobase
DB_PASSWORD: password
DB_DATABASE: nocobase

View File

@ -1,9 +1,6 @@
name: vercel name: vercel
on: on: [push]
push:
branches:
- master
jobs: jobs:
deploy: deploy:

View File

@ -3,8 +3,8 @@ import { defineConfig } from 'dumi';
export default defineConfig({ export default defineConfig({
title: ' ', title: ' ',
hash: true, hash: true,
ssr: {}, // ssr: {},
exportStatic: {}, // exportStatic: {},
mode: 'site', mode: 'site',
logo: 'https://www.nocobase.com/dist/images/logo.png', logo: 'https://www.nocobase.com/dist/images/logo.png',
navs: { navs: {

View File

@ -3,13 +3,23 @@ const path = require('path');
module.exports = { module.exports = {
preset: 'ts-jest', preset: 'ts-jest',
testEnvironment: 'node', testEnvironment: 'node',
setupFilesAfterEnv: [path.resolve(__dirname, 'dotenv.js')], setupFilesAfterEnv: [
path.resolve(__dirname, 'dotenv.js'),
],
testMatch: [ testMatch: [
// '**/__tests__/**/*.[jt]s?(x)', // '**/__tests__/**/*.[jt]s?(x)',
'**/?(*.)+(spec|test).[jt]s?(x)' '**/?(*.)+(spec|test).[jt]s?(x)'
], ],
modulePathIgnorePatterns: [ modulePathIgnorePatterns: [
"<rootDir>/.dumi/", "<rootDir>/.dumi/",
"<rootDir>/packages/father-build/" "<rootDir>/packages/father-build/",
"<rootDir>/packages/client/lib/"
], ],
globals: {
'ts-jest': {
babelConfig: true,
tsconfig: 'tsconfig.jest.json',
diagnostics: false,
},
},
}; };

View File

@ -3,17 +3,16 @@
"packages/*" "packages/*"
], ],
"version": "independent", "version": "independent",
"npmClient": "yarn",
"useWorkspaces": true,
"npmClientArgs": [
"--ignore-engines"
],
"command": { "command": {
"bootstrap": {
"npmClientArgs": [
"--no-package-lock"
]
},
"publish": { "publish": {
"allowBranch": "master",
"ignoreChanges": [ "ignoreChanges": [
"*.md" "*.md"
] ]
} }
} }
} }

View File

17
mock/api.ts Normal file
View File

@ -0,0 +1,17 @@
import Mock from 'mockjs';
import * as resources from './resources';
export default {
'/api/:resource::action': (req: any, res: any) => {
const action = resources[req.params.resource][req.params.action];
if (action) {
return action(req, res);
}
},
'/api/:resource::action/:resourceIndex': (req: any, res: any) => {
const action = resources[req.params.resource][req.params.action];
if (action) {
return action(req, res);
}
},
}

37
mock/resources/blocks.ts Normal file
View File

@ -0,0 +1,37 @@
import Mock from 'mockjs';
export async function getOptions(req: any, res: any) {
res.json({
data: {
name: 'grid1',
'x-component': 'GridBlock',
blocks: [
{
name: 'form1',
resource: {
resourceName: 'users',
resourceKey: 1,
},
fields: [
{
interface: 'string',
type: 'string',
title: `单行文本`,
name: 'username',
required: true,
component: {
type: 'string',
default: 'abcdefg',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {
placeholder: 'please enter',
},
},
},
],
},
],
},
});
}

View File

@ -0,0 +1,8 @@
import Mock from 'mockjs';
export async function list(req: any, res: any) {
res.json({
data: [],
meta: {},
});
}

7
mock/resources/index.ts Normal file
View File

@ -0,0 +1,7 @@
import * as blocks from './blocks';
import * as routes from './routes';
import * as users from './users';
import * as examples from './examples';
export { blocks, routes, users, examples };

184
mock/resources/routes.ts Normal file
View File

@ -0,0 +1,184 @@
import Mock from 'mockjs';
export async function getAccessible(req: any, res: any) {
res.json({
data: [
{
type: 'redirect',
from: '/admin',
to: '/admin/welcome',
exact: true,
},
{
type: 'redirect',
from: '/',
to: '/admin',
exact: true,
},
{
path: '/admin/:name(.+)?',
component: 'AdminLayout',
title: `后台 - ${Mock.mock('@string')}`,
// providers: ['CurrentUserProvider', 'MenuProvider'],
},
{
component: 'AuthLayout',
routes: [
{
name: 'login',
path: '/login',
component: 'PageTemplate',
title: `登录 - ${Mock.mock('@string')}`,
},
{
name: 'register',
path: '/register',
component: 'PageTemplate',
title: `注册 - ${Mock.mock('@string')}`,
},
],
},
],
});
}
export async function getMenu(req: any, res: any) {
res.json({
data: [
{
name: 'welcome',
title: `欢迎 - ${Mock.mock('@string')}`,
children: [
{
name: 'page2',
title: '页面2',
},
],
},
{
name: 'users',
title: `用户 - ${Mock.mock('@string')}`,
},
],
});
}
export async function getPage(req: any, res: any) {
const fields = [
{
interface: 'string',
type: 'string',
title: `单行文本 - ${Mock.mock('@string')}`,
name: 'input',
required: true,
component: {
type: 'string',
default: 'aa',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {},
},
},
{
interface: 'textarea',
type: 'text',
title: `多行文本框 - ${Mock.mock('@string')}`,
name: 'textarea',
required: true,
component: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input.TextArea',
'x-component-props': {},
},
},
];
res.json({
data: {
title: `page - ${req.query.slug} - ${Mock.mock('@string')}`,
blocks: [
{
interface: 'form',
type: 'form',
component: {
'x-component': 'Form',
},
fields,
},
{
interface: 'table',
type: 'table',
component: {
'x-component': 'Table',
},
fields,
},
{
interface: 'calendar',
type: 'calendar',
component: {
'x-component': 'Calendar',
},
},
{
interface: 'kanban',
type: 'kanban',
component: {
'x-component': 'Kanban',
},
},
{
interface: 'grid',
type: 'grid',
component: {
'x-component': 'Grid',
},
rowProps: {
gutter: 16,
},
colsProps: [12, 12],
blocks: [
{
interface: 'markdown',
type: 'markdown',
component: {
'x-component': 'Markdown',
},
content: `# Markdown Content 1`,
rowOrder: 1,
columnOrder: 1,
},
{
interface: 'markdown',
type: 'markdown',
component: {
'x-component': 'Markdown',
},
content: `# Markdown Content 2`,
rowOrder: 2,
columnOrder: 2,
},
{
interface: 'markdown',
type: 'markdown',
component: {
'x-component': 'Markdown',
},
content: `# Markdown Content 1`,
rowOrder: 1,
columnOrder: 2,
},
],
},
{
interface: 'markdown',
type: 'markdown',
component: {
'x-component': 'Markdown',
},
content: `# Markdown Content`,
},
],
},
});
}

19
mock/resources/users.ts Normal file
View File

@ -0,0 +1,19 @@
import Mock from 'mockjs';
export async function check(req: any, res: any) {
res.json({
data: {
username: `username - ${Mock.mock('@string')}`,
token: Mock.mock('@string'),
},
});
}
export async function get(req: any, res: any) {
res.json({
data: {
username: `username - ${Mock.mock('@string')}`,
token: Mock.mock('@string'),
},
});
}

View File

@ -1,6 +0,0 @@
{
"watch": ["packages/", ".env"],
"ignore": ["packages/app"],
"ext": "ts",
"exec": "ts-node -r dotenv/config ./packages/api/src/index.ts"
}

25431
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,74 +1,59 @@
{ {
"name": "root", "name": "nocobase",
"private": true, "private": true,
"workspaces": [
"packages/*"
],
"scripts": { "scripts": {
"start": "cd packages/app && npm start", "bootstrap": "lerna bootstrap",
"start-server": "nodemon", "clean": "lerna clean",
"start-client": "cd packages/app && npx umi dev",
"start-docs": "dumi dev", "start-docs": "dumi dev",
"build-docs": "dumi build", "build-docs": "dumi build",
"pm2-start": "npx pm2 start packages/api/lib/index.js", "build2": "lerna run build",
"build-app": "cd packages/app && npm run build", "build": "npm run build-father-build && node packages/father-build/bin/father-build.js",
"bootstrap": "lerna bootstrap --no-ci", "build-father-build": "cd packages/father-build && npm run build",
"build": "father-build",
"clean": "lerna clean",
"db-migrate": "ts-node -r dotenv/config ./packages/api/src/migrate.ts",
"lint": "eslint --ext .ts,.tsx,.js \"packages/*/src/**.@(ts|tsx|js)\" --fix", "lint": "eslint --ext .ts,.tsx,.js \"packages/*/src/**.@(ts|tsx|js)\" --fix",
"test": "npm run lint && jest", "test": "npm run lint && jest"
"debug": "node --inspect-brk node_modules/.bin/jest --runInBand",
"version:alpha": "lerna version prerelease --preid alpha --force-publish=* --no-git-tag-version -m \"chore(versions): publish packages %s\"",
"release:force": "lerna publish from-package --yes",
"release": "lerna publish"
}, },
"repository": { "resolutions": {
"type": "git", "@types/react": "^17.0.0",
"url": "git+https://github.com/nocobase/nocobase.git", "@types/react-dom": "^17.0.0"
"branch": "master",
"platform": "github"
}, },
"devDependencies": { "devDependencies": {
"@koa/router": "^9.3.1", "@types/jest": "^24.0.18",
"@types/bcrypt": "^3.0.0", "@types/koa": "^2.13.1",
"@types/jest": "^26.0.4", "@types/lodash": "^4.14.169",
"@types/koa": "^2.11.6", "@types/mockjs": "^1.0.3",
"@types/koa-bodyparser": "^4.3.0", "@types/node": "^12.6.8",
"@types/koa-mount": "^4.0.0", "@types/react": "^17.0.0",
"@types/koa__router": "^8.0.2",
"@types/lodash": "^4.14.158",
"@types/node": "^14.0.23",
"@types/react": "^17.0.3",
"@types/react-dom": "^17.0.3", "@types/react-dom": "^17.0.3",
"@types/supertest": "^2.0.10", "@typescript-eslint/eslint-plugin": "^4.9.1",
"@typescript-eslint/eslint-plugin": "^3.6.1", "@typescript-eslint/parser": "^4.8.2",
"@typescript-eslint/parser": "^3.6.1",
"@umijs/plugin-antd": "^0.9.1", "@umijs/plugin-antd": "^0.9.1",
"antd": "^4.15.0", "antd": "^4.15.4",
"cross-env": "^7.0.2",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"dumi": "^1.0.12", "dumi": "^1.1.16",
"eslint": "^7.4.0", "eslint": "^7.4.0",
"eslint-plugin-import": "^2.22.0", "eslint-plugin-import": "^2.22.0",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1", "eslint-plugin-promise": "^4.2.1",
"eslint-plugin-react": "^7.23.2",
"eslint-plugin-standard": "^4.0.1", "eslint-plugin-standard": "^4.0.1",
"father-build": "^1.19.2", "jest": "^26.6.3",
"jest": "^26.1.0",
"koa": "^2.13.0",
"koa-bodyparser": "^4.3.0", "koa-bodyparser": "^4.3.0",
"lerna": "^3.22.0", "lerna": "^3.22.0",
"lint-staged": "^10.0.7", "mockjs": "^1.1.0",
"mysql2": "^2.1.0", "pg": "^8.6.0",
"nodemon": "^2.0.4",
"path-to-regexp": "^6.1.0",
"pg": "^8.3.0",
"pg-hstore": "^2.3.3", "pg-hstore": "^2.3.3",
"pm2": "^4.5.6", "prettier": "^2.3.0",
"prettier": "^1.19.1", "react": "^17.0.1",
"sequelize": "^6.3.4", "react-dom": "^17.0.2",
"supertest": "^4.0.2", "react-router-dom": "^5.2.0",
"ts-jest": "^26.1.2", "react-test-renderer": "^17.0.2",
"ts-node": "^8.10.2", "rimraf": "^3.0.2",
"typescript": "^3.9.6", "supertest": "^6.1.3",
"yorkie": "^2.0.0" "ts-jest": "^26.5.6",
"ts-node": "^9.1.1",
"typescript": "4.1.5"
} }
} }

View File

@ -12,8 +12,7 @@
}, },
"devDependencies": { "devDependencies": {
"koa": "^2.13.0", "koa": "^2.13.0",
"sequelize": "^6.3.4", "sequelize": "^6.3.4"
"typescript": "^3.9.6"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

0
packages/api/bin/nocobase.js Normal file → Executable file
View File

View File

@ -16,7 +16,6 @@
"@nocobase/plugin-automations": "^0.4.0-alpha.7", "@nocobase/plugin-automations": "^0.4.0-alpha.7",
"@nocobase/plugin-china-region": "^0.4.0-alpha.7", "@nocobase/plugin-china-region": "^0.4.0-alpha.7",
"@nocobase/plugin-collections": "^0.4.0-alpha.7", "@nocobase/plugin-collections": "^0.4.0-alpha.7",
"@nocobase/plugin-export": "^0.4.0-alpha.7",
"@nocobase/plugin-file-manager": "^0.4.0-alpha.7", "@nocobase/plugin-file-manager": "^0.4.0-alpha.7",
"@nocobase/plugin-pages": "^0.4.0-alpha.7", "@nocobase/plugin-pages": "^0.4.0-alpha.7",
"@nocobase/plugin-permissions": "^0.4.0-alpha.7", "@nocobase/plugin-permissions": "^0.4.0-alpha.7",

View File

@ -46,7 +46,6 @@ const plugins = [
'@nocobase/plugin-permissions', '@nocobase/plugin-permissions',
'@nocobase/plugin-automations', '@nocobase/plugin-automations',
'@nocobase/plugin-china-region', '@nocobase/plugin-china-region',
'@nocobase/plugin-export',
]; ];
for (const plugin of plugins) { for (const plugin of plugins) {

View File

@ -1,10 +0,0 @@
export default {
entry: 'src/api',
target: 'node',
cjs: { type: 'babel', lazy: true },
include: 'api/*',
disableTypeCheck: true,
// pkgs: [
// 'api',
// ],
};

View File

@ -9,7 +9,7 @@
# production # production
/dist /dist
.env
# misc # misc
.DS_Store .DS_Store
@ -18,4 +18,3 @@
/src/.umi-production /src/.umi-production
/src/.umi-test /src/.umi-test
/.env.local /.env.local
report.*.json

View File

@ -1,12 +0,0 @@
lib
mock
node_modules
*.log
docs
__tests__
jest.config.js
tsconfig.json
src
.fatherrc.ts
.umirc.ts
nodemon.json

0
packages/app/.prettierignore Executable file → Normal file
View File

0
packages/app/.prettierrc Executable file → Normal file
View File

View File

@ -1,41 +1,11 @@
import { defineConfig } from 'umi'; import { defineConfig } from 'umi';
import dotenv from 'dotenv';
import path from 'path';
dotenv.config({
path: path.resolve(__dirname, '../../.env'),
});
console.log('process.env.API_PORT', process.env.API_PORT);
export default defineConfig({ export default defineConfig({
title: false,
nodeModulesTransform: { nodeModulesTransform: {
type: 'none', type: 'none',
}, },
define: {
'process.env.API': `/api`,
// 'process.env.API': `http://localhost:${process.env.API_PORT}/api`,
},
proxy: {
'/api': {
'target': `http://localhost:${process.env.API_PORT}/`,
'changeOrigin': true,
'pathRewrite': { '^/api' : '/api' },
},
},
locale: {
default: 'zh-CN',
// antd: false,
// title: false,
baseNavigator: false,
baseSeparator: '-',
},
routes: [ routes: [
{ { path: '/', exact: false, component: '@/pages/index' },
exact: false,
path: '/:path(.*)',
component: '@/pages/index',
},
], ],
fastRefresh: {},
}); });

4
packages/app/README.md Executable file → Normal file
View File

@ -1,11 +1,11 @@
# NocoBase Application # umi project
## Getting Started ## Getting Started
Install dependencies, Install dependencies,
```bash ```bash
$ yarn install $ yarn
``` ```
Start the dev server, Start the dev server,

0
packages/app/mock/.gitkeep Executable file → Normal file
View File

11
packages/app/mock/api.ts Normal file
View File

@ -0,0 +1,11 @@
import Mock from 'mockjs';
import * as resources from './resources';
export default {
'/api/:resource::action': (req: any, res: any) => {
const action = resources[req.params.resource][req.params.action];
if (action) {
return action(req, res);
}
},
}

View File

@ -0,0 +1,8 @@
import Mock from 'mockjs';
export async function list(req: any, res: any) {
res.json({
data: [],
meta: {},
});
}

View File

@ -0,0 +1,6 @@
import * as routes from './routes';
import * as users from './users';
import * as examples from './examples';
export { routes, users, examples };

View File

@ -0,0 +1,184 @@
import Mock from 'mockjs';
export async function getAccessible(req: any, res: any) {
res.json({
data: [
{
type: 'redirect',
from: '/admin',
to: '/admin/welcome',
exact: true,
},
{
type: 'redirect',
from: '/',
to: '/admin',
exact: true,
},
{
path: '/admin/:name(.+)?',
component: 'AdminLayout',
title: `后台 - ${Mock.mock('@string')}`,
// providers: ['CurrentUserProvider', 'MenuProvider'],
},
{
component: 'AuthLayout',
routes: [
{
name: 'login',
path: '/login',
component: 'PageTemplate',
title: `登录 - ${Mock.mock('@string')}`,
},
{
name: 'register',
path: '/register',
component: 'PageTemplate',
title: `注册 - ${Mock.mock('@string')}`,
},
],
},
],
});
}
export async function getMenu(req: any, res: any) {
res.json({
data: [
{
name: 'welcome',
title: `欢迎 - ${Mock.mock('@string')}`,
children: [
{
name: 'page2',
title: '页面2',
},
],
},
{
name: 'users',
title: `用户 - ${Mock.mock('@string')}`,
},
],
});
}
export async function getPage(req: any, res: any) {
const fields = [
{
interface: 'string',
type: 'string',
title: `单行文本 - ${Mock.mock('@string')}`,
name: 'input',
required: true,
component: {
type: 'string',
default: 'aa',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {},
},
},
{
interface: 'textarea',
type: 'text',
title: `多行文本框 - ${Mock.mock('@string')}`,
name: 'textarea',
required: true,
component: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input.TextArea',
'x-component-props': {},
},
},
];
res.json({
data: {
title: `page - ${req.query.slug} - ${Mock.mock('@string')}`,
blocks: [
{
interface: 'form',
type: 'form',
component: {
'x-component': 'Form',
},
fields,
},
{
interface: 'table',
type: 'table',
component: {
'x-component': 'Table',
},
fields,
},
{
interface: 'calendar',
type: 'calendar',
component: {
'x-component': 'Calendar',
},
},
{
interface: 'kanban',
type: 'kanban',
component: {
'x-component': 'Kanban',
},
},
{
interface: 'grid',
type: 'grid',
component: {
'x-component': 'Grid',
},
rowProps: {
gutter: 16,
},
colsProps: [12, 12],
blocks: [
{
interface: 'markdown',
type: 'markdown',
component: {
'x-component': 'Markdown',
},
content: `# Markdown Content 1`,
rowOrder: 1,
columnOrder: 1,
},
{
interface: 'markdown',
type: 'markdown',
component: {
'x-component': 'Markdown',
},
content: `# Markdown Content 2`,
rowOrder: 2,
columnOrder: 2,
},
{
interface: 'markdown',
type: 'markdown',
component: {
'x-component': 'Markdown',
},
content: `# Markdown Content 1`,
rowOrder: 1,
columnOrder: 2,
},
],
},
{
interface: 'markdown',
type: 'markdown',
component: {
'x-component': 'Markdown',
},
content: `# Markdown Content`,
},
],
},
});
}

View File

@ -0,0 +1,10 @@
import Mock from 'mockjs';
export async function check(req: any, res: any) {
res.json({
data: {
username: `username - ${Mock.mock('@string')}`,
token: Mock.mock('@string'),
},
});
}

View File

@ -2,10 +2,9 @@
"name": "@nocobase/app", "name": "@nocobase/app",
"version": "0.4.0-alpha.7", "version": "0.4.0-alpha.7",
"scripts": { "scripts": {
"start": "concurrently \"cd ../../ && nodemon\" \"umi dev\"", "start": "umi dev",
"dev": "umi dev",
"build": "umi build", "build": "umi build",
"postinstall": "node ./umi.js generate tmp", "postinstall": "umi generate tmp",
"prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'", "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
"test": "umi-test", "test": "umi-test",
"test:coverage": "umi-test --coverage" "test:coverage": "umi-test --coverage"
@ -21,34 +20,19 @@
"prettier --parser=typescript --write" "prettier --parser=typescript --write"
] ]
}, },
"devDependencies": { "dependencies": {
"@ant-design/pro-layout": "^5.0.12",
"@formily/antd-components": "^1.3.6",
"@lourenci/react-kanban": "^2.1.0",
"@nocobase/client": "^0.4.0-alpha.7", "@nocobase/client": "^0.4.0-alpha.7",
"@types/react-big-calendar": "^0.24.8",
"@umijs/preset-react": "1.x", "@umijs/preset-react": "1.x",
"@umijs/test": "^3.2.23", "umi": "^3.0.0"
"ahooks": "^2.9.3",
"antd": "^4.13.0",
"array-move": "^3.0.1",
"attr-accept": "^2.2.2",
"clean-deep": "^3.4.0",
"concurrently": "^5.3.0",
"lint-staged": "^10.0.7",
"marked": "^2.0.0",
"nodemon": "^2.0.6",
"prettier": "^1.19.1",
"react": "16.14.0",
"react-big-calendar": "^0.30.0",
"react-dom": "16.14.0",
"react-drag-listview": "^0.1.8",
"react-image-lightbox": "^5.1.1",
"react-sortable-hoc": "^1.11.0",
"react-to-print": "^2.12.3",
"styled-components": "^4.1.1",
"umi": "^3.2.23",
"yorkie": "^2.0.0"
}, },
"gitHead": "f0b335ac30f29f25c95d7d137655fa64d8d67f1e" "devDependencies": {
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@umijs/test": "^3.4.15",
"mockjs": "^1.1.0",
"prettier": "^2.2.0",
"react": "17.x",
"react-dom": "17.x",
"yorkie": "^2.0.0"
}
} }

View File

@ -1,90 +0,0 @@
import { request } from 'umi';
interface ActionParams {
resourceKey?: string | number;
// resourceName?: string;
// associatedName?: string;
associatedKey?: string | number;
fields?: any;
filter?: any;
values?: any;
page?: any;
perPage?: any;
[key: string]: any;
}
interface Resource {
get: (params?: ActionParams, options?: any) => Promise<any>;
list: (params?: ActionParams, options?: any) => Promise<any>;
create: (params?: ActionParams, options?: any) => Promise<any>;
update: (params?: ActionParams, options?: any) => Promise<any>;
destroy: (params?: ActionParams, options?: any) => Promise<any>;
[name: string]: (params?: ActionParams, options?: any) => Promise<any>;
}
// TODO 待改进,提供一个封装度更完整的 SDKrequest 可以自由替换
class ApiClient {
resource(name: string): Resource {
const proxy: any = new Proxy(
{},
{
get(target, method, receiver) {
return (params: ActionParams = {}, options: any = {}) => {
let {
associatedKey,
resourceKey,
filter,
sorter,
sort = [],
values,
...restParams
} = params;
let url = `/${name}`;
sort = sort || [];
options.params = restParams;
if (['list', 'get', 'export'].indexOf(method as string) !== -1) {
options.method = 'get';
} else {
options.method = 'post';
options.data = values;
}
if (associatedKey) {
url = `/${name.split('.').join(`/${associatedKey}/`)}`;
}
url += `:${method as string}`;
// console.log(name, name.split('.'), associatedKey, name.split('.').join(`/${associatedKey}/`));
if (resourceKey) {
url += `/${resourceKey}`;
}
if (filter) {
options.params['filter'] = JSON.stringify(filter);
}
if (sorter) {
sort = [];
const arr = Array.isArray(sorter) ? sorter : [sorter];
arr.forEach(({ order, field }) => {
if (order === 'descend') {
sort.push(`-${field}`);
} else if (order === 'ascend') {
sort.push(field);
}
});
}
if (sort.length === 0) {
delete options.params['sort'];
} else {
options.params['sort'] = sort.join(',');
}
console.log({ url, params });
return request(url, options);
};
},
},
);
return proxy;
}
}
const api = new ApiClient();
export default api;

View File

@ -1,77 +0,0 @@
import './css_browser_selector';
import { RequestConfig, request as umiRequest, history } from 'umi';
import { configResponsive } from 'ahooks';
configResponsive({
small: 0,
middle: 800,
large: 1200,
});
export const request: RequestConfig = {
prefix: process.env.API,
errorConfig: {
adaptor: resData => {
return {
...resData,
success: true,
showType: 0,
};
},
},
middlewares: [
async (ctx, next) => {
const { headers } = ctx.req.options as any;
const token = localStorage.getItem('NOCOBASE_TOKEN');
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
await next();
},
],
};
// TODO目前这块是写在代码里的待改进
const pathnames = ['/login', '/register', '/lostpassword', '/resetpassword'];
export async function getInitialState() {
const { pathname, search } = location;
console.log(location);
const { data: systemSettings = {} } = await umiRequest(
'/system_settings:get?fields[appends]=logo,logo.storage',
{
method: 'get',
},
);
let redirect = `?redirect=${pathname}${search}`;
if (!pathnames.includes(pathname)) {
try {
const { data = {} } = await umiRequest('/users:check', {
method: 'post',
});
if (!data.id) {
history.push('/login' + redirect);
return {
systemSettings,
currentUser: {},
};
}
return {
systemSettings,
currentUser: data,
};
} catch (error) {
console.log(error);
history.push('/login' + redirect);
}
}
return {
systemSettings,
currentUser: {},
};
}

View File

@ -1,379 +0,0 @@
import React, { useState } from 'react';
import { Space, Button, Popconfirm, Popover } from 'antd';
import {
FilterOutlined,
PlusOutlined,
SelectOutlined,
DeleteOutlined,
PrinterOutlined,
ExportOutlined,
} from '@ant-design/icons';
import Drawer from '@/components/drawer';
import View from '@/components/views';
import get from 'lodash/get';
import set from 'lodash/set';
import ReactToPrint from 'react-to-print';
import api from '@/api-client';
import './style.less';
const ACTIONS = new Map<string, any>();
export function Create(props) {
const { size, onFinish, schema = {}, associatedKey, ...restProps } = props;
const { title, pageTitle, viewName, transform, componentProps = {} } = schema;
return (
<>
<Button
size={size}
onClick={() => {
Drawer.open({
title: pageTitle || title,
content: ({ resolve, closeWithConfirm }) => (
<div>
<View
{...restProps}
onValueChange={() => {
closeWithConfirm && closeWithConfirm(true);
}}
associatedKey={associatedKey}
viewName={viewName}
onReset={resolve}
onDraft={async item => {
const values = transform ? {} : item;
for (const [sourceKey, targetKey] of Object.entries<string>(
transform || {},
)) {
const value = get({ data: item }, sourceKey);
set(values, targetKey, value);
}
await resolve();
console.log('onFinish', values);
onFinish && (await onFinish(values));
}}
onFinish={async item => {
const values = transform ? {} : item;
for (const [sourceKey, targetKey] of Object.entries<string>(
transform || {},
)) {
const value = get({ data: item }, sourceKey);
set(values, targetKey, value);
}
await resolve();
console.log('onFinish', values);
onFinish && (await onFinish(values));
}}
/>
</div>
),
});
}}
icon={<PlusOutlined />}
type={'primary'}
{...componentProps}
>
{title}
</Button>
</>
);
}
export function Update(props) {
const { onFinish, data, schema = {}, associatedKey, ...restProps } = props;
const { title, viewName } = schema;
return (
<>
<Button
onClick={() => {
Drawer.open({
title: title,
content: ({ resolve, closeWithConfirm }) => (
<div>
<View
{...restProps}
associatedKey={associatedKey}
data={data}
viewName={viewName}
onReset={resolve}
onDraft={async values => {
await resolve();
onFinish && (await onFinish(values));
}}
onValueChange={() => {
closeWithConfirm && closeWithConfirm(true);
}}
onFinish={async values => {
await resolve();
onFinish && (await onFinish(values));
}}
/>
</div>
),
});
}}
icon={<PlusOutlined />}
type={'primary'}
>
{title}
</Button>
</>
);
}
export function Add(props) {
const { size, onFinish, schema = {}, associatedKey, ...restProps } = props;
console.log({ associatedKey }, 'add');
const { filter, title, viewName, transform, componentProps = {} } = schema;
return (
<>
<Button
size={size}
onClick={() => {
Drawer.open({
title: title,
content: ({ resolve }) => {
const [selectedRows, setSelectedRows] = useState([]);
return (
<div>
<View
{...restProps}
defaultFilter={filter}
viewName={viewName}
associatedKey={associatedKey}
onSelected={values => {
console.log(values);
setSelectedRows(
values.map(item => {
if (!transform) {
return;
}
const data = {};
for (const [sourceKey, targetKey] of Object.entries<
string
>(transform)) {
const value = get({ data: item }, sourceKey);
set(data, targetKey, value);
}
return data;
}),
);
}}
/>
<Drawer.Footer>
<Space>
<Button onClick={resolve}></Button>
<Button
type={'primary'}
onClick={async () => {
console.log({ schema, onFinish });
onFinish && (await onFinish(selectedRows));
resolve();
}}
>
</Button>
</Space>
</Drawer.Footer>
</div>
);
},
});
}}
icon={<SelectOutlined />}
{...componentProps}
>
{title}
</Button>
</>
);
}
export function Destroy(props) {
const { size, schema = {}, onFinish } = props;
const { title, componentProps = {} } = schema;
return (
<Popconfirm
title="确认删除吗?"
onConfirm={async e => {
onFinish && (await onFinish());
}}
>
<Button
size={size}
danger
type={'ghost'}
icon={<DeleteOutlined />}
{...componentProps}
>
{title}
</Button>
</Popconfirm>
);
}
export function Filter(props) {
const { schema = {}, onFinish } = props;
const { title, fields = [] } = schema;
const [visible, setVisible] = useState(false);
const [data, setData] = useState({});
const [filterCount, setFilterCount] = useState(0);
console.log('Filter', { visible, data });
return (
<>
{visible && (
<div
style={{
height: '100vh',
width: '100vw',
zIndex: 1000,
position: 'fixed',
background: 'rgba(0, 0, 0, 0)',
top: 0,
left: 0,
}}
onClick={() => setVisible(false)}
></div>
)}
<Popover
// title="设置筛选"
trigger="click"
visible={visible}
defaultVisible={visible}
placement={'bottomLeft'}
destroyTooltipOnHide
onVisibleChange={visible => {
setVisible(visible);
}}
className={'filters-popover'}
style={{}}
overlayStyle={{
minWidth: 500,
}}
content={
<>
<View
data={data}
onFinish={async values => {
if (values) {
const items = values.filter.and || values.filter.or;
setFilterCount(Array.isArray(items) ? items.length : 0);
setData(values);
onFinish && (await onFinish(values));
}
setVisible(false);
}}
schema={{
type: 'filterForm',
fields: [
{
dataIndex: ['filter'],
name: 'filter',
interface: 'json',
type: 'json',
component: {
type: 'filter',
'x-component-props': {
fields,
},
},
},
],
}}
/>
</>
}
>
<Button icon={<FilterOutlined />}>
{filterCount ? `${filterCount}${title}` : title}
</Button>
</Popover>
</>
);
}
export function Print(props) {
const { schema = {}, contentRef } = props;
const { title, buttonProps = {} } = schema;
return (
<ReactToPrint
trigger={() => (
<Button {...buttonProps} icon={<PrinterOutlined />}>
{title}
</Button>
)}
content={() => contentRef.current}
pageStyle={`
@page {
margin: 1cm;
}
table { page-break-inside:auto }
tr { page-break-inside:avoid; page-break-after:auto }
`}
documentTitle={' '}
/>
);
}
export function Export(props) {
const { schema = {}, onFinish } = props;
const { title, buttonProps = {} } = schema;
return (
<Button
{...buttonProps}
icon={<ExportOutlined />}
onClick={async () => {
onFinish && (await onFinish({ fields: [] }));
}}
>
{title}
</Button>
);
}
export function Actions(props) {
const { onTrigger = {}, actions = [], style, ...restProps } = props;
return (
actions.length > 0 && (
<div className={'action-buttons'} style={style}>
{actions.map(
action =>
ACTIONS.has(action.type) && (
<div key={action.__index} className={`${action.type}-action-button action-button`}>
<Action
{...restProps}
onFinish={onTrigger[action.type]}
schema={action}
/>
</div>
),
)}
</div>
)
);
}
export default Actions;
export function registerAction(type: string, Action: any) {
ACTIONS.set(type, Action);
}
export function getAction(type: string) {
return ACTIONS.get(type);
}
export function Action(props) {
const { schema = {} } = props;
// cnsole.log(schema);
const { type } = schema;
const Component = getAction(type);
return Component && <Component {...props} />;
}
registerAction('add', Add);
registerAction('update', Update);
registerAction('create', Create);
registerAction('destroy', Destroy);
registerAction('filter', Filter);
registerAction('print', Print);
registerAction('export', Export);

View File

@ -1,15 +0,0 @@
.action-buttons {
display: flex;
// justify-content: flex-end;
min-height: 32px;
flex-direction: row-reverse;
align-items: flex-start;
.action-button {
margin-left: 8px;
}
.filter-action-button {
position: absolute;
left: 24px;
margin-left: 0;
}
}

View File

@ -1,200 +0,0 @@
import React, { Fragment } from 'react';
import {
ISchemaFieldComponentProps,
SchemaField,
} from '@formily/react-schema-renderer';
import { toArr, isFn, FormPath } from '@formily/shared';
import { ArrayList } from '@formily/react-shared-components';
import { CircleButton } from '../circle-button';
import { TextButton } from '../text-button';
import { Card } from 'antd';
import {
PlusOutlined,
DeleteOutlined,
DownOutlined,
UpOutlined,
} from '@ant-design/icons';
import styled from 'styled-components';
const ArrayComponents = {
CircleButton,
TextButton,
AdditionIcon: () => <PlusOutlined />,
RemoveIcon: () => <DeleteOutlined />,
MoveDownIcon: () => <DownOutlined />,
MoveUpIcon: () => <UpOutlined />,
};
export const ArrayCards: any = styled(
(props: ISchemaFieldComponentProps & { className: string }) => {
const { value, schema, className, editable, path, mutators } = props;
const {
renderAddition,
renderRemove,
renderMoveDown,
renderMoveUp,
renderEmpty,
renderExtraOperations,
...componentProps
} = schema.getExtendsComponentProps() || {};
const schemaItems = Array.isArray(schema.items)
? schema.items[schema.items.length - 1]
: schema.items;
const onAdd = () => {
if (schemaItems) {
mutators.push(schemaItems.getEmptyValue());
}
};
return (
<div className={className}>
<ArrayList
value={value}
minItems={schema.minItems}
maxItems={schema.maxItems}
editable={editable}
components={ArrayComponents}
renders={{
renderAddition,
renderRemove,
renderMoveDown,
renderMoveUp,
renderEmpty,
}}
>
{toArr(value).map((item, index) => {
return (
<Card
{...componentProps}
size="small"
className={`card-list-item`}
key={index}
title={
<span>
{index + 1}
<span>.</span> {componentProps.title || schema.title}
</span>
}
extra={
<Fragment>
<ArrayList.Remove
index={index}
onClick={() => mutators.remove(index)}
/>
<ArrayList.MoveDown
index={index}
onClick={() => mutators.moveDown(index)}
/>
<ArrayList.MoveUp
index={index}
onClick={() => mutators.moveUp(index)}
/>
{isFn(renderExtraOperations)
? renderExtraOperations(index)
: renderExtraOperations}
</Fragment>
}
>
{schemaItems && (
<SchemaField
path={FormPath.parse(path).concat(index)}
schema={schemaItems}
/>
)}
</Card>
);
})}
<ArrayList.Empty>
{({ children, allowAddition }) => {
return (
<Card
{...componentProps}
size="small"
className={`card-list-item card-list-empty ${
allowAddition ? 'add-pointer' : ''
}`}
onClick={allowAddition ? onAdd : undefined}
>
<div className="empty-wrapper">{children}</div>
</Card>
);
}}
</ArrayList.Empty>
<ArrayList.Addition>
{({ children, isEmpty }) => {
if (!isEmpty) {
return (
<div className="array-cards-addition" onClick={onAdd}>
{children}
</div>
);
}
}}
</ArrayList.Addition>
</ArrayList>
</div>
);
},
)<ISchemaFieldComponentProps>`
width: 100%;
.ant-card {
.ant-card {
box-shadow: none;
}
.ant-card-body {
padding: 20px 10px 0 10px;
}
.array-cards-addition {
box-shadow: none;
border: 1px solid #eee;
transition: all 0.35s ease-in-out;
&:hover {
border: 1px solid #ccc;
}
}
.empty-wrapper {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 10px;
img {
height: 85px;
}
.ant-btn {
color: #888;
}
}
}
.card-list-empty.card-list-item.add-pointer {
cursor: pointer;
}
.array-cards-addition {
margin-top: 10px;
margin-bottom: 3px;
background: #fff;
display: flex;
cursor: pointer;
padding: 5px 0;
justify-content: center;
box-shadow: 1px 1px 4px 0 rgba(0, 0, 0, 0.1);
}
.card-list-item {
margin-top: 10px;
border: 1px solid #eee;
}
.card-list-item:first-child {
margin-top: 0 !important;
}
.ant-card-extra {
display: flex;
button {
margin-right: 8px;
}
}
`;
ArrayCards.isFieldComponent = true;
export default ArrayCards;

View File

@ -1 +0,0 @@
import 'antd/lib/card/style/index';

View File

@ -1,238 +0,0 @@
import React, { useContext } from 'react';
import {
ISchemaFieldComponentProps,
SchemaField,
Schema,
complieExpression,
FormExpressionScopeContext,
} from '@formily/react-schema-renderer';
import { toArr, isFn, isArr, FormPath } from '@formily/shared';
import { ArrayList, DragListView } from '@formily/react-shared-components';
import { CircleButton } from '../circle-button';
import { TextButton } from '../text-button';
import { Table, Form, Button } from 'antd';
import { FormItemShallowProvider } from '@formily/antd';
import {
PlusOutlined,
DeleteOutlined,
DownOutlined,
UpOutlined,
} from '@ant-design/icons';
import styled from 'styled-components';
const ArrayComponents = {
CircleButton,
TextButton,
AdditionIcon: () => (
<>
<PlusOutlined />
</>
),
RemoveIcon: () => <DeleteOutlined />,
MoveDownIcon: () => <DownOutlined />,
MoveUpIcon: () => <UpOutlined />,
};
const DragHandler = styled.span`
width: 7px;
display: inline-block;
height: 14px;
border: 2px dotted #c5c5c5;
border-top: 0;
border-bottom: 0;
cursor: move;
margin-bottom: 24px;
`;
export const ArrayTable: any = styled(
(props: ISchemaFieldComponentProps & { className: string }) => {
const expressionScope = useContext(FormExpressionScopeContext);
const { value, schema, className, editable, path, mutators } = props;
const {
renderAddition,
renderRemove,
renderMoveDown,
renderMoveUp,
renderEmpty,
renderExtraOperations,
operationsWidth,
operations,
draggable,
...componentProps
} = schema.getExtendsComponentProps() || {};
const schemaItems = Array.isArray(schema.items)
? schema.items[schema.items.length - 1]
: schema.items;
const onAdd = () => {
if (schemaItems) {
mutators.push(schemaItems.getEmptyValue());
}
};
const onMove = (dragIndex, dropIndex) => {
mutators.move(dragIndex, dropIndex);
};
const renderColumns = (items: Schema) => {
return items.mapProperties((props, key) => {
const itemProps = {
...props.getExtendsItemProps(),
...props.getExtendsProps(),
};
return {
title: complieExpression(props.title, expressionScope),
...itemProps,
key,
dataIndex: key,
render: (value: any, record: any, index: number) => {
const newPath = FormPath.parse(path).concat(index, key);
return (
<FormItemShallowProvider
key={newPath.toString()}
label={undefined}
labelCol={undefined}
wrapperCol={undefined}
>
<SchemaField path={newPath} schema={props} />
</FormItemShallowProvider>
);
},
};
});
};
// 兼容异步items schema传入
let columns = [];
if (schema.items) {
columns = isArr(schema.items)
? schema.items.reduce((buf, items) => {
return buf.concat(renderColumns(items));
}, [])
: renderColumns(schema.items);
}
if (editable && operations !== false) {
columns.push({
...operations,
key: 'operations',
dataIndex: 'operations',
width: operationsWidth || 200,
render: (value: any, record: any, index: number) => {
return (
<Form.Item>
<div className="array-item-operator">
<ArrayList.Remove
index={index}
onClick={() => mutators.remove(index)}
/>
<ArrayList.MoveDown
index={index}
onClick={() => mutators.moveDown(index)}
/>
<ArrayList.MoveUp
index={index}
onClick={() => mutators.moveUp(index)}
/>
{isFn(renderExtraOperations)
? renderExtraOperations(index)
: renderExtraOperations}
</div>
</Form.Item>
);
},
});
}
if (draggable) {
columns.unshift({
width: 20,
key: 'dragHandler',
render: () => {
return <DragHandler className="drag-handler" />;
},
});
}
const renderTable = () => {
return (
<Table
{...componentProps}
rowKey={record => {
return toArr(value).indexOf(record);
}}
pagination={false}
columns={columns}
dataSource={toArr(value)}
></Table>
);
};
return (
<div className={className}>
<ArrayList
value={value}
minItems={schema.minItems}
maxItems={schema.maxItems}
editable={editable}
components={ArrayComponents}
renders={{
renderAddition,
renderRemove,
renderMoveDown,
renderMoveUp,
renderEmpty,
}}
>
{draggable ? (
<DragListView
onDragEnd={onMove}
nodeSelector="tr.ant-table-row"
ignoreSelector="tr.ant-table-expanded-row"
>
{renderTable()}
</DragListView>
) : (
renderTable()
)}
<ArrayList.Addition>
{({ children }) => {
return (
children && (
<div className="array-table-addition" onClick={onAdd}>
{children}
</div>
)
);
}}
</ArrayList.Addition>
</ArrayList>
</div>
);
},
)`
width: 100%;
margin-bottom: 10px;
table {
margin-bottom: 0 !important;
}
.array-table-addition {
background: #fbfbfb;
cursor: pointer;
margin-top: 3px;
border-radius: 3px;
.next-btn-text {
color: #888;
}
.next-icon:before {
width: 16px !important;
font-size: 16px !important;
margin-right: 5px;
}
}
.ant-btn {
color: #888;
}
.array-item-operator {
display: flex;
button {
margin-right: 8px;
}
}
`;
ArrayTable.isFieldComponent = true;
export default ArrayTable;

View File

@ -1 +0,0 @@
import 'antd/lib/table/style/index';

View File

@ -1,361 +0,0 @@
import React, { useEffect, useState } from 'react';
import { connect } from '@formily/react-schema-renderer';
import {
Button,
Select,
DatePicker,
Tag,
InputNumber,
TimePicker,
Input,
} from 'antd';
import {
Select as AntdSelect,
mapStyledProps,
mapTextComponent,
} from '../shared';
import moment from 'moment';
import './style.less';
import api from '@/api-client';
import { useRequest } from 'umi';
export const DateTime = connect({
getProps: mapStyledProps,
getComponent: mapTextComponent,
})(props => {
const { associatedKey, automationType, filter, onChange } = props;
const [aKey, setaKey] = useState(associatedKey);
const [aType, setaType] = useState(automationType);
console.log('Automations.DateTime', aKey, associatedKey);
const [value, setValue] = useState(props.value || {});
const [offsetType, setOffsetType] = useState(() => {
if (!value.offset) {
return 'current';
}
if (value.offset > 0) {
return 'after';
}
if (value.offset < 0) {
return 'before';
}
return 'current';
});
useEffect(() => {
if (associatedKey !== aKey || automationType !== aType) {
setOffsetType('current');
setValue({
value: null,
byField: null,
offset: 0,
unit: undefined,
});
onChange({
value: null,
byField: null,
offset: 0,
unit: undefined,
});
setaKey(associatedKey);
}
}, [associatedKey, automationType, aKey, aType]);
const { data = [], loading = true } = useRequest(
() => {
return associatedKey && automationType !== 'schedule'
? api.resource('collections.fields').list({
associatedKey,
filter: filter || {
type: 'date',
},
})
: Promise.resolve({ data: [] });
},
{
refreshDeps: [associatedKey, automationType, filter],
},
);
console.log({ data });
return (
<div>
<div>
{automationType === 'schedule' ? (
<DatePicker
showTime
onChange={(m, dateString) => {
onChange({ value: m.toISOString() });
setValue({ value: m.toISOString() });
// console.log('Automations.DateTime', m.toISOString(), {m, dateString})
}}
defaultValue={(() => {
if (!value.value) {
return undefined;
}
const m = moment(value.value);
return m.isValid() ? m : undefined;
})()}
/>
) : (
<Input.Group compact>
<Select
style={{ width: 120 }}
value={value.byField}
onChange={v => {
setValue({ ...value, byField: v });
onChange({ ...value, byField: v });
}}
loading={loading}
options={data.map(item => ({
value: item.name,
label: item.title || item.name,
}))}
placeholder={'选择日期字段'}
></Select>
<Select
onChange={offsetType => {
let values = { ...value };
switch (offsetType) {
case 'current':
values = { byField: values.byField, offset: 0 };
break;
case 'before':
if (values.offset) {
values.offset = -1 * Math.abs(values.offset);
}
break;
case 'after':
if (values.offset) {
values.offset = Math.abs(values.offset);
}
break;
}
setOffsetType(offsetType);
setValue(values);
onChange(values);
}}
value={offsetType}
placeholder={'选择日期字段'}
>
<Select.Option value={'current'}></Select.Option>
<Select.Option value={'before'}></Select.Option>
<Select.Option value={'after'}></Select.Option>
</Select>
{offsetType !== 'current' && (
<InputNumber
step={1}
min={1}
value={Math.abs(value.offset) || undefined}
onChange={(offset: number) => {
const values = {
unit: 'day',
...value,
};
if (offsetType === 'before') {
values.offset = -1 * Math.abs(offset);
} else if (offsetType === 'after') {
values.offset = Math.abs(offset);
}
setValue(values);
onChange(values);
console.log('Automations.DateTime', values);
// console.log(offsetType);
}}
placeholder={'数字'}
/>
)}
{offsetType !== 'current' && (
<Select
onChange={v => {
setValue({ ...value, unit: v });
onChange({ ...value, unit: v });
}}
value={value.unit}
placeholder={'选择单位'}
>
<Select.Option value={'second'}></Select.Option>
<Select.Option value={'minute'}></Select.Option>
<Select.Option value={'hour'}></Select.Option>
<Select.Option value={'day'}></Select.Option>
<Select.Option value={'week'}></Select.Option>
<Select.Option value={'month'}></Select.Option>
</Select>
)}
{offsetType !== 'current' &&
value.unit &&
['day', 'week', 'month'].indexOf(value.unit) !== -1 && (
<TimePicker
value={(() => {
const m = moment(value.time, 'HH:mm:ss');
return m.isValid() ? m : undefined;
})()}
onChange={(m, dateString) => {
console.log('Automations.DateTime', m, dateString);
setValue({ ...value, time: dateString });
onChange({ ...value, time: dateString });
}}
/>
)}
</Input.Group>
)}
</div>
</div>
);
});
const cronmap = {
none: '不重复',
everysecond: '每秒',
everyminute: '每分钟',
everyhour: '每小时',
everyday: '每天',
everyweek: '每周',
everymonth: '每月',
custom: '自定义',
};
export const Cron = connect({
getProps: mapStyledProps,
getComponent: mapTextComponent,
})(props => {
const { value, onChange } = props;
console.log('Automations.DateTime', { value });
const re = /every_(\d+)_(.+)/i;
const match = cronmap[value] ? null : re.exec(value);
const [unit, setUnit] = useState(match ? match[2] : 'days');
const [num, setNum] = useState<any>(match ? parseInt(match[1]) : undefined);
const [cron, setCron] = useState(() => {
if (!value) {
return 'none';
}
return match ? 'custom' : cronmap[value];
});
return (
<div>
<Input.Group compact>
<Select
value={cron}
onChange={v => {
setCron(v);
onChange(v);
}}
>
{Object.keys(cronmap).map(key => {
return <Select.Option value={key}>{cronmap[key]}</Select.Option>;
})}
</Select>
{cron === 'custom' && (
<Input
type={'number'}
onChange={e => {
const v = parseInt(e.target.value);
setNum(v);
onChange(`every_${v}_${unit}`);
}}
defaultValue={num}
addonBefore={'每'}
/>
)}
{cron === 'custom' && (
<Select
onChange={v => {
setUnit(v);
onChange(`every_${num}_${v}`);
}}
defaultValue={unit}
>
<Select.Option value={'seconds'}></Select.Option>
<Select.Option value={'minutes'}></Select.Option>
<Select.Option value={'hours'}></Select.Option>
<Select.Option value={'days'}></Select.Option>
<Select.Option value={'weeks'}></Select.Option>
<Select.Option value={'months'}></Select.Option>
</Select>
)}
</Input.Group>
</div>
);
});
export const EndMode = connect({
getProps: mapStyledProps,
getComponent: mapTextComponent,
})(props => {
const { value = 'none', onChange, automationType } = props;
const re = /after_(\d+)_times/i;
const match = re.exec(value);
const [mode, setMode] = useState(() => {
if (automationType === 'schedule' && value === 'byField') {
return 'none';
} else if (
automationType === 'collections:schedule' &&
value === 'customTime'
) {
return 'none';
}
return match ? 'times' : value;
});
const [num, setNum] = useState(match ? parseInt(match[1]) : undefined);
useEffect(() => {
if (automationType === 'schedule' && value === 'byField') {
setMode('none');
onChange('none');
} else if (
automationType === 'collections:schedule' &&
value === 'customTime'
) {
setMode('none');
onChange('none');
}
}, [automationType]);
console.log('Automations.DateTime', { value, automationType, mode });
return (
<div>
<Input.Group compact>
<Select
style={{ width: 150 }}
value={mode}
onChange={v => {
setMode(v);
onChange(v);
}}
>
<Select.Option value={'none'}></Select.Option>
<Select.Option value={'times'}></Select.Option>
{automationType === 'schedule' && (
<Select.Option value={'customTime'}></Select.Option>
)}
{automationType === 'collections:schedule' && (
<Select.Option value={'byField'}></Select.Option>
)}
</Select>
{mode === 'times' && (
<Input
type={'number'}
onChange={e => {
const v = parseInt(e.target.value);
setNum(v);
onChange(`after_${v}_times`);
}}
defaultValue={num}
addonAfter={'次'}
/>
)}
</Input.Group>
</div>
);
});
export const Automations = {
DateTime,
Cron,
EndMode,
};

View File

@ -1,5 +0,0 @@
.ant-input-group.ant-input-group-compact {
.ant-input-group-wrapper {
width: 120px;
}
}

View File

@ -1,161 +0,0 @@
import React, { useEffect, useState } from 'react';
import { connect } from '@formily/react-schema-renderer';
import { Cascader as AntdCascader } from 'antd';
import { useRequest } from 'umi';
import api from '@/api-client';
import {
transformDataSourceKey,
mapStyledProps,
mapTextComponent,
} from '../shared';
function findTreeNode(
tree,
values,
{ value = 'value', children = 'children' },
) {
let node, i;
for (node = tree, i = 0; node && values[i]; i++) {
node = (node[children] || []).find(
item => item[value] === values[i][value],
);
}
return node;
}
export const Cascader = connect({
getProps: mapStyledProps,
getComponent: mapTextComponent,
})(function(props) {
const {
disabled,
// target,
// labelField,
// valueField = 'id',
// parentField,
// maxLevel,
// changeOnSelect,
value = [],
onChange,
schema = {},
placeholder,
// TODO(feature): 增加静态数据支持
// dataSource: []
} = props;
const {
target,
targetKey: valueField,
// 值字段
// valueField: 'code',
// 名称字段
labelField,
// TODO(refactor): 等 toWhere 重构完成后要改成 parent
// 上级字段名
parentField,
maxLevel,
// valueField = 'id',
// 深度限制,默认:-1代表不控制即如果是数据表则无限加载
// limit: -1,
// 可选层级,默认:-1代表可选的最深层级
// maxLevel: null,
// 是否可以不选择到最深一级
// 'x-component-props': { changeOnSelect: true }
incompletely: changeOnSelect,
} = schema;
const fieldNames = {
label: labelField,
value: valueField,
children: 'children',
};
const [options, setOptions] = useState([]);
const { loading, run } = useRequest(
async (selectedOptions = []) => {
if (maxLevel != null && selectedOptions.length >= maxLevel) {
return;
}
const last = selectedOptions[selectedOptions.length - 1] || null;
if (last) {
if (last.isLeaf) {
return;
}
last.loading = true;
}
return api.resource(target).list({
filter: {
[parentField]: last && last[valueField],
},
perPage: -1,
sort: [valueField],
});
// TODO(bug): 关联资源加载问题较多,暂时先用 filter 解决
// return api.resource(`${target}.${target}`).list({
// associatedKey: last,
// perPage: -1
// });
},
{
manual: true,
onSuccess(result, [selectedOptions = []]) {
if (!result) {
return;
}
const data = result.map(item => ({
...item,
isLeaf: maxLevel != null && item.level >= maxLevel,
}));
// 找到已有值指向的 options 节点
const root = { [fieldNames.children]: options };
const node = findTreeNode(root, selectedOptions, fieldNames);
if (node && node !== root) {
node.children = data;
node.loading = false;
// use spread array to avoid popup to be collapsed
setOptions([...options]);
} else {
setOptions(data);
}
},
},
);
// 根据 value 的值,按需预加载相应的数据
useEffect(() => {
if (value.length) {
value.reduce(
(promise, option, i) => promise.then(() => run(value.slice(0, i))),
Promise.resolve(),
);
} else {
run([]);
}
}, []);
return (
<AntdCascader
placeholder={placeholder}
disabled={disabled}
options={options}
value={value.map(item => item[valueField])}
onChange={(v, selected) => {
if (maxLevel != null && v.length < maxLevel) {
run(selected);
}
if (changeOnSelect || !v.length || v.length >= maxLevel) {
onChange(selected);
}
}}
loadData={run}
changeOnSelect={changeOnSelect}
fieldNames={fieldNames}
/>
);
});
export default Cascader;

View File

@ -1 +0,0 @@
import 'antd/lib/cascader/style/index';

View File

@ -1,19 +0,0 @@
import { connect } from '@formily/react-schema-renderer';
import { Checkbox as AntdCheckbox } from 'antd';
import {
transformDataSourceKey,
mapStyledProps,
mapTextComponent,
} from '../shared';
export const Checkbox = connect<'Group'>({
valueName: 'checked',
getProps: mapStyledProps,
})(AntdCheckbox);
Checkbox.Group = connect({
getProps: mapStyledProps,
getComponent: mapTextComponent,
})(transformDataSourceKey(AntdCheckbox.Group, 'options'));
export default Checkbox;

View File

@ -1 +0,0 @@
import 'antd/lib/checkbox/style/index';

View File

@ -1,16 +0,0 @@
import React from 'react';
import { Button } from 'antd';
import { ButtonProps } from 'antd/lib/button';
export const CircleButton: React.FC<ButtonProps> = props => {
const hasText = String(props.className || '').indexOf('has-text') > -1;
return (
<Button
type={hasText ? 'link' : undefined}
shape={hasText ? undefined : 'circle'}
{...props}
/>
);
};
export default CircleButton;

View File

@ -1 +0,0 @@
import 'antd/lib/button/style/index';

View File

@ -1,40 +0,0 @@
import { connect } from '@formily/react-schema-renderer';
import React from 'react';
import { Select, Tag } from 'antd';
import {
Select as AntdSelect,
mapStyledProps,
mapTextComponent,
} from '../shared';
export const ColorSelect = connect({
getProps: mapStyledProps,
getComponent: mapTextComponent,
})(props => {
const colors = {
red: '薄暮',
magenta: '法式洋红',
volcano: '火山',
orange: '日暮',
gold: '金盏花',
lime: '青柠',
green: '极光绿',
cyan: '明青',
blue: '拂晓蓝',
geekblue: '极客蓝',
purple: '酱紫',
default: '默认',
};
return (
<Select {...props}>
{Object.keys(colors).map(color => (
<Select.Option value={color}>
<Tag color={color}>{colors[color]}</Tag>
</Select.Option>
))}
</Select>
);
});
export default ColorSelect;

View File

@ -1,105 +0,0 @@
import React from 'react';
import { connect } from '@formily/react-schema-renderer';
import moment from 'moment';
import { DatePicker as AntdDatePicker } from 'antd';
import {
mapStyledProps,
mapTextComponent,
compose,
isStr,
isArr,
} from '../shared';
class YearPicker extends React.Component {
public render() {
return <AntdDatePicker {...this.props} picker={'year'} />;
}
}
const transformMoment = (value, format = 'YYYY-MM-DD HH:mm:ss') => {
if (value === '') return undefined;
return value && value.format ? value.format(format) : value;
};
const mapMomentValue = (props: any, fieldProps: any) => {
const { value, showTime = false } = props;
if (!fieldProps.editable) return props;
try {
if (isStr(value) && value) {
props.value = moment(
value,
showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD',
);
} else if (isArr(value) && value.length) {
props.value = value.map(
item =>
(item &&
moment(item, showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD')) ||
'',
);
}
} catch (e) {
throw new Error(e);
}
return props;
};
export const DatePicker = connect<
'RangePicker' | 'MonthPicker' | 'YearPicker' | 'WeekPicker'
>({
getValueFromEvent(_, value) {
const props = this.props || {};
return transformMoment(
value,
props.format || (props.showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD'),
);
},
getProps: compose(mapStyledProps, mapMomentValue),
getComponent: mapTextComponent,
})(AntdDatePicker);
DatePicker.RangePicker = connect({
getValueFromEvent(_, [startDate, endDate]) {
const props = this.props || {};
const format =
props.format || (props.showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD');
return [
transformMoment(startDate, format),
transformMoment(endDate, format),
];
},
getProps: compose(mapStyledProps, mapMomentValue),
getComponent: mapTextComponent,
})(AntdDatePicker.RangePicker);
DatePicker.MonthPicker = connect({
getValueFromEvent(_, value) {
return transformMoment(value);
},
getProps: compose(mapStyledProps, mapMomentValue),
getComponent: mapTextComponent,
})(AntdDatePicker.MonthPicker);
DatePicker.WeekPicker = connect({
getValueFromEvent(_, value) {
return transformMoment(value, 'gggg-wo');
},
getProps: compose(mapStyledProps, props => {
if (isStr(props.value) && props.value) {
const parsed = props.value.match(/\D*(\d+)\D*(\d+)\D*/) || ['', '', ''];
props.value = moment(parsed[1], 'YYYY').add(parsed[2] - 1, 'weeks');
}
return props;
}),
getComponent: mapTextComponent,
})(AntdDatePicker.WeekPicker);
DatePicker.YearPicker = connect({
getValueFromEvent(_, value) {
return transformMoment(value, 'YYYY');
},
getProps: compose(mapStyledProps, mapMomentValue),
getComponent: mapTextComponent,
})(YearPicker);
export default DatePicker;

View File

@ -1 +0,0 @@
import 'antd/lib/date-picker/style/index';

View File

@ -1,167 +0,0 @@
import React, { useState } from 'react';
import { connect } from '@formily/react-schema-renderer';
import { Select, Button, Space } from 'antd';
import {
mapStyledProps,
mapTextComponent,
compose,
isStr,
isArr,
} from '../shared';
import Drawer from '@/components/drawer';
import View from '@/components/views';
function transform({ value, multiple, labelField, valueField = 'id' }) {
let selectedKeys = [];
let selectedValue = [];
const values = Array.isArray(value) ? value : [value];
selectedKeys = values.filter(item => item).map(item => item[valueField]);
selectedValue = values
.filter(item => item)
.map(item => {
return {
value: item[valueField],
label: item[labelField],
};
});
if (!multiple) {
return [selectedKeys.shift(), selectedValue.shift()];
}
return [selectedKeys, selectedValue];
}
export function DrawerSelectComponent(props) {
const {
__parent,
size,
schema = {},
disabled,
viewName,
target,
multiple,
filter,
resourceName,
associatedKey,
valueField = 'id',
value,
onChange,
} = props;
const labelField = props.labelField || schema.labelField;
const [selectedKeys, selectedValue] = transform({
value,
multiple,
labelField,
valueField,
});
const [selectedRowKeys, setSelectedRowKeys] = useState(
multiple ? selectedKeys : [selectedKeys],
);
const [selectedRows, setSelectedRows] = useState(selectedValue);
const [options, setOptions] = useState(selectedValue);
const { title = '' } = schema;
console.log({ schema });
return (
<>
<Select
disabled={disabled}
open={false}
mode={multiple ? 'tags' : undefined}
labelInValue
size={size}
allowClear={true}
value={options}
notFoundContent={''}
onChange={data => {
setOptions(data);
if (Array.isArray(data)) {
const srks = data.map(item => item.value);
onChange(srks);
setSelectedRowKeys(srks);
console.log('datadatadatadata', { data, srks });
} else if (data && typeof data === 'object') {
onChange(data.value);
setSelectedRowKeys([data.value]);
} else {
console.log(data);
onChange(null);
setSelectedRowKeys([]);
}
}}
onClick={() => {
if (!disabled) {
Drawer.open({
title: `选择要关联的${title}数据`,
content: ({ resolve }) => {
console.log(
'valuevaluevaluevaluevaluevalue',
selectedRowKeys,
selectedRows,
options,
);
const [rows, setRows] = useState(selectedRows);
const [rowKeys, setRowKeys] = useState(selectedRowKeys);
const [selected, setSelected] = useState(
Array.isArray(value) ? value : [value],
);
console.log({ selectedRowKeys });
return (
<>
<View
isFieldComponent={true}
__parent={__parent}
associatedKey={associatedKey}
multiple={multiple}
defaultFilter={filter}
defaultSelectedRowKeys={selectedRowKeys}
onSelected={values => {
setSelected(values);
const [selectedKeys, selectedValue] = transform({
value: values,
multiple: true,
labelField,
valueField,
});
setSelectedRows(selectedValue);
setRows(selectedValue);
setSelectedRowKeys(selectedKeys);
setRowKeys(selectedKeys);
console.log({ values, selectedValue, selectedKeys });
console.log({ selectedRows, selectedRowKeys });
}}
viewName={viewName || `${target}.table`}
/>
<Drawer.Footer>
<Space>
<Button onClick={resolve}></Button>
<Button
onClick={() => {
setOptions(rows);
// console.log('valuevaluevaluevaluevaluevalue', {selectedRowKeys});
onChange(multiple ? selected : selected.shift());
// console.log({rows, rowKeys});
resolve();
}}
type={'primary'}
>
</Button>
</Space>
</Drawer.Footer>
</>
);
},
});
// setVisible(true);
}
}}
></Select>
</>
);
}
export const DrawerSelect = connect({
getProps: mapStyledProps,
getComponent: mapTextComponent,
})(DrawerSelectComponent);
export default DrawerSelect;

View File

@ -1,646 +0,0 @@
import React, { useCallback, useEffect, useState } from 'react';
import {
Button,
Select,
Input,
Space,
Form,
InputNumber,
DatePicker,
TimePicker,
Radio,
} from 'antd';
import { PlusCircleOutlined, CloseCircleOutlined } from '@ant-design/icons';
import useDynamicList from './useDynamicList';
import { connect } from '@formily/react-schema-renderer';
import { mapStyledProps } from '../shared';
import get from 'lodash/get';
import moment from 'moment';
import './style.less';
import api from '@/api-client';
import { useRequest } from 'umi';
export function FilterGroup(props: any) {
const {
showDeleteButton = true,
fields = [],
sourceFields = [],
onDelete,
onChange,
onAdd,
dataSource = {},
} = props;
const { list, getKey, push, remove, replace } = useDynamicList<any>(
dataSource.list || [
{
type: 'item',
},
],
);
let style: any = {
position: 'relative',
};
if (showDeleteButton) {
style = {
...style,
marginBottom: 14,
padding: 14,
border: '1px dashed #dedede',
};
}
return (
<div style={style}>
<div style={{ marginBottom: 14 }}>
{' '}
<Select
style={{ width: 80 }}
onChange={value => {
onChange({ type: 'group', list, andor: value });
}}
defaultValue={dataSource.andor || 'and'}
>
<Select.Option value={'and'}></Select.Option>
<Select.Option value={'or'}></Select.Option>
</Select>{' '}
</div>
<div>
{list.map((item, index) => {
// console.log(item);
const Component = item.type === 'group' ? FilterGroup : FilterItem;
return (
<div style={{ marginBottom: 8 }}>
{
<Component
fields={fields}
sourceFields={sourceFields}
dataSource={item}
// showDeleteButton={list.length > 1}
onChange={value => {
replace(index, value);
const newList = [...list];
newList[index] = value;
onChange({ ...dataSource, list: newList });
// console.log(list, value, index);
}}
onDelete={() => {
remove(index);
const newList = [...list];
newList.splice(index, 1);
onChange({ ...dataSource, list: newList });
// console.log(list, index);
}}
/>
}
</div>
);
})}
</div>
<div>
<Space>
<Button
style={{ padding: 0 }}
type={'link'}
onClick={() => {
const data = {
type: 'item',
};
push(data);
const newList = [...list];
newList.push(data);
onChange({ ...dataSource, list: newList });
}}
>
<PlusCircleOutlined />
</Button>{' '}
<Button
style={{ padding: 0 }}
type={'link'}
onClick={() => {
const data = {
type: 'group',
list: [
{
type: 'item',
},
],
};
push(data);
const newList = [...list];
newList.push(data);
onChange({ ...dataSource, list: newList });
}}
>
<PlusCircleOutlined />
</Button>
{showDeleteButton && (
<Button
className={'filter-remove-link filter-group'}
style={{
padding: 0,
position: 'absolute',
top: 0,
right: 0,
width: 32,
}}
type={'link'}
onClick={e => {
onDelete && onDelete(e);
}}
>
<CloseCircleOutlined />
</Button>
)}
</Space>
</div>
</div>
);
}
interface FieldOptions {
name: string;
title: string;
interface: string;
[key: string]: any;
}
interface FilterItemProps {
fields: FieldOptions[];
[key: string]: any;
}
const OP_MAP = {
string: [
{ label: '包含', value: '$includes', selected: true },
{ label: '不包含', value: '$notIncludes' },
{ label: '等于', value: 'eq' },
{ label: '不等于', value: 'ne' },
{ label: '非空', value: '$notNull' },
{ label: '为空', value: '$null' },
],
number: [
{ label: '等于', value: 'eq', selected: true },
{ label: '不等于', value: 'ne' },
{ label: '大于', value: 'gt' },
{ label: '大于等于', value: 'gte' },
{ label: '小于', value: 'lt' },
{ label: '小于等于', value: 'lte' },
// {label: '介于', value: 'between'},
{ label: '非空', value: '$notNull' },
{ label: '为空', value: '$null' },
],
file: [
{ label: '存在', value: 'id.gt' },
{ label: '不存在', value: 'id.$null' },
],
boolean: [
{ label: '是', value: '$isTruly', selected: true },
{ label: '否', value: '$isFalsy' },
],
select: [
{ label: '等于', value: 'eq', selected: true },
{ label: '不等于', value: 'ne' },
{ label: '包含', value: 'in' },
{ label: '不包含', value: 'notIn' },
{ label: '非空', value: '$notNull' },
{ label: '为空', value: '$null' },
],
multipleSelect: [
{ label: '等于', value: '$match', selected: true },
{ label: '不等于', value: '$notMatch' },
{ label: '包含', value: '$anyOf' },
{ label: '不包含', value: '$noneOf' },
{ label: '非空', value: '$notNull' },
{ label: '为空', value: '$null' },
],
datetime: [
{ label: '等于', value: '$dateOn', selected: true },
{ label: '不等于', value: '$dateNotOn' },
{ label: '早于', value: '$dateBefore' },
{ label: '晚于', value: '$dateAfter' },
{ label: '不早于', value: '$dateNotBefore' },
{ label: '不晚于', value: '$dateNotAfter' },
// {label: '介于', value: 'between'},
{ label: '非空', value: '$notNull' },
{ label: '为空', value: '$null' },
// {label: '是今天', value: 'now'},
// {label: '在今天之前', value: 'before_today'},
// {label: '在今天之后', value: 'after_today'},
],
time: [
{ label: '等于', value: 'eq', selected: true },
{ label: '不等于', value: 'neq' },
{ label: '大于', value: 'gt' },
{ label: '大于等于', value: 'gte' },
{ label: '小于', value: 'lt' },
{ label: '小于等于', value: 'lte' },
// {label: '介于', value: 'between'},
{ label: '非空', value: '$notNull' },
{ label: '为空', value: '$null' },
// {label: '是今天', value: 'now'},
// {label: '在今天之前', value: 'before_today'},
// {label: '在今天之后', value: 'after_today'},
],
// linkTo: [
// {label: '包含', value: 'cont'},
// {label: '不包含', value: 'ncont'},
// {label: '非空', value: '$notNull'},
// {label: '为空', value: '$null'},
// ],
};
const op = {
string: OP_MAP.string,
textarea: OP_MAP.string,
number: OP_MAP.number,
percent: OP_MAP.number,
datetime: OP_MAP.datetime,
date: OP_MAP.datetime,
time: OP_MAP.time,
checkbox: OP_MAP.boolean,
boolean: OP_MAP.boolean,
select: OP_MAP.select,
multipleSelect: OP_MAP.multipleSelect,
checkboxes: OP_MAP.multipleSelect,
radio: OP_MAP.select,
upload: OP_MAP.file,
attachment: OP_MAP.file,
};
const StringInput = props => {
const { value, onChange, ...restProps } = props;
return (
<Input
{...restProps}
defaultValue={value}
onChange={e => {
onChange(e.target.value);
}}
/>
);
};
const controls = {
string: StringInput,
textarea: StringInput,
number: InputNumber,
percent: props => (
<InputNumber
formatter={value => (value ? `${value}%` : '')}
parser={value => value.replace('%', '')}
{...props}
/>
),
boolean: BooleanControl,
checkbox: BooleanControl,
select: OptionControl,
radio: OptionControl,
checkboxes: OptionControl,
multipleSelect: OptionControl,
time: TimeControl,
date: DateControl,
};
function DateControl(props: any) {
const { field, value, onChange, ...restProps } = props;
let format = field.dateFormat;
// if (field.showTime) {
// format += ` ${field.timeFormat}`;
// }
const m = moment(value, format);
return (
<DatePicker
format={format}
value={m.isValid() ? m : null}
onChange={value => {
onChange(value ? value.format('YYYY-MM-DD') : null);
}}
/>
);
// return (
// <DatePicker format={format} showTime={field.showTime} value={m.isValid() ? m : null} onChange={(value) => {
// onChange(value ? value.format(field.showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD') : null)
// }}/>
// );
}
function TimeControl(props: any) {
const { field, value, onChange, ...restProps } = props;
let format = field.timeFormat;
const m = moment(value, format);
return (
<TimePicker
value={m.isValid() ? m : null}
format={field.timeFormat}
onChange={value => {
onChange(value ? value.format('HH:mm:ss') : null);
}}
/>
);
}
function OptionControl(props) {
const { multiple = true, op, options, value, onChange, ...restProps } = props;
let mode: any = 'multiple';
if (!multiple && ['eq', 'ne'].indexOf(op) !== -1) {
mode = undefined;
}
return (
<Select
style={{ minWidth: 120 }}
mode={mode}
value={value}
onChange={value => {
onChange(value);
}}
options={options}
></Select>
);
}
function BooleanControl(props) {
const { value, onChange, ...restProps } = props;
return (
<Radio.Group
value={value}
onChange={e => {
onChange(e.target.value);
}}
>
<Radio value={true}></Radio>
<Radio value={false}></Radio>
</Radio.Group>
);
}
function NullControl(props) {
return null;
}
function getComponentTypeByField(field) {
if (!field.component) {
return 'string';
}
let componentType = field.component.type;
if (field.component.type === 'select' && field.multiple) {
componentType = 'multipleSelect';
}
return componentType;
}
export function FilterItem(props: FilterItemProps) {
const {
index,
fields = [],
sourceFields = [],
showDeleteButton = true,
onDelete,
onChange,
} = props;
const defaultField: any =
fields.find(field => field.name === props.dataSource.column) || {};
const componentType = getComponentTypeByField(defaultField);
// const [type, setType] = useState(defaultField.interface || 'string');
const [field, setField] = useState<any>({});
const [dataSource, setDataSource] = useState(props.dataSource || {});
const [valueType, setValueType] = useState('custom');
useEffect(() => {
const field = fields.find(field => field.name === props.dataSource.column);
if (field) {
setField(field);
// let componentType = getComponentTypeByField(field);
// setType(componentType);
}
setDataSource({ ...props.dataSource });
if (/^{{.+}}$/.test(props.dataSource.value)) {
setValueType('ref');
}
}, [props.dataSource]);
let ValueControl = controls[componentType] || controls.string;
if (
['$null', '$notNull', '$isTruly', '$isFalsy'].indexOf(dataSource.op) !== -1
) {
ValueControl = NullControl;
}
if (['boolean', 'checkbox'].indexOf(componentType) !== -1) {
ValueControl = NullControl;
}
// let multiple = true;
// if ()
const opOptions = op[componentType || 'string'] || op.string;
console.log({ componentType, defaultField, field, valueType, opOptions });
return (
<Space>
<Select
value={dataSource.column}
onChange={value => {
const field = fields.find(field => field.name === value);
let componentType = getComponentTypeByField(field);
// setType(componentType);
setValueType('custom');
onChange({
...dataSource,
column: value,
op: get(op, [componentType, 0, 'value']),
value: undefined,
});
}}
style={{ width: 120 }}
placeholder={'选择字段'}
>
{fields.map(field => (
<Select.Option value={field.name}>{field.title}</Select.Option>
))}
</Select>
<Select
value={dataSource.column ? dataSource.op : null}
style={{ minWidth: 100 }}
onChange={value => {
onChange({ ...dataSource, op: value });
}}
// value={get(opOptions, [0, 'value'])}
options={opOptions}
>
{/* {(op[type]||op.string).map(option => (
<Select.Option value={option.value}>{option.label}</Select.Option>
))} */}
</Select>
{sourceFields.length > 0 && (
<Select
style={{ minWidth: 100 }}
onChange={value => {
setDataSource({ ...dataSource, value: undefined });
onChange({ ...dataSource, value: undefined });
setValueType(value);
}}
defaultValue={valueType}
>
<Select.Option value={'custom'}></Select.Option>
<Select.Option value={'ref'}></Select.Option>
</Select>
)}
{valueType !== 'ref' ? (
<ValueControl
field={defaultField}
multiple={componentType === 'checkboxes' || !!defaultField.multiple}
op={dataSource.op}
options={defaultField.dataSource}
value={dataSource.value}
onChange={value => {
onChange({ ...dataSource, value: value });
}}
style={{ width: 180 }}
/>
) : sourceFields.length > 0 ? (
<Select
value={dataSource.value}
onChange={value => {
onChange({ ...dataSource, value: value });
}}
style={{ width: 120 }}
placeholder={'选择字段'}
>
{sourceFields.map(field => (
<Select.Option value={`{{ ${field.name} }}`}>
{field.title}
</Select.Option>
))}
</Select>
) : null}
{showDeleteButton && (
<Button
className={'filter-remove-link filter-item'}
type={'link'}
style={{ padding: 0 }}
onClick={e => {
onDelete && onDelete(e);
}}
>
<CloseCircleOutlined />
</Button>
)}
</Space>
);
}
function toFilter(values: any) {
let filter: any;
let { type, andor = 'and', list = [], column, op, value } = values;
if (type === 'group') {
filter = {
[andor]: list.map(value => toFilter(value)).filter(Boolean),
};
} else if (type === 'item' && column && op) {
if (
[
'id.$null',
'id.$notNull',
'$null',
'$notNull',
'$isTruly',
'$isFalsy',
].indexOf(op) !== -1
) {
value = true;
}
// if (op === 'id.gt') {
// value = 0;
// }
filter = {
[`${column}`]: { [op]: value },
};
}
return filter;
}
function toValues(filter: any = {}) {
let values: any = {};
Object.keys(filter).forEach(key => {
const value = filter[key];
if (Array.isArray(value)) {
values['andor'] = key;
values['type'] = 'group';
values['list'] = value.map(v => toValues(v));
} else if (typeof value === 'object') {
values['type'] = 'item';
values['column'] = key;
values['op'] = Object.keys(value).shift();
values['value'] = Object.values(value).shift();
}
});
return values;
}
export const Filter = connect({
getProps: mapStyledProps,
})(props => {
const dataSource = {
type: 'group',
list: [
{
type: 'item',
},
],
};
const {
value,
onChange,
associatedKey,
filter = {},
sourceName,
sourceFilter = {},
fields = [],
...restProps
} = props;
console.log('filter', { associatedKey });
const { data = [], loading = true } = useRequest(
() => {
return associatedKey
? api.resource(`collections.fields`).list({
associatedKey,
filter,
perPage: -1,
})
: Promise.resolve({
data: fields,
});
},
{
refreshDeps: [associatedKey],
},
);
const { data: sourceFields = [] } = useRequest(
() => {
return sourceName
? api.resource(`collections.fields`).list({
associatedKey: sourceName,
filter: sourceFilter,
perPage: -1,
})
: Promise.resolve({
data: [],
});
},
{
refreshDeps: [sourceName],
},
);
console.log({ sourceName, sourceFields });
return (
<FilterGroup
showDeleteButton={false}
dataSource={value ? toValues(value) : dataSource}
onChange={values => {
console.log(values);
onChange(toFilter(values));
}}
{...restProps}
sourceFields={sourceFields}
fields={data.filter(item => item.filterable)}
/>
);
});
export default Filter;

View File

@ -1,3 +0,0 @@
.filter-remove-link {
color: #d9d9d9;
}

View File

@ -1,163 +0,0 @@
import { useCallback, useRef, useState } from 'react';
export default <T>(initialValue: T[]) => {
const counterRef = useRef(-1);
// key 存储器
const keyList = useRef<number[]>([]);
// 内部方法
const setKey = useCallback((index: number) => {
counterRef.current += 1;
keyList.current.splice(index, 0, counterRef.current);
}, []);
const [list, setList] = useState(() => {
(initialValue || []).forEach((_, index) => {
setKey(index);
});
return initialValue || [];
});
const resetList = (newList: T[] = []) => {
keyList.current = [];
counterRef.current = -1;
setList(() => {
(newList || []).forEach((_, index) => {
setKey(index);
});
return newList || [];
});
};
const insert = (index: number, obj: T) => {
setList(l => {
const temp = [...l];
temp.splice(index, 0, obj);
setKey(index);
return temp;
});
};
const getAll = () => list;
const getKey = (index: number) => keyList.current[index];
const getIndex = (index: number) =>
keyList.current.findIndex(ele => ele === index);
const merge = (index: number, obj: T[]) => {
setList(l => {
const temp = [...l];
obj.forEach((_, i) => {
setKey(index + i);
});
temp.splice(index, 0, ...obj);
return temp;
});
};
const replace = (index: number, obj: T) => {
setList(l => {
const temp = [...l];
temp[index] = obj;
return temp;
});
};
const remove = (index: number) => {
setList(l => {
const temp = [...l];
temp.splice(index, 1);
// remove keys if necessary
try {
keyList.current.splice(index, 1);
} catch (e) {
console.error(e);
}
return temp;
});
};
const move = (oldIndex: number, newIndex: number) => {
if (oldIndex === newIndex) {
return;
}
setList(l => {
const newList = [...l];
const temp = newList.filter((_: {}, index: number) => index !== oldIndex);
temp.splice(newIndex, 0, newList[oldIndex]);
// move keys if necessary
try {
const keyTemp = keyList.current.filter(
(_: {}, index: number) => index !== oldIndex,
);
keyTemp.splice(newIndex, 0, keyList.current[oldIndex]);
keyList.current = keyTemp;
} catch (e) {
console.error(e);
}
return temp;
});
};
const push = (obj: T) => {
setList(l => {
setKey(l.length);
return l.concat([obj]);
});
};
const pop = () => {
// remove keys if necessary
try {
keyList.current = keyList.current.slice(0, keyList.current.length - 1);
} catch (e) {
console.error(e);
}
setList(l => l.slice(0, l.length - 1));
};
const unshift = (obj: T) => {
setList(l => {
setKey(0);
return [obj].concat(l);
});
};
const sortForm = (result: unknown[]) =>
result
.map((item, index) => ({ key: index, item })) // add index into obj
.sort((a, b) => getIndex(a.key) - getIndex(b.key)) // sort based on the index of table
.filter(item => !!item.item) // remove undefined(s)
.map(item => item.item); // retrive the data
const shift = () => {
// remove keys if necessary
try {
keyList.current = keyList.current.slice(1, keyList.current.length);
} catch (e) {
console.error(e);
}
setList(l => l.slice(1, l.length));
};
return {
list,
insert,
merge,
replace,
remove,
getAll,
getKey,
getIndex,
move,
push,
pop,
unshift,
shift,
sortForm,
resetList,
};
};

View File

@ -1,24 +0,0 @@
import React from 'react';
import { createVirtualBox } from '@formily/react-schema-renderer';
import { Card } from 'antd';
import { CardProps } from 'antd/lib/card';
import styled from 'styled-components';
export const FormBlock = createVirtualBox<CardProps>(
'block',
styled(({ children, className, ...props }) => {
return (
<Card className={className} size="small" {...props}>
{children}
</Card>
);
})`
margin-bottom: 10px !important;
&.ant-card {
border: none;
box-shadow: none;
}
`,
);
export default FormBlock;

View File

@ -1 +0,0 @@
import 'antd/lib/card/style/index';

View File

@ -1,20 +0,0 @@
import React from 'react';
import { createVirtualBox } from '@formily/react-schema-renderer';
import { Card } from 'antd';
import { CardProps } from 'antd/lib/card';
import styled from 'styled-components';
export const FormCard = createVirtualBox<CardProps>(
'card',
styled(({ children, className, ...props }) => {
return (
<Card className={className} size="small" {...props}>
{children}
</Card>
);
})`
margin-bottom: 10px !important;
`,
);
export default FormCard;

View File

@ -1 +0,0 @@
import 'antd/lib/card/style/index';

View File

@ -1,50 +0,0 @@
import React from 'react';
import { createVirtualBox } from '@formily/react-schema-renderer';
import { Card } from 'antd';
import styled from 'styled-components';
import { markdown } from '@/components/views/Field';
export const FormDescription = createVirtualBox(
'description',
styled(({ schema = {}, children, className, ...props }) => {
const { title, tooltip } = schema as any;
console.log({ schema });
return (
<Card
title={title}
size={'small'}
headStyle={{ padding: 0 }}
bodyStyle={{
padding: 0,
}}
className={className}
{...props}
>
{typeof tooltip === 'string' && tooltip && (
<div dangerouslySetInnerHTML={{ __html: markdown(tooltip) }}></div>
)}
</Card>
);
})`
margin-bottom: 24px !important;
&.ant-card {
border: none;
box-shadow: none;
.ant-card-head {
font-size: 16px;
background: #fafafa;
margin: 0 -24px;
padding: 4px 24px !important;
border-top: 1px solid #f0f0f0;
}
p:first-child {
margin-top: 14px;
}
p:last-child {
margin-bottom: 0;
}
}
`,
);
export default FormDescription;

View File

@ -1,10 +0,0 @@
import React from 'react';
import { createVirtualBox } from '@formily/react-schema-renderer';
import { Col } from 'antd';
import { ColProps } from 'antd/lib/grid';
export const FormGridCol = createVirtualBox<ColProps>('grid-col', props => {
return <Col {...props}>{props.children}</Col>;
});
export default FormGridCol;

View File

@ -1 +0,0 @@
import 'antd/lib/col/style/index';

View File

@ -1,25 +0,0 @@
import React from 'react';
import { AntdSchemaFieldAdaptor, pickFormItemProps } from '@formily/antd';
import { createVirtualBox } from '@formily/react-schema-renderer';
import { Row } from 'antd';
import { RowProps } from 'antd/lib/grid';
import { FormItemProps as ItemProps } from 'antd/lib/form';
import { IItemProps } from '../types';
export const FormGridRow = createVirtualBox<RowProps & ItemProps & IItemProps>(
'grid-row',
props => {
const { title, label } = props;
const grids = <Row {...props}>{props.children}</Row>;
if (title || label) {
return (
<AntdSchemaFieldAdaptor {...pickFormItemProps(props)}>
{grids}
</AntdSchemaFieldAdaptor>
);
}
return grids;
},
);
export default FormGridRow;

View File

@ -1 +0,0 @@
import 'antd/lib/row/style/index';

View File

@ -1,68 +0,0 @@
import React, { Fragment } from 'react';
import {
AntdSchemaFieldAdaptor,
pickFormItemProps,
pickNotFormItemProps,
} from '@formily/antd';
import { createVirtualBox } from '@formily/react-schema-renderer';
import { toArr } from '@formily/shared';
import { Row, Col } from 'antd';
import { FormItemProps as ItemProps } from 'antd/lib/form';
import { IFormItemGridProps, IItemProps } from '../types';
import { normalizeCol } from '../shared';
export const FormItemGrid = createVirtualBox<
React.PropsWithChildren<IFormItemGridProps & ItemProps & IItemProps>
>('grid', props => {
const {
cols: rawCols,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
title,
label,
} = props;
const formItemProps = pickFormItemProps(props);
const gridProps = pickNotFormItemProps(props);
const children = toArr(props.children);
const cols = toArr(rawCols).map(col => normalizeCol(col));
const childNum = children.length;
if (cols.length < childNum) {
let offset: number = childNum - cols.length;
let lastSpan: number =
24 -
cols.reduce((buf, col) => {
return (
buf +
Number(col.span ? col.span : 0) +
Number(col.offset ? col.offset : 0)
);
}, 0);
for (let i = 0; i < offset; i++) {
cols.push({ span: Math.floor(lastSpan / offset) });
}
}
const grids = (
<Row {...gridProps}>
{children.reduce((buf, child, key) => {
return child
? buf.concat(
<Col key={key} {...cols[key]}>
{child}
</Col>,
)
: buf;
}, [])}
</Row>
);
if (title || label) {
return (
<AntdSchemaFieldAdaptor {...formItemProps}>
{grids}
</AntdSchemaFieldAdaptor>
);
}
return <Fragment>{grids}</Fragment>;
});
export default FormItemGrid;

View File

@ -1,2 +0,0 @@
import 'antd/lib/row/style/index';
import 'antd/lib/col/style/index';

View File

@ -1,29 +0,0 @@
import React from 'react';
import { FormItemDeepProvider, useDeepFormItem } from '@formily/antd';
import { createVirtualBox } from '@formily/react-schema-renderer';
import cls from 'classnames';
import { IFormItemTopProps } from '../types';
export const FormLayout = createVirtualBox<IFormItemTopProps>(
'layout',
props => {
const { inline } = useDeepFormItem();
const isInline = props.inline || inline;
const children =
isInline || props.className || props.style ? (
<div
className={cls(props.className, {
'ant-form ant-form-inline': isInline,
})}
style={props.style}
>
{props.children}
</div>
) : (
props.children
);
return <FormItemDeepProvider {...props}>{children}</FormItemDeepProvider>;
},
);
export default FormLayout;

View File

@ -1,3 +0,0 @@
import { MegaLayout, FormMegaLayout } from '@formily/antd';
export { MegaLayout, FormMegaLayout };

View File

@ -1,5 +0,0 @@
import { FormSlot } from '@formily/react-schema-renderer';
export { FormSlot };
export default FormSlot;

View File

@ -1,157 +0,0 @@
import React, { useRef, Fragment, useEffect } from 'react';
import {
createControllerBox,
ISchemaVirtualFieldComponentProps,
createEffectHook,
useFormEffects,
useFieldState,
IVirtualBoxProps,
} from '@formily/react-schema-renderer';
import { toArr } from '@formily/shared';
import { Steps } from 'antd';
import { createMatchUpdate } from '../shared';
import { IFormStep } from '../types';
enum StateMap {
ON_FORM_STEP_NEXT = 'onFormStepNext',
ON_FORM_STEP_PREVIOUS = 'onFormStepPrevious',
ON_FORM_STEP_GO_TO = 'onFormStepGoto',
ON_FORM_STEP_CURRENT_CHANGE = 'onFormStepCurrentChange',
ON_FORM_STEP_DATA_SOURCE_CHANGED = 'onFormStepDataSourceChanged',
}
const EffectHooks = {
onStepNext$: createEffectHook<void>(StateMap.ON_FORM_STEP_NEXT),
onStepPrevious$: createEffectHook<void>(StateMap.ON_FORM_STEP_PREVIOUS),
onStepGoto$: createEffectHook<void>(StateMap.ON_FORM_STEP_GO_TO),
onStepCurrentChange$: createEffectHook<{
value: number;
preValue: number;
}>(StateMap.ON_FORM_STEP_CURRENT_CHANGE),
};
type ExtendsProps = StateMap & typeof EffectHooks;
export const FormStep: React.FC<IVirtualBoxProps<IFormStep>> &
ExtendsProps = createControllerBox<IFormStep>(
'step',
({
form,
schema,
path,
name,
children,
}: ISchemaVirtualFieldComponentProps) => {
const { dataSource, ...stepProps } = schema.getExtendsComponentProps();
const [{ current }, setFieldState] = useFieldState({
current: stepProps.current || 0,
});
const ref = useRef(current);
const itemsRef = useRef([]);
itemsRef.current = toArr(dataSource);
const matchUpdate = createMatchUpdate(name, path);
const update = (cur: number) => {
form.notify(StateMap.ON_FORM_STEP_CURRENT_CHANGE, {
path,
name,
value: cur,
preValue: current,
});
setFieldState({
current: cur,
});
};
useEffect(() => {
form.notify(StateMap.ON_FORM_STEP_DATA_SOURCE_CHANGED, {
path,
name,
value: itemsRef.current,
});
}, [itemsRef.current.length]);
useFormEffects(($, { setFieldState }) => {
const updateFields = () => {
itemsRef.current.forEach(({ name }, index) => {
setFieldState(name, (state: any) => {
state.display = index === current;
});
});
};
updateFields();
$(StateMap.ON_FORM_STEP_DATA_SOURCE_CHANGED).subscribe(
({ name, path }) => {
matchUpdate(name, path, () => {
updateFields();
});
},
);
$(StateMap.ON_FORM_STEP_CURRENT_CHANGE).subscribe(
({ value, name, path }: any = {}) => {
matchUpdate(name, path, () => {
form.hostUpdate(() => {
itemsRef.current.forEach(({ name }, index) => {
if (!name)
throw new Error(
'FormStep dataSource must include `name` property',
);
setFieldState(name, (state: any) => {
state.display = index === value;
});
});
});
});
},
);
$(StateMap.ON_FORM_STEP_NEXT).subscribe(({ name, path }: any = {}) => {
matchUpdate(name, path, () => {
form.validate().then(({ errors }) => {
if (errors.length === 0) {
update(
ref.current + 1 > itemsRef.current.length - 1
? ref.current
: ref.current + 1,
);
}
});
});
});
$(StateMap.ON_FORM_STEP_PREVIOUS).subscribe(
({ name, path }: any = {}) => {
matchUpdate(name, path, () => {
update(ref.current - 1 < 0 ? ref.current : ref.current - 1);
});
},
);
$(StateMap.ON_FORM_STEP_GO_TO).subscribe(
({ name, path, value }: any = {}) => {
matchUpdate(name, path, () => {
if (!(value < 0 || value > itemsRef.current.length)) {
update(value);
}
});
},
);
});
ref.current = current;
return (
<Fragment>
<Steps {...stepProps} current={current}>
{itemsRef.current.map((props, key) => {
return <Steps.Step {...props} key={key} />;
})}
</Steps>{' '}
{children}
</Fragment>
);
},
) as any;
Object.assign(FormStep, StateMap, EffectHooks);
export default FormStep;

View File

@ -1 +0,0 @@
import 'antd/lib/steps/style/index';

View File

@ -1,194 +0,0 @@
import React, { Fragment, useEffect, useRef } from 'react';
import {
createControllerBox,
ISchemaVirtualFieldComponentProps,
createEffectHook,
useFormEffects,
useFieldState,
FormEffectHooks,
SchemaField,
FormPath,
IVirtualBoxProps,
} from '@formily/react-schema-renderer';
import { Tabs, Badge } from 'antd';
import { TabPaneProps } from 'antd/lib/tabs';
import { IFormTab } from '../types';
import { createMatchUpdate } from '../shared';
enum StateMap {
ON_FORM_TAB_ACTIVE_KEY_CHANGE = 'onFormTabActiveKeyChange',
}
const { onFormChange$ } = FormEffectHooks;
const EffectHooks = {
onTabActiveKeyChange$: createEffectHook<{
name?: string;
path?: string;
value?: any;
}>(StateMap.ON_FORM_TAB_ACTIVE_KEY_CHANGE),
};
const parseTabItems = (items: any, hiddenKeys?: string[]) => {
return items.reduce((buf: any, { schema, key }) => {
if (Array.isArray(hiddenKeys)) {
if (hiddenKeys.includes(key)) {
return buf;
}
}
if (schema.getExtendsComponent() === 'tabpane') {
return buf.concat({
props: schema.getExtendsComponentProps(),
schema,
key,
});
}
return buf;
}, []);
};
const parseDefaultActiveKey = (
hiddenKeys: Array<string> = [],
items: any,
defaultActiveKey,
) => {
if (!hiddenKeys.includes(defaultActiveKey)) return defaultActiveKey;
const index = items.findIndex(item => !hiddenKeys.includes(item.key));
return index >= 0 ? items[index].key : '';
};
const parseChildrenErrors = (errors: any, target: string) => {
return errors.filter(({ path }) => {
return FormPath.parse(path).includes(target);
});
};
const addErrorBadge = (
tab: React.ReactNode,
currentPath: FormPath,
childrenErrors: any[],
) => {
const currentErrors = childrenErrors.filter(({ path }) => {
return FormPath.parse(path).includes(currentPath);
});
if (currentErrors.length > 0) {
return (
<Badge offset={[12, 0]} count={currentErrors.length}>
{tab}
</Badge>
);
}
return tab;
};
type ExtendsProps = StateMap &
typeof EffectHooks & {
TabPane: React.FC<IVirtualBoxProps<TabPaneProps>>;
};
type ExtendsState = {
activeKey?: string;
childrenErrors?: any;
};
export const FormTab: React.FC<IVirtualBoxProps<IFormTab>> &
ExtendsProps = createControllerBox<IFormTab>(
'tab',
({ form, schema, name, path }: ISchemaVirtualFieldComponentProps) => {
const orderProperties = schema.getOrderProperties();
let {
hiddenKeys,
defaultActiveKey,
...componentProps
} = schema.getExtendsComponentProps();
hiddenKeys = hiddenKeys || [];
const [{ activeKey, childrenErrors }, setFieldState] = useFieldState<
ExtendsState
>({
activeKey: parseDefaultActiveKey(
hiddenKeys,
orderProperties,
defaultActiveKey,
),
childrenErrors: [],
});
const itemsRef = useRef([]);
itemsRef.current = parseTabItems(orderProperties, hiddenKeys);
const update = (cur: string) => {
form.notify(StateMap.ON_FORM_TAB_ACTIVE_KEY_CHANGE, {
name,
path,
value: cur,
});
};
const matchUpdate = createMatchUpdate(name, path);
useEffect(() => {
if (Array.isArray(hiddenKeys)) {
setFieldState({
activeKey: parseDefaultActiveKey(
hiddenKeys,
orderProperties,
defaultActiveKey,
),
});
}
}, [hiddenKeys.length]);
useFormEffects(({ hasChanged }) => {
onFormChange$().subscribe(formState => {
const errorsChanged = hasChanged(formState, 'errors');
if (errorsChanged) {
setFieldState({
childrenErrors: parseChildrenErrors(formState.errors, path),
});
}
});
EffectHooks.onTabActiveKeyChange$().subscribe(
({ value, name, path }: any = {}) => {
if (!itemsRef.current.map(item => item.key).includes(value)) return;
matchUpdate(name, path, () => {
setFieldState({
activeKey: value,
});
});
},
);
});
return (
<Tabs {...componentProps} activeKey={activeKey} onChange={update}>
{itemsRef.current.map(({ props, schema, key }) => {
const currentPath = FormPath.parse(path).concat(key);
return (
<Tabs.TabPane
{...props}
tab={
activeKey === key
? props.tab
: addErrorBadge(props.tab, currentPath, childrenErrors)
}
key={key}
forceRender
>
<SchemaField
path={currentPath}
schema={schema}
onlyRenderProperties
/>
</Tabs.TabPane>
);
})}
</Tabs>
);
},
) as any;
FormTab.TabPane = createControllerBox<TabPaneProps>(
'tabpane',
({ children }) => {
return <Fragment>{children}</Fragment>;
},
);
Object.assign(FormTab, StateMap, EffectHooks);
export default FormTab;

View File

@ -1,2 +0,0 @@
import 'antd/lib/tabs/style/index';
import 'antd/lib/badge/style/index';

View File

@ -1,128 +0,0 @@
import React, { useRef, useLayoutEffect } from 'react';
import { createControllerBox, Schema } from '@formily/react-schema-renderer';
import { IFormTextBox } from '../types';
import { toArr } from '@formily/shared';
import { FormItemProps as ItemProps } from 'antd/lib/form';
import { version } from 'antd';
import { AntdSchemaFieldAdaptor, pickFormItemProps } from '@formily/antd';
import styled from 'styled-components';
const isV4 = /^4\./.test(version);
export const FormTextBox = createControllerBox<IFormTextBox & ItemProps>(
'text-box',
styled(({ props, form, className, children }) => {
const schema = new Schema(props);
const mergeProps = schema.getExtendsComponentProps();
const { title, label, text, gutter, style } = Object.assign(
{
gutter: 5,
},
mergeProps,
);
const formItemProps = pickFormItemProps(mergeProps);
const ref: React.RefObject<HTMLDivElement> = useRef();
const arrChildren = toArr(children);
const split = text.split('%s');
let index = 0;
useLayoutEffect(() => {
if (ref.current) {
const elements = ref.current.querySelectorAll('.text-box-field');
const syncLayouts = Array.prototype.map.call(
elements,
(el: HTMLElement) => {
return [
el,
() => {
const ctrl = el.querySelector('.ant-form-item-children');
setTimeout(() => {
if (ctrl) {
const editable = form.getFormState(state => state.editable);
el.style.width = editable
? ctrl.getBoundingClientRect().width + 'px'
: 'auto';
}
});
},
];
},
);
syncLayouts.forEach(([el, handler]) => {
handler();
el.addEventListener('DOMSubtreeModified', handler);
});
return () => {
syncLayouts.forEach(([el, handler]) => {
el.removeEventListener('DOMSubtreeModified', handler);
});
};
}
}, []);
const newChildren = split.reduce((buf, item, key) => {
return buf.concat(
item ? (
<p
key={index++}
className="text-box-words"
style={{
marginRight: gutter / 2,
marginLeft: gutter / 2,
...style,
}}
>
{item}
</p>
) : null,
arrChildren[key] ? (
<div key={index++} className="text-box-field">
{arrChildren[key]}
</div>
) : null,
);
}, []);
const textChildren = (
<div
className={`${className} ${mergeProps.className}`}
style={{
marginRight: -gutter / 2,
marginLeft: -gutter / 2,
}}
ref={ref}
>
{newChildren}
</div>
);
if (!title && !label) return textChildren;
return (
<AntdSchemaFieldAdaptor {...formItemProps}>
{textChildren}
</AntdSchemaFieldAdaptor>
);
})`
display: flex;
.text-box-words:nth-child(1) {
margin-left: 0;
}
.text-box-words {
margin-bottom: 0 !important;
${isV4 ? 'line-height:32px' : ''}
}
.text-box-field {
display: inline-block;
.ant-form-item {
margin-bottom: 0 !important;
}
}
.next-form-item {
margin-bottom: 0 !important;
}
.preview-text {
text-align: center !important;
}
`,
);
export default FormTextBox;

View File

@ -1,55 +0,0 @@
import React, { useState } from 'react';
import { connect } from '@formily/react-schema-renderer';
import { Popover, Button, Input } from 'antd';
import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared';
import { icons, hasIcon, Icon as IconComponent } from '@/components/icons';
import { CloseOutlined } from '@ant-design/icons';
function IconField(props: any) {
const { value, onChange } = props;
const [visible, setVisible] = useState(false);
return (
<div>
<Input.Group compact>
<Popover
placement={'bottom'}
visible={visible}
onVisibleChange={val => {
setVisible(val);
}}
content={
<div>
{[...icons.keys()].map(key => (
<span
style={{ fontSize: 18, marginRight: 10, cursor: 'pointer' }}
onClick={() => {
onChange(key);
setVisible(false);
}}
>
<IconComponent type={key} />
</span>
))}
</div>
}
title="图标"
trigger="click"
>
<Button>
{hasIcon(value) ? <IconComponent type={value} /> : '选择图标'}
</Button>
</Popover>
{value && <Button icon={<CloseOutlined/>} onClick={e => {
onChange(null);
}}></Button>}
</Input.Group>
</div>
);
}
export const Icon = connect({
getProps: mapStyledProps,
getComponent: mapTextComponent,
})(IconField);
export default Icon;

View File

@ -1,30 +0,0 @@
export * from './text-button';
export * from './time-picker';
export * from './transfer';
export * from './switch';
export * from './array-cards';
export * from './array-table';
export * from './checkbox';
export * from './circle-button';
export * from './date-picker';
export * from './form-block';
export * from './form-card';
export * from './form-tab';
export * from './form-grid-col';
export * from './form-grid-row';
export * from './form-item-grid';
export * from './form-layout';
export * from './form-mega-layout';
export * from './form-description';
export * from './form-step';
export * from './form-text-box';
export * from './form-slot';
export * from './input';
export * from './select';
export * from './number-picker';
export * from './password';
export * from './radio';
export * from './range';
export * from './rating';
export * from './upload';
export * from './registry';

View File

@ -1,31 +0,0 @@
import { connect } from '@formily/react-schema-renderer';
import React from 'react';
import { Input as AntdInput } from 'antd';
import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared';
export const Input = connect<'TextArea'>({
getProps: mapStyledProps,
getComponent: mapTextComponent,
})(
acceptEnum(({ onChange, ...restProps }) => (
<AntdInput
autoComplete={'off'}
{...restProps}
onChange={e => {
// 文本字段,如果空要 null 处理
onChange(e.target.value ? e : null);
}}
/>
)),
);
Input.TextArea = connect({
getProps: mapStyledProps,
getComponent: mapTextComponent,
})(
acceptEnum(props => (
<AntdInput.TextArea autoSize={{ minRows: 2, maxRows: 12 }} {...props} />
)),
);
export default Input;

View File

@ -1 +0,0 @@
import 'antd/lib/input/style/index';

View File

@ -1,15 +0,0 @@
import { connect } from '@formily/react-schema-renderer';
import React from 'react';
import { Input as AntdInput } from 'antd';
import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared';
export const Markdown = connect({
getProps: mapStyledProps,
getComponent: mapTextComponent,
})(
acceptEnum(props => (
<AntdInput.TextArea autoSize={{ minRows: 2, maxRows: 12 }} {...props} />
)),
);
export default Markdown;

View File

@ -1,24 +0,0 @@
import React from 'react';
import { connect } from '@formily/react-schema-renderer';
import { InputNumber } from 'antd';
import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared';
export const NumberPicker = connect({
getProps: mapStyledProps,
getComponent: mapTextComponent,
})(acceptEnum(InputNumber));
export const Percent = connect({
getProps: mapStyledProps,
getComponent: mapTextComponent,
})(
acceptEnum(props => (
<InputNumber
formatter={value => (value ? `${value}%` : '')}
parser={value => value.replace('%', '')}
{...props}
/>
)),
);
export default NumberPicker;

View File

@ -1 +0,0 @@
import 'antd/lib/input-number/style/index';

View File

@ -1,88 +0,0 @@
import React, { useState } from 'react';
import { connect } from '@formily/react-schema-renderer';
import { Input } from 'antd';
import { PasswordProps } from 'antd/lib/input';
import { PasswordStrength } from '@formily/react-shared-components';
import styled from 'styled-components';
import { mapStyledProps } from '../shared';
export interface IPasswordProps extends PasswordProps {
checkStrength: boolean;
}
export const Password = connect({
getProps: mapStyledProps,
})(styled((props: IPasswordProps) => {
const { value, className, checkStrength, onChange, ...others } = props;
return (
<span className={className}>
<Input.Password
autoComplete={'new-password'}
{...others}
value={value}
onChange={e => {
// 密码字段,如果没有设置不处理
onChange(e.target.value ? e : undefined);
}}
/>
{checkStrength && (
<PasswordStrength value={String(value)}>
{score => {
return (
<div className="password-strength-wrapper">
<div className="div-1 div" />
<div className="div-2 div" />
<div className="div-3 div" />
<div className="div-4 div" />
<div
className="password-strength-bar"
style={{
clipPath: `polygon(0 0,${score}% 0,${score}% 100%,0 100%)`,
}}
/>
</div>
);
}}
</PasswordStrength>
)}
</span>
);
})`
.password-strength-wrapper {
background: #e0e0e0;
margin-bottom: 3px;
position: relative;
.div {
position: absolute;
z-index: 1;
height: 8px;
top: 0;
background: #fff;
width: 1px;
transform: translate(-50%, 0);
}
.div-1 {
left: 20%;
}
.div-2 {
left: 40%;
}
.div-3 {
left: 60%;
}
.div-4 {
left: 80%;
}
.password-strength-bar {
position: relative;
background-image: -webkit-linear-gradient(left, #ff5500, #ff9300);
transition: all 0.35s ease-in-out;
height: 8px;
width: 100%;
margin-top: 5px;
}
}
`);
export default Password;

View File

@ -1 +0,0 @@
import 'antd/lib/input/style/index';

View File

@ -1,376 +0,0 @@
import { connect } from '@formily/react-schema-renderer';
import React, { useEffect, useState } from 'react';
import { Input as AntdInput, Table, Checkbox, Select, Tag } from 'antd';
import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared';
import api from '@/api-client';
import { useRequest } from 'umi';
import { useDynamicList } from 'ahooks';
import findIndex from 'lodash/findIndex';
import get from 'lodash/get';
import set from 'lodash/set';
import { DrawerSelectComponent } from '../drawer-select';
export const Permissions = {} as { Actions: any; Fields: any; Tabs: any };
Permissions.Actions = connect({
getProps: mapStyledProps,
getComponent: mapTextComponent,
})(({ onChange, value = [], resourceKey, ...restProps }) => {
const { data = [], loading = true } = useRequest(
() => {
return api.resource('collections.actions').list({
associatedKey: resourceKey,
perPage: -1,
});
},
{
refreshDeps: [resourceKey],
},
);
return (
<Table
size={'small'}
pagination={false}
dataSource={data}
columns={[
{
title: '操作',
dataIndex: ['title'],
},
{
title: '类型',
dataIndex: ['type'],
render: type => {
return type === 'create' ? (
<Tag color={'green'}></Tag>
) : (
<Tag color={'blue'}></Tag>
);
},
},
{
title: '允许操作',
dataIndex: ['name'],
render: (val, record) => {
const values = [...(value || [])];
const index = findIndex(
values,
(item: any) =>
item && item.name === `${resourceKey}:${record.name}`,
);
console.log(values);
return (
<Checkbox
defaultChecked={index >= 0}
onChange={e => {
// const index = findIndex(values, (item: any) => item && item.name === `${resourceKey}:${record.name}`);
if (index >= 0) {
if (!e.target.checked) {
values.splice(index, 1);
}
} else {
values.push({
name: `${resourceKey}:${record.name}`,
});
}
onChange(values);
}}
/>
);
},
},
{
title: '可操作的数据范围',
dataIndex: ['scope'],
render: (scope, record) => {
if (['filter', 'create'].indexOf(record.type) !== -1) {
return null;
}
const values = [...(value || [])];
const index = findIndex(
values,
(item: any) =>
item && item.name === `${resourceKey}:${record.name}`,
);
console.log(
values,
index,
`${resourceKey}:${record.name}`,
get(values, [index, 'scope']),
);
return (
<DrawerSelectComponent
schema={{
title: '选择可操作的数据范围',
}}
size={'small'}
associatedKey={resourceKey}
viewName={'collections.scopes.table'}
target={'scopes'}
multiple={false}
labelField={'title'}
valueField={'id'}
value={get(values, [index, 'scope'])}
onChange={data => {
const values = [...(value || [])];
const index = findIndex(
values,
(item: any) =>
item && item.name === `${resourceKey}:${record.name}`,
);
if (index === -1) {
values.push({
name: `${resourceKey}:${record.name}`,
scope_id: data.id,
});
} else {
set(values, [index, 'scope_id'], data.id);
}
console.log('valvalvalvalval', { values });
onChange(values);
console.log('valvalvalvalval', data);
}}
/>
// <Scope
// resourceTarget={'scopes'}
// associatedName={'collections'}
// associatedKey={resourceKey}
// target={'scopes'}
// multiple={false}
// labelField={'title'}
// valueField={'id'}
// value={get(values, [index, 'scope'])}
// onChange={(data) => {
// const values = [...value||[]];
// const index = findIndex(values, (item: any) => item && item.name === `${resourceKey}:${record.name}`);
// if (index === -1) {
// values.push({
// name: `${resourceKey}:${record.name}`,
// scope_id: data,
// });
// } else {
// set(values, [index, 'scope_id'], data);
// }
// console.log('valvalvalvalval', {values})
// onChange(values);
// console.log('valvalvalvalval', data);
// }}
// />
);
},
},
]}
loading={loading}
/>
);
});
Permissions.Fields = connect<'TextArea'>({
getProps: mapStyledProps,
getComponent: mapTextComponent,
})(({ onChange, value = [], resourceKey, ...restProps }) => {
const actions = {};
value.forEach(item => {
actions[item.field_id] = item.actions;
});
// console.log(actions);
const [fields, setFields] = useState(value || []);
const { data = [], loading = true } = useRequest(
() => {
return api.resource('collections.fields').list({
associatedKey: resourceKey,
perPage: -1,
});
},
{
refreshDeps: [resourceKey],
},
);
console.log({ resourceKey, data });
const columns = [
{
title: '字段名称',
dataIndex: ['title'],
},
].concat(
[
{
title: '查看',
action: `${resourceKey}:list`,
},
{
title: '编辑',
action: `${resourceKey}:update`,
},
{
title: '新增',
action: `${resourceKey}:create`,
},
].map(({ title, action }) => {
let checked =
value.filter(({ actions = [] }) => actions.indexOf(action) !== -1)
.length === data.length;
return {
title: (
<>
<Checkbox
checked={checked}
onChange={e => {
const values = data.map(field => {
const items = actions[field.id] || [];
const index = items.indexOf(action);
if (index > -1) {
if (!e.target.checked) {
items.splice(index, 1);
}
} else {
if (e.target.checked) {
items.push(action);
}
}
return {
field_id: field.id,
actions: items,
};
});
// console.log(values);
setFields([...values]);
onChange([...values]);
}}
/>{' '}
{title}
</>
),
dataIndex: ['id'],
render: (val, record) => {
const items = actions[record.id] || [];
// console.log({items}, items.indexOf(action));
return (
<Checkbox
checked={items.indexOf(action) !== -1}
onChange={e => {
const values = [...value];
const index = findIndex(
values,
({ field_id, actions = [] }) => {
return field_id === record.id;
},
);
if (e.target.checked && index === -1) {
values.push({
field_id: record.id,
actions: [action],
});
} else {
const items = values[index].actions || [];
const actionIndex = items.indexOf(action);
if (!e.target.checked && actionIndex > -1) {
items.splice(actionIndex, 1);
// values[index].actions = items;
} else if (e.target.checked && actionIndex === -1) {
items.push(action);
}
}
onChange(values);
setFields(values);
}}
/>
);
},
};
}) as any,
);
return (
<Table
size={'small'}
loading={loading}
pagination={false}
dataSource={data}
columns={columns}
/>
);
});
Permissions.Tabs = connect<'TextArea'>({
getProps: mapStyledProps,
getComponent: mapTextComponent,
})(({ onChange, value = [], resourceKey, ...restProps }) => {
const { data = [], loading = true, mutate } = useRequest(
() => {
return api.resource('collections.tabs').list({
associatedKey: resourceKey,
perPage: -1,
});
},
{
refreshDeps: [resourceKey],
},
);
// const [checked, setChecked] = useState(false);
// console.log(checked);
// useEffect(() => {
// setChecked(data.length === value.length);
// console.log({resourceKey, data, value}, data.length === value.lengh);
// }, [
// data,
// ]);
return (
<Table
size={'small'}
pagination={false}
dataSource={data}
columns={[
{
title: '标签页',
dataIndex: ['title'],
},
{
title: (
<>
<Checkbox
checked={data.length === value.length}
onChange={e => {
onChange(
e.target.checked ? data.map(record => record.id) : [],
);
}}
/>{' '}
</>
),
dataIndex: ['id'],
render: (val, record) => {
const values = [...value];
return (
<Checkbox
checked={values.indexOf(record.id) !== -1}
onChange={e => {
const index = values.indexOf(record.id);
if (index !== -1) {
if (!e.target.checked) {
values.splice(index, 1);
}
} else {
values.push(record.id);
}
onChange(values);
}}
/>
);
},
},
]}
loading={loading}
/>
);
});

View File

@ -1,19 +0,0 @@
import { connect } from '@formily/react-schema-renderer';
import { Radio as AntdRadio } from 'antd';
import {
transformDataSourceKey,
mapStyledProps,
mapTextComponent,
} from '../shared';
export const Radio = connect<'Group'>({
valueName: 'checked',
getProps: mapStyledProps,
})(AntdRadio);
Radio.Group = connect({
getProps: mapStyledProps,
getComponent: mapTextComponent,
})(transformDataSourceKey(AntdRadio.Group, 'options'));
export default Radio;

View File

@ -1 +0,0 @@
import 'antd/lib/radio/style/index';

View File

@ -1,60 +0,0 @@
import React from 'react';
import { Slider } from 'antd';
import { connect } from '@formily/react-schema-renderer';
import { mapStyledProps } from '../shared';
export interface ISliderMarks {
[key: number]:
| React.ReactNode
| {
style: React.CSSProperties;
label: React.ReactNode;
};
}
export declare type SliderValue = number | [number, number];
// TODO 并不是方法,最好能引用组件的 typescript 接口定义
export interface ISliderProps {
min?: number;
max?: number;
marks?: ISliderMarks;
value?: SliderValue;
defaultValue?: SliderValue;
onChange?: (value: SliderValue) => void;
}
export const Range = connect({
defaultProps: {
style: {
width: 320,
},
},
getProps: mapStyledProps,
})(
class Component extends React.Component<ISliderProps> {
public render() {
const { onChange, value, min, max, marks, ...rest } = this.props;
let newMarks = {};
if (Array.isArray(marks)) {
marks.forEach(mark => {
newMarks[mark] = mark;
});
} else {
newMarks = marks;
}
return (
<Slider
{...rest}
onChange={onChange}
value={value}
min={min}
max={max}
marks={newMarks}
/>
);
}
},
);
export default Range;

View File

@ -1 +0,0 @@
import 'antd/lib/slider/style/index';

View File

@ -1,9 +0,0 @@
import { connect } from '@formily/react-schema-renderer';
import { Rate } from 'antd';
import { mapStyledProps } from '../shared';
export const Rating = connect({
getProps: mapStyledProps,
})(Rate);
export default Rating;

View File

@ -1 +0,0 @@
import 'antd/lib/rate/style/index';

View File

@ -1,73 +0,0 @@
import { registerFormFields } from '@formily/antd';
import { TimePicker } from './time-picker';
import { Transfer } from './transfer';
import { Switch } from './switch';
import { ArrayCards } from './array-cards';
import { ArrayTable } from './array-table';
import { Checkbox } from './checkbox';
import { DatePicker } from './date-picker';
import { Input } from './input';
import { NumberPicker, Percent } from './number-picker';
import { Password } from './password';
import { Radio } from './radio';
import { Range } from './range';
import { Rating } from './rating';
import { Upload } from './upload';
import { Filter } from './filter';
import { RemoteSelect } from './remote-select';
import { DrawerSelect } from './drawer-select';
import { SubTable } from './sub-table';
import { Cascader } from './cascader';
import { Icon } from './icons';
import { ColorSelect } from './color-select';
import { Permissions } from './permissions';
import { Values } from './values';
import { Automations } from './automations';
import { Wysiwyg } from './wysiwyg';
import { Markdown } from './markdown';
export const setup = () => {
registerFormFields({
time: TimePicker,
timerange: TimePicker.RangePicker,
transfer: Transfer,
cascader: Cascader,
boolean: Checkbox,
switch: Switch,
checkbox: Checkbox,
array: ArrayCards,
cards: ArrayCards,
table: ArrayTable,
checkboxes: Checkbox.Group,
date: DatePicker,
daterange: DatePicker.RangePicker,
year: DatePicker.YearPicker,
month: DatePicker.MonthPicker,
week: DatePicker.WeekPicker,
string: Input,
select: Input,
icon: Icon,
textarea: Input.TextArea,
number: NumberPicker,
percent: Percent,
password: Password,
radio: Radio.Group,
range: Range,
rating: Rating,
upload: Upload,
filter: Filter,
remoteSelect: RemoteSelect,
drawerSelect: DrawerSelect,
colorSelect: ColorSelect,
subTable: SubTable,
values: Values,
wysiwyg: Wysiwyg,
markdown: Markdown,
'permissions.actions': Permissions.Actions,
'permissions.fields': Permissions.Fields,
'permissions.tabs': Permissions.Tabs,
'automations.datetime': Automations.DateTime,
'automations.endmode': Automations.EndMode,
'automations.cron': Automations.Cron,
});
};

View File

@ -1,100 +0,0 @@
import React, { useEffect } from 'react';
import { connect } from '@formily/react-schema-renderer';
import moment from 'moment';
import { Select } from 'antd';
import {
mapStyledProps,
mapTextComponent,
compose,
isStr,
isArr,
} from '../shared';
import { useRequest } from 'umi';
import api from '@/api-client';
import { Spin } from '@/components/spin';
import get from 'lodash/get';
function RemoteSelectComponent(props) {
let {
schema = {},
value,
onChange,
disabled,
resourceName,
associatedKey,
filter,
labelField,
valueField,
objectValue,
placeholder,
multiple,
} = props;
console.log({ schema });
if (!resourceName) {
resourceName = get(schema, 'component.resourceName');
}
if (!filter) {
filter = get(schema, 'component.filter');
}
if (!labelField) {
labelField = get(schema, 'component.labelField');
}
if (!valueField) {
valueField = get(schema, 'component.valueField');
}
if (!valueField) {
valueField = 'id';
}
const { data = [], loading = true } = useRequest(
() => {
return api.resource(resourceName).list({
associatedKey,
filter,
});
},
{
refreshDeps: [resourceName, associatedKey],
},
);
const selectProps: any = {};
if (multiple) {
selectProps.mode = 'multiple';
}
console.log({ data, props, associatedKey });
return (
<>
<Select
{...selectProps}
placeholder={placeholder}
disabled={disabled}
notFoundContent={loading ? <Spin /> : undefined}
allowClear
loading={loading}
value={value && typeof value === 'object' ? value[valueField] : value}
onChange={(value, option) => {
if (value === null || typeof value === 'undefined') {
onChange(undefined);
return;
}
// @ts-ignore
const item = option.item;
onChange(objectValue ? item : value);
}}
>
{!loading &&
data.map(item => (
<Select.Option item={item} value={item[valueField]}>
{item[labelField]}
</Select.Option>
))}
</Select>
</>
);
}
export const RemoteSelect = connect({
getProps: mapStyledProps,
getComponent: mapTextComponent,
})(RemoteSelectComponent);
export default RemoteSelect;

View File

@ -1,13 +0,0 @@
import { connect } from '@formily/react-schema-renderer';
import {
Select as AntdSelect,
mapStyledProps,
mapTextComponent,
} from '../shared';
export const Select = connect({
getProps: mapStyledProps,
getComponent: mapTextComponent,
})(AntdSelect);
export default Select;

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