v0.5
This commit is contained in:
parent
cb8edc7b75
commit
2cbcd087ce
@ -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;
|
||||||
|
@ -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"
|
||||||
|
@ -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
6
.editorconfig
Normal file → Executable 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
10
.eslintignore
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
node_modules
|
||||||
|
lib
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
coverage
|
||||||
|
expected
|
||||||
|
website
|
||||||
|
gh-pages
|
||||||
|
weex
|
||||||
|
build.ts
|
47
.github/workflows/node.js.yml
vendored
47
.github/workflows/node.js.yml
vendored
@ -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
|
|
5
.github/workflows/vercel.yml
vendored
5
.github/workflows/vercel.yml
vendored
@ -1,9 +1,6 @@
|
|||||||
name: vercel
|
name: vercel
|
||||||
|
|
||||||
on:
|
on: [push]
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
|
@ -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: {
|
||||||
|
@ -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,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
11
lerna.json
11
lerna.json
@ -3,14 +3,13 @@
|
|||||||
"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"
|
||||||
]
|
]
|
||||||
|
0
packages/create-nocobase-app/src/fixtures/.gitkeep → mock/.gitkeep
Executable file → Normal file
0
packages/create-nocobase-app/src/fixtures/.gitkeep → mock/.gitkeep
Executable file → Normal file
17
mock/api.ts
Normal file
17
mock/api.ts
Normal 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
37
mock/resources/blocks.ts
Normal 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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
8
mock/resources/examples.ts
Normal file
8
mock/resources/examples.ts
Normal 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
7
mock/resources/index.ts
Normal 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
184
mock/resources/routes.ts
Normal 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
19
mock/resources/users.ts
Normal 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'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
@ -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
25431
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
89
package.json
89
package.json
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
0
packages/api/bin/nocobase.js
Normal file → Executable 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",
|
||||||
|
@ -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) {
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
export default {
|
|
||||||
entry: 'src/api',
|
|
||||||
target: 'node',
|
|
||||||
cjs: { type: 'babel', lazy: true },
|
|
||||||
include: 'api/*',
|
|
||||||
disableTypeCheck: true,
|
|
||||||
// pkgs: [
|
|
||||||
// 'api',
|
|
||||||
// ],
|
|
||||||
};
|
|
3
packages/app/.gitignore
vendored
3
packages/app/.gitignore
vendored
@ -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
|
|
@ -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
0
packages/app/.prettierignore
Executable file → Normal file
0
packages/app/.prettierrc
Executable file → Normal file
0
packages/app/.prettierrc
Executable file → Normal 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
4
packages/app/README.md
Executable file → Normal 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
0
packages/app/mock/.gitkeep
Executable file → Normal file
11
packages/app/mock/api.ts
Normal file
11
packages/app/mock/api.ts
Normal 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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
8
packages/app/mock/resources/examples.ts
Normal file
8
packages/app/mock/resources/examples.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import Mock from 'mockjs';
|
||||||
|
|
||||||
|
export async function list(req: any, res: any) {
|
||||||
|
res.json({
|
||||||
|
data: [],
|
||||||
|
meta: {},
|
||||||
|
});
|
||||||
|
}
|
6
packages/app/mock/resources/index.ts
Normal file
6
packages/app/mock/resources/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
import * as routes from './routes';
|
||||||
|
import * as users from './users';
|
||||||
|
import * as examples from './examples';
|
||||||
|
|
||||||
|
export { routes, users, examples };
|
184
packages/app/mock/resources/routes.ts
Normal file
184
packages/app/mock/resources/routes.ts
Normal 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`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
10
packages/app/mock/resources/users.ts
Normal file
10
packages/app/mock/resources/users.ts
Normal 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'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
@ -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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 待改进,提供一个封装度更完整的 SDK,request 可以自由替换
|
|
||||||
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;
|
|
@ -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: {},
|
|
||||||
};
|
|
||||||
}
|
|
@ -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);
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
@ -1 +0,0 @@
|
|||||||
import 'antd/lib/card/style/index';
|
|
@ -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;
|
|
@ -1 +0,0 @@
|
|||||||
import 'antd/lib/table/style/index';
|
|
@ -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,
|
|
||||||
};
|
|
@ -1,5 +0,0 @@
|
|||||||
.ant-input-group.ant-input-group-compact {
|
|
||||||
.ant-input-group-wrapper {
|
|
||||||
width: 120px;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
@ -1 +0,0 @@
|
|||||||
import 'antd/lib/cascader/style/index';
|
|
@ -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;
|
|
@ -1 +0,0 @@
|
|||||||
import 'antd/lib/checkbox/style/index';
|
|
@ -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;
|
|
@ -1 +0,0 @@
|
|||||||
import 'antd/lib/button/style/index';
|
|
@ -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;
|
|
@ -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;
|
|
@ -1 +0,0 @@
|
|||||||
import 'antd/lib/date-picker/style/index';
|
|
@ -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;
|
|
@ -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;
|
|
@ -1,3 +0,0 @@
|
|||||||
.filter-remove-link {
|
|
||||||
color: #d9d9d9;
|
|
||||||
}
|
|
@ -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,
|
|
||||||
};
|
|
||||||
};
|
|
@ -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;
|
|
@ -1 +0,0 @@
|
|||||||
import 'antd/lib/card/style/index';
|
|
@ -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;
|
|
@ -1 +0,0 @@
|
|||||||
import 'antd/lib/card/style/index';
|
|
@ -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;
|
|
@ -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;
|
|
@ -1 +0,0 @@
|
|||||||
import 'antd/lib/col/style/index';
|
|
@ -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;
|
|
@ -1 +0,0 @@
|
|||||||
import 'antd/lib/row/style/index';
|
|
@ -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;
|
|
@ -1,2 +0,0 @@
|
|||||||
import 'antd/lib/row/style/index';
|
|
||||||
import 'antd/lib/col/style/index';
|
|
@ -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;
|
|
@ -1,3 +0,0 @@
|
|||||||
import { MegaLayout, FormMegaLayout } from '@formily/antd';
|
|
||||||
|
|
||||||
export { MegaLayout, FormMegaLayout };
|
|
@ -1,5 +0,0 @@
|
|||||||
import { FormSlot } from '@formily/react-schema-renderer';
|
|
||||||
|
|
||||||
export { FormSlot };
|
|
||||||
|
|
||||||
export default FormSlot;
|
|
@ -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;
|
|
@ -1 +0,0 @@
|
|||||||
import 'antd/lib/steps/style/index';
|
|
@ -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;
|
|
@ -1,2 +0,0 @@
|
|||||||
import 'antd/lib/tabs/style/index';
|
|
||||||
import 'antd/lib/badge/style/index';
|
|
@ -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;
|
|
@ -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;
|
|
@ -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';
|
|
@ -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;
|
|
@ -1 +0,0 @@
|
|||||||
import 'antd/lib/input/style/index';
|
|
@ -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;
|
|
@ -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;
|
|
@ -1 +0,0 @@
|
|||||||
import 'antd/lib/input-number/style/index';
|
|
@ -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;
|
|
@ -1 +0,0 @@
|
|||||||
import 'antd/lib/input/style/index';
|
|
@ -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}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
@ -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;
|
|
@ -1 +0,0 @@
|
|||||||
import 'antd/lib/radio/style/index';
|
|
@ -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;
|
|
@ -1 +0,0 @@
|
|||||||
import 'antd/lib/slider/style/index';
|
|
@ -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;
|
|
@ -1 +0,0 @@
|
|||||||
import 'antd/lib/rate/style/index';
|
|
@ -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,
|
|
||||||
});
|
|
||||||
};
|
|
@ -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;
|
|
@ -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
Loading…
Reference in New Issue
Block a user