diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..94b2b75e9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +# Matches multiple files with brace expansion notation +# Set default charset +[*] +charset = utf-8 + +# Indentation override for all JS under lib directory +[*.{js,ts,json}] +indent_style = space +indent_size = 2 diff --git a/.env b/.env deleted file mode 100644 index c6323f000..000000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -DIALECT=postgres \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..42cd9137c --- /dev/null +++ b/.env.example @@ -0,0 +1,16 @@ +DB_DIALECT=postgres +DB_HOST=localhost +DB_PORT=5432 +DB_DATABASE=nocobase +DB_USER=test +DB_PASSWORD=test + +# DB_DIALECT=mysql +# DB_PORT=3306 + +DB_MYSQL_PORT=3306 +DB_POSTGRES_PORT=5432 + +HTTP_PORT=23000 + +VERDACCIO_PORT=4873 diff --git a/.eslintrc b/.eslintrc old mode 100755 new mode 100644 diff --git a/.fatherrc.ts b/.fatherrc.ts old mode 100755 new mode 100644 index 87b27ac7a..00146e346 --- a/.fatherrc.ts +++ b/.fatherrc.ts @@ -7,11 +7,12 @@ const headPkgs = [ 'database', 'resourcer', 'actions', + 'client', ]; const tailPkgs = []; const otherPkgs = readdirSync(join(__dirname, 'packages')).filter( (pkg) => { - return pkg !== 'father-build' && pkg.charAt(0) !== '.' && !headPkgs.includes(pkg) && !tailPkgs.includes(pkg) + return !['father-build', 'app'].includes(pkg) && pkg.charAt(0) !== '.' && !headPkgs.includes(pkg) && !tailPkgs.includes(pkg) }, ); diff --git a/.gitignore b/.gitignore index 70eec6e8a..163d53542 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules/ lib/ +.env .DS_Store package-lock.json yarn.lock diff --git a/README.md b/README.md new file mode 100644 index 000000000..5b584dcfa --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ + +Development +---------- + +### Install Dependencies + +~~~shell +# Install dependencies for root project +npm i + +# Install dependencies for sub packages via lerna +npm run bootstrap +~~~ + +### Set Environment Variables + +~~~shell +cp .env.example .env +~~~ + +### Test + +~~~ +# For all packages +npm test + +# For specific package +npm test packages/ +~~~ diff --git a/docker-compose.yml b/docker-compose.yml index bd10a15a2..745268718 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,9 +9,10 @@ services: networks: - node-network environment: - - VERDACCIO_PORT=4873 + - VERDACCIO_PORT=${VERDACCIO_PORT} + restart: always ports: - - "4873:4873" + - "${VERDACCIO_PORT}:4873" # volumes: # - "./verdaccio/storage:/verdaccio/storage" # - "./verdaccio/config:/verdaccio/conf" @@ -19,20 +20,20 @@ services: mysql: image: mysql:5.7 environment: - MYSQL_DATABASE: "test" - MYSQL_USER: "test" - MYSQL_PASSWORD: "test" - MYSQL_ROOT_PASSWORD: "test" + MYSQL_DATABASE: ${DB_DATABASE} + MYSQL_USER: ${DB_USER} + MYSQL_PASSWORD: ${DB_PASSWORD} + MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} restart: always ports: - - "43306:3306" + - "${DB_MYSQL_PORT}:3306" postgres: image: postgres:10 restart: always ports: - - "45432:5432" + - "${DB_POSTGRES_PORT}:5432" command: postgres -c wal_level=logical environment: - POSTGRES_USER: test - POSTGRES_DB: test - POSTGRES_PASSWORD: test \ No newline at end of file + POSTGRES_USER: ${DB_USER} + POSTGRES_DB: ${DB_DATABASE} + POSTGRES_PASSWORD: ${DB_PASSWORD} \ No newline at end of file diff --git a/jest.config.js b/jest.config.js old mode 100755 new mode 100644 diff --git a/lerna.json b/lerna.json index 79dfc4aad..0c4b9e089 100644 --- a/lerna.json +++ b/lerna.json @@ -10,7 +10,7 @@ ] }, "publish": { - "allowBranch": "master", + "allowBranch": ["master", "develop"], "ignoreChanges": [ "*.md" ] diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 000000000..5b0d1b9a8 --- /dev/null +++ b/nodemon.json @@ -0,0 +1,5 @@ +{ + "watch": ["packages", ".env"], + "ext": "ts", + "exec": "ts-node ./packages/server/example/index.ts" +} \ No newline at end of file diff --git a/package.json b/package.json index c952fe652..e7a1e5c44 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,8 @@ "name": "root", "private": true, "scripts": { + "start": "cd packages/app && npm start", + "start-server": "yarn nodemon", "bootstrap": "lerna bootstrap --no-ci", "build": "npm run build-father-build && node packages/father-build/bin/father-build.js", "build-father-build": "cd packages/father-build && npm run build", @@ -14,11 +16,13 @@ "devDependencies": { "@koa/router": "^9.3.1", "@types/jest": "^26.0.4", + "@types/koa": "^2.11.6", "@types/koa-bodyparser": "^4.3.0", "@types/koa-mount": "^4.0.0", "@types/koa__router": "^8.0.2", "@types/lodash": "^4.14.158", "@types/node": "^14.0.23", + "@types/react": "^16.9.53", "@types/supertest": "^2.0.10", "@typescript-eslint/eslint-plugin": "^3.6.1", "@typescript-eslint/parser": "^3.6.1", @@ -29,6 +33,7 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-standard": "^4.0.1", + "father-build": "^1.18.5", "jest": "^26.1.0", "koa": "^2.13.0", "koa-bodyparser": "^4.3.0", diff --git a/packages/api/Dockerfile b/packages/api/Dockerfile deleted file mode 100644 index fc9816be4..000000000 --- a/packages/api/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM node:stretch - -WORKDIR /app -COPY . /app - -EXPOSE 23000 - -# # Install app dependencies -# ENV NPM_CONFIG_LOGLEVEL warn -# RUN yarn install - -# # Show current folder structure in logs -# RUN ls -al -R - -# CMD [ "npm", "run", "serve" ] diff --git a/packages/api/docker-compose.yml b/packages/api/docker-compose.yml deleted file mode 100644 index b85c156c8..000000000 --- a/packages/api/docker-compose.yml +++ /dev/null @@ -1,41 +0,0 @@ -version: "3" -networks: - backend: - driver: bridge -services: - app: - build: - context: ./ - volumes: - - ./:/app - ports: - - "${HTTP_PORT}:23000" - command: [ "yarn", "start" ] - env_file: - - ./.env - networks: - - backend - mysql: - image: mysql:5.7 - environment: - MYSQL_DATABASE: ${DB_DATABASE} - MYSQL_USER: ${DB_USER} - MYSQL_PASSWORD: ${DB_PASSWORD} - MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} - restart: always - ports: - - "23306:3306" - networks: - - backend - postgres: - image: postgres:10 - restart: always - ports: - - "25432:5432" - networks: - - backend - command: postgres -c wal_level=logical - environment: - POSTGRES_DB: ${DB_DATABASE} - POSTGRES_USER: ${DB_USER} - POSTGRES_PASSWORD: ${DB_PASSWORD} diff --git a/packages/api/example/index.ts b/packages/api/example/index.ts deleted file mode 100644 index a4e640a6d..000000000 --- a/packages/api/example/index.ts +++ /dev/null @@ -1,42 +0,0 @@ -import Api from '../src'; -import dotenv from 'dotenv'; - -const sync = { - force: true, - alter: { - drop: true, - }, -}; - -dotenv.config(); - -const api = Api.create({ - database: { - username: process.env.DB_USER, - password: process.env.DB_PASSWORD, - database: process.env.DB_DATABASE, - host: process.platform === 'linux' ? process.env.DB_HOST : 'localhost', - port: process.platform === 'linux' ? parseInt(process.env.DB_PORT) : ( process.env.DB_DIALECT == 'postgres' ? 25432 : 23306 ), - dialect: process.env.DB_DIALECT as any, - dialectOptions: { - charset: 'utf8mb4', - collate: 'utf8mb4_unicode_ci', - }, - // logging: false, - define: {}, - sync, - }, - resourcer: { - prefix: '/api', - }, -}); - -api - .plugins([ - [require('../../plugin-collections/src/index').default, {}], - ]) - .then(() => { - api.listen(23001, () => { - console.log('http://localhost:23001/'); - }); - }); diff --git a/packages/api/nodemon.json b/packages/api/nodemon.json deleted file mode 100644 index 1c3c15cda..000000000 --- a/packages/api/nodemon.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "watch": ["src", ".env", ".env.dev", "../plugin-collections/src"], - "ext": "ts", - "ignore": ["src/**/*.test.ts"], - "exec": "ts-node ./example/index.ts" -} \ No newline at end of file diff --git a/packages/api/package.json b/packages/api/package.json deleted file mode 100644 index f453936db..000000000 --- a/packages/api/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "@nocobase/api", - "version": "0.3.0-alpha.0", - "main": "lib/index.js", - "license": "MIT", - "scripts": { - "start": "nodemon", - "sync": "ts-node ./src/sync.ts", - "serve": "pm2-runtime start pm2.json", - "build": "tsc --declaration", - "db:sync": "docker-compose run app bash -c 'yarn sync'", - "logs": "docker-compose logs app" - }, - "dependencies": { - "@koa/cors": "^3.1.0", - "@koa/router": "^9.4.0", - "@nocobase/actions": "^0.3.0-alpha.0", - "@nocobase/database": "^0.3.0-alpha.0", - "@nocobase/resourcer": "^0.3.0-alpha.0", - "@types/koa": "^2.11.4", - "@types/koa-bodyparser": "^4.3.0", - "@types/koa__router": "^8.0.2", - "bcrypt": "^5.0.0", - "crypto-random-string": "^3.3.0", - "dotenv": "^8.2.0", - "koa": "^2.13.0", - "koa-bodyparser": "^4.3.0", - "mockjs": "^1.1.0", - "mysql": "^2.18.1", - "mysql2": "^2.1.0", - "nodemon": "^2.0.4", - "pg": "^8.3.3", - "pg-hstore": "^2.3.3", - "pm2": "^4.4.1", - "ts-node": "^9.0.0", - "typescript": "^4.0.2" - } -} diff --git a/packages/api/pm2.json b/packages/api/pm2.json deleted file mode 100755 index c44d08639..000000000 --- a/packages/api/pm2.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "apps": [{ - "name": "nocobase-api", - "script": "lib/index.js", - "instances": 0, - "exec_mode": "cluster", - "env": { - "NODE_ENV": "development" - }, - "env_production" : { - "NODE_ENV": "production" - } - }] -} diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts deleted file mode 100644 index 94c8f627a..000000000 --- a/packages/api/src/index.ts +++ /dev/null @@ -1,53 +0,0 @@ -import Koa from 'koa'; -import Database from '@nocobase/database'; -import Resourcer from '@nocobase/resourcer'; -import actions from '@nocobase/actions'; - -export class Application extends Koa { - - database: Database; - - resourcer: Resourcer; - - async plugins(plugins: any[]) { - await Promise.all(plugins.map(async (pluginOption) => { - let plugin: Function; - let options = {}; - if (Array.isArray(pluginOption)) { - plugin = pluginOption.shift(); - plugin = plugin.bind(this); - options = pluginOption.shift()||{}; - } else if (typeof pluginOption === 'function') { - plugin = pluginOption.bind(this); - } - return await plugin(options); - })); - } -} - -export default { - create(options: any): Application { - console.log(options); - - const app = new Application(); - const resourcer = new Resourcer(); - const database = new Database(options.database); - - app.database = database; - app.resourcer = resourcer; - - resourcer.registerHandlers(actions.common); - - app.use(async (ctx, next) => { - ctx.db = database; - ctx.database = database; - await next(); - }); - - app.use(resourcer.middleware(options.resourcer || { - prefix: '/api', - })); - - return app; - } -} diff --git a/packages/app/.editorconfig b/packages/app/.editorconfig new file mode 100755 index 000000000..7e3649acc --- /dev/null +++ b/packages/app/.editorconfig @@ -0,0 +1,16 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab diff --git a/packages/app/.env.example b/packages/app/.env.example new file mode 100644 index 000000000..42cd9137c --- /dev/null +++ b/packages/app/.env.example @@ -0,0 +1,16 @@ +DB_DIALECT=postgres +DB_HOST=localhost +DB_PORT=5432 +DB_DATABASE=nocobase +DB_USER=test +DB_PASSWORD=test + +# DB_DIALECT=mysql +# DB_PORT=3306 + +DB_MYSQL_PORT=3306 +DB_POSTGRES_PORT=5432 + +HTTP_PORT=23000 + +VERDACCIO_PORT=4873 diff --git a/packages/app/.fatherrc.ts b/packages/app/.fatherrc.ts new file mode 100755 index 000000000..491de68f6 --- /dev/null +++ b/packages/app/.fatherrc.ts @@ -0,0 +1,10 @@ +export default { + entry: 'src/api', + target: 'node', + cjs: { type: 'babel', lazy: true }, + include: 'api/*', + disableTypeCheck: true, + // pkgs: [ + // 'api', + // ], +}; diff --git a/packages/app/.gitignore b/packages/app/.gitignore new file mode 100644 index 000000000..e9b162c8e --- /dev/null +++ b/packages/app/.gitignore @@ -0,0 +1,20 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/npm-debug.log* +/yarn-error.log +/yarn.lock +/package-lock.json + +# production +/dist +.env +# misc +.DS_Store + +# umi +/src/.umi +/src/.umi-production +/src/.umi-test +/.env.local diff --git a/packages/app/.prettierignore b/packages/app/.prettierignore new file mode 100755 index 000000000..0d4222f54 --- /dev/null +++ b/packages/app/.prettierignore @@ -0,0 +1,8 @@ +**/*.md +**/*.svg +**/*.ejs +**/*.html +package.json +.umi +.umi-production +.umi-test diff --git a/packages/app/.prettierrc b/packages/app/.prettierrc new file mode 100755 index 000000000..94beb1484 --- /dev/null +++ b/packages/app/.prettierrc @@ -0,0 +1,11 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "printWidth": 80, + "overrides": [ + { + "files": ".prettierrc", + "options": { "parser": "json" } + } + ] +} diff --git a/packages/app/.umirc.ts b/packages/app/.umirc.ts new file mode 100644 index 000000000..9befd627f --- /dev/null +++ b/packages/app/.umirc.ts @@ -0,0 +1,25 @@ +import { defineConfig } from 'umi'; + +export default defineConfig({ + nodeModulesTransform: { + type: 'none', + }, + define: { + 'process.env.API': `/api`, + // 'process.env.API': `http://localhost:${process.env.HTTP_PORT}/api`, + }, + proxy: { + '/api': { + 'target': `http://localhost:${process.env.HTTP_PORT}/`, + 'changeOrigin': true, + 'pathRewrite': { '^/api' : '/api' }, + }, + }, + routes: [ + { + exact: false, + path: '/:path(.*)', + component: '@/pages/index', + }, + ], +}); diff --git a/packages/app/README.md b/packages/app/README.md new file mode 100755 index 000000000..e521300a0 --- /dev/null +++ b/packages/app/README.md @@ -0,0 +1,15 @@ +# NocoBase Application + +## Getting Started + +Install dependencies, + +```bash +$ yarn install +``` + +Start the dev server, + +```bash +$ yarn start +``` diff --git a/packages/plugin-pages/src/index.ts b/packages/app/mock/.gitkeep old mode 100644 new mode 100755 similarity index 100% rename from packages/plugin-pages/src/index.ts rename to packages/app/mock/.gitkeep diff --git a/packages/app/nodemon.json b/packages/app/nodemon.json new file mode 100644 index 000000000..67624595a --- /dev/null +++ b/packages/app/nodemon.json @@ -0,0 +1,5 @@ +{ + "watch": ["src/api", ".env", "../"], + "ext": "ts", + "exec": "ts-node ./src/api/index.ts" +} \ No newline at end of file diff --git a/packages/app/package.json b/packages/app/package.json new file mode 100644 index 000000000..99a8e3b4c --- /dev/null +++ b/packages/app/package.json @@ -0,0 +1,45 @@ +{ + "name": "@nocobase/app", + "version": "0.3.0-alpha.0", + "private": true, + "scripts": { + "start": "concurrently \"nodemon\" \"umi dev\"", + "build": "father-build && umi build", + "postinstall": "umi generate tmp", + "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'", + "test": "umi-test", + "test:coverage": "umi-test --coverage" + }, + "gitHooks": { + "pre-commit": "lint-staged" + }, + "lint-staged": { + "*.{js,jsx,less,md,json}": [ + "prettier --write" + ], + "*.ts?(x)": [ + "prettier --parser=typescript --write" + ] + }, + "dependencies": { + "@ant-design/pro-layout": "^5.0.12", + "@formily/antd-components": "^1.3.6", + "@nocobase/client": "^0.3.0-alpha.0", + "@nocobase/database": "^0.3.0-alpha.0", + "@nocobase/father-build": "^0.3.0-alpha.0", + "@nocobase/plugin-collections": "^0.3.0-alpha.0", + "@nocobase/plugin-pages": "^0.3.0-alpha.0", + "@nocobase/server": "^0.3.0-alpha.0", + "@umijs/preset-react": "1.x", + "@umijs/test": "^3.2.23", + "concurrently": "^5.3.0", + "lint-staged": "^10.0.7", + "nodemon": "^2.0.6", + "prettier": "^1.19.1", + "react": "^16.12.0", + "react-dom": "^16.12.0", + "styled-components": "^5.2.1", + "umi": "^3.2.23", + "yorkie": "^2.0.0" + } +} diff --git a/packages/app/src/api/index.ts b/packages/app/src/api/index.ts new file mode 100644 index 000000000..c938d3314 --- /dev/null +++ b/packages/app/src/api/index.ts @@ -0,0 +1,197 @@ +import Api from '../../../server/src'; +import dotenv from 'dotenv'; +import path from 'path'; +import Database, { Model } from '@nocobase/database'; +import { get } from 'lodash'; + +const sync = { + force: true, + alter: { + drop: true, + }, +}; + +dotenv.config(); + +const api = Api.create({ + database: { + username: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_DATABASE, + host: process.env.DB_HOST, + port: process.env.DB_PORT, + dialect: process.env.DB_DIALECT, + dialectOptions: { + charset: 'utf8mb4', + collate: 'utf8mb4_unicode_ci', + }, + logging: false, + define: {}, + sync, + }, + resourcer: { + prefix: '/api', + }, +}); + +(async () => { + await api + .plugins([ + [path.resolve(__dirname, '../../../plugin-collections'), {}], + [path.resolve(__dirname, '../../../plugin-pages'), {}], + [path.resolve(__dirname, '../../../plugin-permissions'), {}], + [path.resolve(__dirname, '../../../plugin-users'), {}], + [path.resolve(__dirname, '../../../plugin-file-manager'), {}], + // [require('../../plugin-collections/src/index').default, {}], + // [require('../../plugin-pages/src/index').default, {}], + ]); + + const database: Database = api.database; + + const [Collection, View, Action, Tab] = database.getModels(['collections', 'views', 'actions', 'tabs']); + const tables = database.getTables([]); + + for (let table of tables) { + const options = table.getOptions(); + const collection = await Collection.create(options); + // console.log(options); + const associations: any = {}; + if (options.fields) { + associations['fields'] = options.fields.map(item => ({ + ...item, + options: item, + })) + } + if (options.tabs) { + associations['tabs'] = options.tabs.map(item => ({ + ...item, + options: item, + })) + } + if (options.actions) { + associations['actions'] = options.actions.map(item => ({ + ...item, + options: item, + })) + } + if (options.views) { + associations['views'] = options.views.map(item => ({ + ...item, + options: item, + })) + } + await collection.updateAssociations(associations); + } + + const actions = await Action.findAll(); + + for (const action of actions) { + const viewName = action.options.viewName; + console.log({viewName}); + if (viewName) { + const view = await View.findOne({ + where: { + name: viewName, + collection_name: action.collection_name + }, + }); + if (view) { + action.options.viewId = view.id; + console.log(action.options); + action.setDataValue('options', action.options); + action.changed('options', true); + await action.save(); + } + } + } + const tabs = await Tab.findAll(); + + for (const tab of tabs) { + const viewName = tab.options.viewName; + if (!viewName) { + continue; + } + let view: any; + if (tab.type === 'association') { + view = await View.findOne({ + where: { + name: viewName, + collection_name: tab.options.association, + }, + }); + } else { + view = await View.findOne({ + where: { + name: viewName, + collection_name: tab.collection_name, + }, + }); + } + if (view) { + tab.options.viewId = view.id; + tab.setDataValue('options', tab.options); + tab.changed('options', true); + await tab.save(); + } + } + const views = await View.findAll(); + for (const view of views) { + const detailsViewName = view.options.detailsViewName; + if (detailsViewName) { + const v = await View.findOne({ + where: { + name: detailsViewName, + collection_name: view.collection_name + }, + }); + if (v) { + view.options.detailsViewId = v.id; + view.setDataValue('options', view.options); + view.changed('options', true); + await view.save(); + } + } + const updateViewName = view.options.updateViewName; + if (updateViewName) { + const v = await View.findOne({ + where: { + name: updateViewName, + collection_name: view.collection_name + }, + }); + if (v) { + view.options.updateViewId = v.id; + view.setDataValue('options', view.options); + view.changed('options', true); + await view.save(); + } + } + console.log({detailsViewName, updateViewName}); + } + + // for (let table of tables) { + // const options = table.getOptions(); + // const collection = await Collection.findOne({ + // where: { + // name: options.name, + // }, + // }); + // const tabs = await collection.getTabs() as Model[]; + // const actions = await collection.getActions() as Model[]; + // const views = await collection.getViews() as Model[]; + // for (const tab of tabs) { + // tab.options.viewName; + + // } + // } + + // const collections = await Collection.findAll(); + + // await Promise.all(collections.map(async (collection) => { + // return await collection.modelInit(); + // })); + + api.listen(process.env.HTTP_PORT, () => { + console.log(`http://localhost:${process.env.HTTP_PORT}/`); + }); +})(); diff --git a/packages/app/src/app.ts b/packages/app/src/app.ts new file mode 100644 index 000000000..09fa7ca70 --- /dev/null +++ b/packages/app/src/app.ts @@ -0,0 +1,24 @@ +import { RequestConfig, request as umiRequest, history } from 'umi'; + +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(); + } + ], +}; \ No newline at end of file diff --git a/packages/app/src/components/actions/Create.tsx b/packages/app/src/components/actions/Create.tsx new file mode 100644 index 000000000..f3c718f3f --- /dev/null +++ b/packages/app/src/components/actions/Create.tsx @@ -0,0 +1,39 @@ +import React, { useRef } from 'react'; +import { Button } from 'antd'; +import ViewFactory from '@/components/views'; + +export function Create(props) { + console.log(props); + const { title, viewCollectionName, viewName } = props.schema; + const { activeTab = {}, item = {} } = props; + const { association, collection_name } = activeTab; + + const params = {}; + + if (association) { + params['resourceName'] = association; + params['associatedName'] = collection_name; + params['associatedKey'] = item.itemId; + } else { + params['resourceName'] = collection_name; + params['resourceKey'] = item.itemId; + } + + const drawerRef = useRef(); + return ( + <> + + + + ) +} + +export default Create; diff --git a/packages/app/src/components/actions/Destroy.tsx b/packages/app/src/components/actions/Destroy.tsx new file mode 100644 index 000000000..bbc9195e2 --- /dev/null +++ b/packages/app/src/components/actions/Destroy.tsx @@ -0,0 +1,18 @@ +import React, { useRef } from 'react'; +import { Button } from 'antd'; +import ViewFactory from '@/components/views'; + +export function Destroy(props) { + console.log(props); + const { title, viewId } = props.schema; + const drawerRef = useRef(); + return ( + <> + + + ) +} + +export default Destroy; diff --git a/packages/app/src/components/actions/Update.tsx b/packages/app/src/components/actions/Update.tsx new file mode 100644 index 000000000..31fbbab4d --- /dev/null +++ b/packages/app/src/components/actions/Update.tsx @@ -0,0 +1,39 @@ +import React, { useRef } from 'react'; +import { Button } from 'antd'; +import ViewFactory from '@/components/views'; + +export function Update(props) { + console.log(props); + const { title, viewCollectionName, viewName } = props.schema; + const { activeTab = {}, item = {} } = props; + const { association, collection_name } = activeTab; + + const params = {}; + + if (association) { + params['resourceName'] = association; + params['associatedName'] = collection_name; + params['associatedKey'] = item.itemId; + } else { + params['resourceName'] = collection_name; + params['resourceKey'] = item.itemId; + } + + const drawerRef = useRef(); + return ( + <> + + + + ) +} + +export default Update; diff --git a/packages/app/src/components/actions/index.tsx b/packages/app/src/components/actions/index.tsx new file mode 100644 index 000000000..542282c42 --- /dev/null +++ b/packages/app/src/components/actions/index.tsx @@ -0,0 +1,39 @@ + +import React from 'react'; +import Create from './Create'; +import Update from './Update'; +import Destroy from './Destroy'; +import { Space } from 'antd'; + +const ACTIONS = new Map(); + +export function registerAction(type: string, Action: any) { + ACTIONS.set(type, Action); +} + +registerAction('update', Update); +registerAction('create', Create); +registerAction('destroy', Destroy); + +export function getAction(type: string) { + return ACTIONS.get(type); +} + +export function Action(props) { + const { schema = {} } = props; + // cnsole.log(schema); + const { type } = schema; + const Action = getAction(type); + return Action && ; +} + +export function Actions(props) { + const { style, schema, actions = [] } = props; + return actions.length > 0 && ( + + {actions.map(action => )} + + ); +} + +export default Actions; diff --git a/packages/app/src/components/form.fields/array-cards/index.tsx b/packages/app/src/components/form.fields/array-cards/index.tsx new file mode 100644 index 000000000..6c4ed4a40 --- /dev/null +++ b/packages/app/src/components/form.fields/array-cards/index.tsx @@ -0,0 +1,197 @@ +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: () => , + RemoveIcon: () => , + MoveDownIcon: () => , + MoveUpIcon: () => +} + +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 ( +
+ + {toArr(value).map((item, index) => { + return ( + + {index + 1}. {componentProps.title || schema.title} + + } + extra={ + + mutators.remove(index)} + /> + mutators.moveDown(index)} + /> + mutators.moveUp(index)} + /> + {isFn(renderExtraOperations) + ? renderExtraOperations(index) + : renderExtraOperations} + + } + > + {schemaItems && ( + + )} + + ) + })} + + {({ children, allowAddition }) => { + return ( + +
{children}
+
+ ) + }} +
+ + {({ children, isEmpty }) => { + if (!isEmpty) { + return ( +
+ {children} +
+ ) + } + }} +
+
+
+ ) + } +)` + 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 diff --git a/packages/app/src/components/form.fields/array-cards/style.ts b/packages/app/src/components/form.fields/array-cards/style.ts new file mode 100644 index 000000000..f6b218068 --- /dev/null +++ b/packages/app/src/components/form.fields/array-cards/style.ts @@ -0,0 +1 @@ +import 'antd/lib/card/style/index' \ No newline at end of file diff --git a/packages/app/src/components/form.fields/array-table/index.tsx b/packages/app/src/components/form.fields/array-table/index.tsx new file mode 100644 index 000000000..2d6f9c845 --- /dev/null +++ b/packages/app/src/components/form.fields/array-table/index.tsx @@ -0,0 +1,234 @@ +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 } 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: () => , + RemoveIcon: () => , + MoveDownIcon: () => , + MoveUpIcon: () => +} + +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 ( + + + + ) + } + } + }) + } + // 兼容异步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 ( + +
+ mutators.remove(index)} + /> + mutators.moveDown(index)} + /> + mutators.moveUp(index)} + /> + {isFn(renderExtraOperations) + ? renderExtraOperations(index) + : renderExtraOperations} +
+
+ ) + } + }) + } + if (draggable) { + columns.unshift({ + width: 20, + key: 'dragHandler', + render: () => { + return + } + }) + } + const renderTable = () => { + return ( + { + return toArr(value).indexOf(record) + }} + pagination={false} + columns={columns} + dataSource={toArr(value)} + >
+ ) + } + return ( +
+ + {draggable ? ( + + {renderTable()} + + ) : ( + renderTable() + )} + + {({ children }) => { + return ( + children && ( +
+ {children} +
+ ) + ) + }} +
+
+
+ ) + } +)` + 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 diff --git a/packages/app/src/components/form.fields/array-table/style.ts b/packages/app/src/components/form.fields/array-table/style.ts new file mode 100644 index 000000000..67502ef29 --- /dev/null +++ b/packages/app/src/components/form.fields/array-table/style.ts @@ -0,0 +1 @@ +import 'antd/lib/table/style/index' \ No newline at end of file diff --git a/packages/app/src/components/form.fields/checkbox/index.tsx b/packages/app/src/components/form.fields/checkbox/index.tsx new file mode 100644 index 000000000..e9b6fd55e --- /dev/null +++ b/packages/app/src/components/form.fields/checkbox/index.tsx @@ -0,0 +1,21 @@ +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 diff --git a/packages/app/src/components/form.fields/checkbox/style.ts b/packages/app/src/components/form.fields/checkbox/style.ts new file mode 100644 index 000000000..560a1c8de --- /dev/null +++ b/packages/app/src/components/form.fields/checkbox/style.ts @@ -0,0 +1 @@ +import 'antd/lib/checkbox/style/index' \ No newline at end of file diff --git a/packages/app/src/components/form.fields/circle-button/index.tsx b/packages/app/src/components/form.fields/circle-button/index.tsx new file mode 100644 index 000000000..194632132 --- /dev/null +++ b/packages/app/src/components/form.fields/circle-button/index.tsx @@ -0,0 +1,16 @@ +import React from 'react' +import { Button } from 'antd' +import { ButtonProps } from 'antd/lib/button' + +export const CircleButton: React.FC = props => { + const hasText = String(props.className || '').indexOf('has-text') > -1 + return ( + + + ) + } + } +) + +export default Upload diff --git a/packages/app/src/components/form.fields/upload/style.ts b/packages/app/src/components/form.fields/upload/style.ts new file mode 100644 index 000000000..0fd63d0c7 --- /dev/null +++ b/packages/app/src/components/form.fields/upload/style.ts @@ -0,0 +1 @@ +import 'antd/lib/upload/style/index' \ No newline at end of file diff --git a/packages/app/src/components/pages/CollectionLoader/Breadcrumb/index.tsx b/packages/app/src/components/pages/CollectionLoader/Breadcrumb/index.tsx new file mode 100644 index 000000000..72e5dfbc0 --- /dev/null +++ b/packages/app/src/components/pages/CollectionLoader/Breadcrumb/index.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { PageHeader, Tabs, Button, Statistic, Descriptions } from 'antd'; +import './style.less'; + +export function Breadcrumb(props) { + return ( +
{props.children}
+ ); +} + +Breadcrumb.Item = (props) => { + return ( +
标题一
+ ); +} + +export default Breadcrumb; diff --git a/packages/app/src/components/pages/CollectionLoader/Breadcrumb/style.less b/packages/app/src/components/pages/CollectionLoader/Breadcrumb/style.less new file mode 100644 index 000000000..33d611a1c --- /dev/null +++ b/packages/app/src/components/pages/CollectionLoader/Breadcrumb/style.less @@ -0,0 +1,28 @@ +.collection-item { + .breadcrumb { + display: flex; + position: absolute; + width: 100%; + padding: 12px 14px 0; + z-index: 4; + &-item { + vertical-align: middle; + color: rgba(0,0,0,.45); + &::after { + content: "/"; + color: rgba(0,0,0,.45); + display: inline-block; + margin: 0 8px; + } + &:last-child::after { + display: none; + } + a { + color: rgba(0,0,0,.45); + } + span { + color: rgba(0,0,0,.45); + } + } + } +} diff --git a/packages/app/src/components/pages/CollectionLoader/CollectionIndex.tsx b/packages/app/src/components/pages/CollectionLoader/CollectionIndex.tsx new file mode 100644 index 000000000..ff1f6eb74 --- /dev/null +++ b/packages/app/src/components/pages/CollectionLoader/CollectionIndex.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import ViewFactory from '@/components/views'; +import { PageHeader, Tabs, Button, Statistic, Descriptions } from 'antd'; +import { useRequest, request, Spin } from '@nocobase/client'; +import { getPathName } from './utils'; + +export function CollectionIndex(props) { + const { viewName, collection } = props.match.params; + const { title, defaultViewName } = props.collection; + + return ( +
+ Operation, + // , + // , + ]} + // footer={ + // + // + // + // + // } + /> +
+ +
+
+ ); +} + +export default CollectionIndex; diff --git a/packages/app/src/components/pages/CollectionLoader/CollectionSingle.tsx b/packages/app/src/components/pages/CollectionLoader/CollectionSingle.tsx new file mode 100644 index 000000000..6e3b7d46a --- /dev/null +++ b/packages/app/src/components/pages/CollectionLoader/CollectionSingle.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { PageHeader, Tabs, Button, Statistic, Descriptions } from 'antd'; +import { CollectionTabPane } from './CollectionTabPane'; +import { getPathName, redirectTo } from './utils'; + +export function CollectionSingle(props) { + console.log(props); + const { item = {} } = props; + const { tabs = [] } = props.collection; + const activeTab = tabs.find(tab => tab.name == item.tabName)||{}; + if (!activeTab) { + return null; + } + return ( +
+ { + redirectTo({ + ...props.match.params, + removeLastItem: true, + }); + }} + title={'企业信息库'} + // subTitle="This is a subtitle" + extra={[ + // , + // , + // , + ]} + footer={ + { + redirectTo({ + ...props.match.params, + lastItem: { + tabName: activeKey, + }, + }); + }} + > + {tabs.map(tab => )} + + } + /> +
+ +
+
+ ); +} + +export default CollectionSingle; diff --git a/packages/app/src/components/pages/CollectionLoader/CollectionTabPane.tsx b/packages/app/src/components/pages/CollectionLoader/CollectionTabPane.tsx new file mode 100644 index 000000000..5d48fbc89 --- /dev/null +++ b/packages/app/src/components/pages/CollectionLoader/CollectionTabPane.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import ViewFactory from '@/components/views'; +import { PageHeader, Tabs, Button, Statistic, Descriptions } from 'antd'; +import { useRequest, request, Spin } from '@nocobase/client'; + +export function CollectionTabPane(props) { + const { activeTab = {}, item = {} } = props; + const { viewCollectionName, viewName, association, collection_name } = activeTab; + + const params = {}; + + if (association) { + params['resourceName'] = association; + params['associatedName'] = collection_name; + params['associatedKey'] = item.itemId; + } else { + params['resourceName'] = collection_name; + params['resourceKey'] = item.itemId; + } + + return ( +
+ +
+ ); +} + +export default CollectionTabPane; diff --git a/packages/app/src/components/pages/CollectionLoader/index.tsx b/packages/app/src/components/pages/CollectionLoader/index.tsx new file mode 100644 index 000000000..1dd0382d2 --- /dev/null +++ b/packages/app/src/components/pages/CollectionLoader/index.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import Breadcrumb from './Breadcrumb'; +import CollectionIndex from './CollectionIndex'; +import CollectionSingle from './CollectionSingle'; +import './style.less'; +import { useRequest, request, Spin } from '@nocobase/client'; + +export function CollectionLoader(props: any) { + let { path, pagepath, collection } = props.match.params; + if (path.startsWith(pagepath)) { + path = path.substring(pagepath.length); + } + let matches: any; + matches = /\/views\/([^/]+)/.exec(path); + if (matches) { + props.match.params['viewName'] = matches[1]; + path = path.substring(`/views/${matches[1]}`.length); + } + const re = /\/items\/([^/]+)\/tabs\/([^/]+)/g; + matches = [...path.matchAll(re)]; + let items = matches.map((match, index) => ({ + itemId: match[1], + tabName: match[2], + })); + props.match.params['items'] = items; + console.log(props.match, path); + const { data = {}, error, loading, run } = useRequest(() => request(`/${collection}:getCollection`)); + + if (loading) { + return ; + } + + return ( +
+
+ +
+ {items.length > 0 && ( +
+ {/* + {items.map(item => )} + */} + {items.map(item => { + return ( +
+ +
+ ); + })} +
+ )} +
+ ); +} + +export default CollectionLoader; diff --git a/packages/app/src/components/pages/CollectionLoader/style.less b/packages/app/src/components/pages/CollectionLoader/style.less new file mode 100644 index 000000000..c69b5e9ef --- /dev/null +++ b/packages/app/src/components/pages/CollectionLoader/style.less @@ -0,0 +1,42 @@ +.collection { + position: relative; + height: calc(100vh - 48px); + overflow: hidden; +} + +.collection-index { + position: absolute; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + z-index: 1; + background: #f0f2f5; +} + +.collection-item { + position: absolute; + top: 0; + width: 100%; + height: 100%; + overflow: hidden; + z-index: 4; + background: #f0f2f5; +} + +.collection-single { + position: absolute; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background: #f0f2f5; +} + +.collection-content { + margin: 24px; +} + +// .ant-page-header.has-footer { +// padding-top: 50px; +// } \ No newline at end of file diff --git a/packages/app/src/components/pages/CollectionLoader/utils.ts b/packages/app/src/components/pages/CollectionLoader/utils.ts new file mode 100644 index 000000000..4e06d1f77 --- /dev/null +++ b/packages/app/src/components/pages/CollectionLoader/utils.ts @@ -0,0 +1,34 @@ +import cloneDeep from 'lodash/cloneDeep'; +import { history } from 'umi'; + +export function getPathName(params) { + const { pagepath, viewName, removeLastItem, items = [], lastItem = {}, newItem = {} } = cloneDeep(params); + let path = `/${pagepath}`; + if (viewName) { + path += `/views/${viewName}`; + } + let last = items.pop(); + path += items.map(item => `/items/${item.itemId}/tabs/${item.tabName}`).join('/'); + if (removeLastItem) { + return path; + } + last = {...last, ...lastItem}; + if (typeof last.itemId !== 'undefined') { + path += `/items/${last.itemId}/tabs/${last.tabName}`; + } + if (typeof newItem.itemId !== 'undefined') { + path += `/items/${newItem.itemId}/tabs/${newItem.tabName}`; + } + return path; +} + +/** + * 跳转 + * + * @param params + * @param item + */ +export function redirectTo(params = {}) { + const pathname = getPathName(params); + history.push(pathname); +} diff --git a/packages/app/src/components/pages/PageLoader.tsx b/packages/app/src/components/pages/PageLoader.tsx new file mode 100644 index 000000000..897ba1b16 --- /dev/null +++ b/packages/app/src/components/pages/PageLoader.tsx @@ -0,0 +1,27 @@ +import React, { useState } from 'react'; +// import { request } from 'umi'; +import { TemplateLoader } from './TemplateLoader'; +import { useRequest, request } from '@nocobase/client'; +import templates from '@/templates'; + +export function PageLoader(props: any) { + const { path } = props.match.params; + const { data = {}, error, loading, run } = useRequest(() => request('/pages:getRoutes')); + const [first, setFirst] = useState(true); + (window as any).routesReload = async () => { + setFirst(false); + await run(); + }; + console.log(data); + return ( + + ); +} + +export default PageLoader; diff --git a/packages/app/src/components/pages/SideMenuLayout/index.tsx b/packages/app/src/components/pages/SideMenuLayout/index.tsx new file mode 100644 index 000000000..f77e0ca4a --- /dev/null +++ b/packages/app/src/components/pages/SideMenuLayout/index.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Layout, Menu, Breadcrumb } from 'antd'; +import { Link } from 'umi'; +import './style.less'; + +export function SideMenuLayout(props: any) { + const { menu = [] } = props.page; + console.log(menu); + return ( + + + + {menu.map(item => ( + + {item.title} + + ))} + + + + {props.children} + + + ); +}; + +export default SideMenuLayout; diff --git a/packages/app/src/components/pages/SideMenuLayout/style.less b/packages/app/src/components/pages/SideMenuLayout/style.less new file mode 100644 index 000000000..ed412ed2e --- /dev/null +++ b/packages/app/src/components/pages/SideMenuLayout/style.less @@ -0,0 +1,9 @@ +.nb-sider { + position: relative; + left: 0; + z-index: 100; + box-shadow: 2px 0 8px 0 rgba(29,35,41,.05); + .ant-menu-light { + border-right-color: transparent !important; + } +} \ No newline at end of file diff --git a/packages/app/src/components/pages/TemplateLoader.tsx b/packages/app/src/components/pages/TemplateLoader.tsx new file mode 100644 index 000000000..fe627da83 --- /dev/null +++ b/packages/app/src/components/pages/TemplateLoader.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { Redirect } from 'umi'; +import get from 'lodash/get'; +import { Spin } from '@nocobase/client'; +import { CollectionLoader } from './CollectionLoader'; + +function getRoutes(path: string, pages: any) { + const keys = path.split('/'); + const routes: Array = []; + while(keys.length) { + const uri = keys.join('/'); + if (pages[uri]) { + routes.push({...pages[uri], path: uri}); + } + keys.pop(); + } + if (path && pages['/']) { + routes.push({...pages['/'], path: '/'}); + } + return routes; +} + +export function TemplateLoader(props: any) { + const { loading, pathname, pages, templates } = props; + if (loading) { + return ; + } + const redirect = get(pages, [pathname, 'redirect']); + if (redirect) { + return + } + const routes = getRoutes(pathname, pages); + let component: any; + console.log(templates); + const len = routes.length; + const componentProps = {...props}; + while(routes.length) { + const route = routes.shift(); + console.log(route.template); + // const tmp = route.template.replace(/^@\/pages\//g, ''); + let Component: any; + // const Component = route.type === 'collection' + // ? CollectionLoader + // : require(`@/pages/${tmp}`).default; + if (route.type === 'collection') { + Component = CollectionLoader; + componentProps.match.params['collection'] = route.collection; + } else { + Component = templates[route.template]; + } + if (len === routes.length+1) { + componentProps.match.params['pagepath'] = route.path.replace(/^\//g, ''); + componentProps['lastPage'] = route; + } + componentProps['page'] = route; + component = {component}; + if (route.inherit === false) { + break; + } + } + return component; +} + +export default TemplateLoader; diff --git a/packages/app/src/components/pages/TopMenuLayout/index.tsx b/packages/app/src/components/pages/TopMenuLayout/index.tsx new file mode 100644 index 000000000..1747b62e7 --- /dev/null +++ b/packages/app/src/components/pages/TopMenuLayout/index.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Layout, Menu } from 'antd'; +import { Link } from 'umi'; +import './style.less'; + +export function TopMenuLayout(props: any) { + const { menu = [] } = props.page; + return ( + + +
+ + {menu.map(item => ( + + {item.title} + + ))} + + + + {props.children} + + + ); +}; + +export default TopMenuLayout; diff --git a/packages/ui/src/table/index.tsx b/packages/app/src/components/pages/TopMenuLayout/style.less similarity index 100% rename from packages/ui/src/table/index.tsx rename to packages/app/src/components/pages/TopMenuLayout/style.less diff --git a/packages/app/src/components/pages/index.tsx b/packages/app/src/components/pages/index.tsx new file mode 100644 index 000000000..6a5caa7fe --- /dev/null +++ b/packages/app/src/components/pages/index.tsx @@ -0,0 +1,6 @@ +export { PageLoader } from './PageLoader'; +export { TemplateLoader } from './TemplateLoader'; +export { CollectionLoader } from './CollectionLoader'; +export { PageLoader as default } from './PageLoader'; +export { SideMenuLayout } from './SideMenuLayout/index'; +export { TopMenuLayout } from './TopMenuLayout/index'; diff --git a/packages/app/src/components/views/Form/DrawerForm.tsx b/packages/app/src/components/views/Form/DrawerForm.tsx new file mode 100644 index 000000000..6519f2135 --- /dev/null +++ b/packages/app/src/components/views/Form/DrawerForm.tsx @@ -0,0 +1,75 @@ +import React, { useState, useEffect, useImperativeHandle, forwardRef, useRef } from 'react'; +import { Button, Drawer } from 'antd'; +import { Tooltip, Input } from 'antd'; +import { + SchemaForm, + SchemaMarkupField as Field, + createFormActions, + createAsyncFormActions, + Submit, + Reset, + FormButtonGroup, + registerFormFields, + FormValidator, + setValidationLanguage, +} from '@formily/antd'; +import { QuestionCircleOutlined } from '@ant-design/icons'; + +export const DrawerForm = forwardRef((props: any, ref) => { + console.log(props); + const [visible, setVisible] = useState(false); + useImperativeHandle(ref, () => ({ + setVisible, + })); + const actions = createAsyncFormActions(); + const { title } = props.schema||{}; + return ( + { + setVisible(false); + }} + title={title} + footer={[ + + ]} + > + + + + ); + }, + }} + > + + + ); +}); diff --git a/packages/app/src/components/views/Form/Form.tsx b/packages/app/src/components/views/Form/Form.tsx new file mode 100644 index 000000000..a0a573123 --- /dev/null +++ b/packages/app/src/components/views/Form/Form.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { Tooltip, Card } from 'antd'; +import { + SchemaForm, + SchemaMarkupField as Field, + createFormActions, + createAsyncFormActions, + Submit, + Reset, + FormButtonGroup, + registerFormFields, + FormValidator, + setValidationLanguage, +} from '@formily/antd'; +import { QuestionCircleOutlined } from '@ant-design/icons'; + +export function Form(props: any) { + const actions = createAsyncFormActions(); + + return ( + + + + + ); + }, + }} + > + + + + + + ); +} diff --git a/packages/app/src/components/views/Form/index.tsx b/packages/app/src/components/views/Form/index.tsx new file mode 100644 index 000000000..2a6832f39 --- /dev/null +++ b/packages/app/src/components/views/Form/index.tsx @@ -0,0 +1,20 @@ +import { setup } from '@/components/form.fields'; +import { QuestionCircleOutlined } from '@ant-design/icons'; +import { + SchemaForm, + SchemaMarkupField as Field, + createFormActions, + createAsyncFormActions, + Submit, + Reset, + FormButtonGroup, + registerFormFields, + FormValidator, + setValidationLanguage, +} from '@formily/antd'; + +setup(); +setValidationLanguage('zh-CN'); + +export { Form } from './Form'; +export { DrawerForm } from './DrawerForm'; diff --git a/packages/app/src/components/views/SimpleTable.tsx b/packages/app/src/components/views/SimpleTable.tsx new file mode 100644 index 000000000..3798d661f --- /dev/null +++ b/packages/app/src/components/views/SimpleTable.tsx @@ -0,0 +1,51 @@ +import React, { useRef } from 'react'; +import { Table as AntdTable, Card } from 'antd'; +import { redirectTo } from '@/components/pages/CollectionLoader/utils'; +import { Actions } from '@/components/actions'; +import ViewFactory from '@/components/views'; + +const dataSource = []; +for (let i = 0; i < 46; i++) { + dataSource.push({ + id: i, + name: `Edward King ${i}`, + age: 32, + address: `London, Park Lane no. ${i}`, + }); +} + +const columns = [ + { + title: '姓名', + dataIndex: 'name', + key: 'name', + }, + { + title: '年龄', + dataIndex: 'age', + key: 'age', + }, + { + title: '住址', + dataIndex: 'address', + key: 'address', + }, +]; + +export function SimpleTable(props: any) { + console.log(props); + const { activeTab, schema } = props; + const { viewCollectionName, rowViewName, actions = [] } = schema; + const drawerRef = useRef(); + return ( + + + + ({ + onClick: () => { + drawerRef.current.setVisible(true); + }, + })} columns={columns} /> + + ); +} diff --git a/packages/app/src/components/views/details.tsx b/packages/app/src/components/views/details.tsx new file mode 100644 index 000000000..9b5b947b1 --- /dev/null +++ b/packages/app/src/components/views/details.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { Card, Descriptions, Button } from 'antd'; +import { Actions } from '@/components/actions'; + +export function Details(props: any) { + console.log(props); + const { actions = [], fields = [] } = props.schema; + return ( + + + + Cloud Database + Prepaid + YES + 2018-04-24 18:00:00 + + 2019-04-24 18:00:00 + + $80.00 + $20.00 + $60.00 + + Data disk type: MongoDB +
+ Database version: 3.4 +
+ Package: dds.mongo.mid +
+ Storage space: 10 GB +
+ Replication factor: 3 +
+ Region: East China 1
+
+
+
+ ); +} diff --git a/packages/app/src/components/views/index.tsx b/packages/app/src/components/views/index.tsx new file mode 100644 index 000000000..6b28bb62d --- /dev/null +++ b/packages/app/src/components/views/index.tsx @@ -0,0 +1,37 @@ + +import React from 'react'; +import { Form, DrawerForm } from './Form/index'; +import { Table } from './Table'; +import { Details } from './Details'; +import { useRequest, request, Spin } from '@nocobase/client'; +import { SimpleTable } from './SimpleTable'; + +const TEMPLATES = new Map(); + +export function registerView(template: string, Template: any) { + TEMPLATES.set(template, Template); +} + +export function getViewTemplate(template: string) { + return TEMPLATES.get(template); +} + +registerView('DrawerForm', DrawerForm); +registerView('PermissionForm', DrawerForm); +registerView('Form', Form); +registerView('Table', Table); +registerView('SimpleTable', SimpleTable); +registerView('Details', Details); + +export default function ViewFactory(props) { + const { viewCollectionName, viewName, reference } = props; + const { data = {}, error, loading, run } = useRequest(() => request(`/${viewCollectionName}:getView/${viewName}`), { + refreshDeps: [viewCollectionName, viewName], + }); + if (loading) { + return ; + } + const { template } = data.data; + const Template = getViewTemplate(template); + return Template &&