From d9e6d2e6148ebb5be3c9bbd84cf045bc6e84ce28 Mon Sep 17 00:00:00 2001 From: chenos Date: Sun, 29 Nov 2020 16:26:53 +0800 Subject: [PATCH] feat(users): add users module (#26) * feat(users): add users module * user check --- packages/app/src/api/index.ts | 41 ++++-- packages/app/src/app.ts | 35 ++++- packages/app/src/components/views/Details.tsx | 8 +- .../src/components/views/Form/DrawerForm.tsx | 3 +- .../app/src/components/views/Form/Login.tsx | 74 +++++++++++ .../src/components/views/Form/Register.tsx | 125 ++++++++++++++++++ .../app/src/components/views/Form/index.tsx | 1 + .../app/src/components/views/Form/style.less | 5 + .../app/src/components/views/SimpleTable.tsx | 2 +- packages/app/src/components/views/Table.tsx | 2 +- packages/app/src/components/views/index.tsx | 5 + packages/app/src/components/views/style.less | 13 ++ packages/app/src/pages/login.tsx | 16 +++ packages/app/src/pages/register.tsx | 16 +++ packages/app/src/templates.tsx | 2 + packages/plugin-users/package.json | 2 + packages/plugin-users/src/actions/check.ts | 6 + packages/plugin-users/src/actions/login.ts | 28 ++++ packages/plugin-users/src/actions/logout.ts | 6 + packages/plugin-users/src/actions/register.ts | 11 ++ .../plugin-users/src/collections/users.ts | 67 ++++++++++ packages/plugin-users/src/resources/users.ts | 32 +++++ packages/plugin-users/src/server.ts | 15 +++ 23 files changed, 500 insertions(+), 15 deletions(-) create mode 100644 packages/app/src/components/views/Form/Login.tsx create mode 100644 packages/app/src/components/views/Form/Register.tsx create mode 100644 packages/app/src/components/views/Form/style.less create mode 100644 packages/app/src/components/views/style.less create mode 100644 packages/app/src/pages/login.tsx create mode 100644 packages/app/src/pages/register.tsx create mode 100644 packages/plugin-users/src/actions/check.ts create mode 100644 packages/plugin-users/src/actions/login.ts create mode 100644 packages/plugin-users/src/actions/logout.ts create mode 100644 packages/plugin-users/src/actions/register.ts create mode 100644 packages/plugin-users/src/resources/users.ts diff --git a/packages/app/src/api/index.ts b/packages/app/src/api/index.ts index 6c06106ff..1f6a1045f 100644 --- a/packages/app/src/api/index.ts +++ b/packages/app/src/api/index.ts @@ -131,14 +131,14 @@ const data = { icon: 'dashboard', sort: 110, }, - { - title: '权限配置', - type: 'collection', - collection: 'roles', - path: '/settings/roles', - icon: 'dashboard', - sort: 120, - }, + // { + // title: '权限配置', + // type: 'collection', + // collection: 'roles', + // path: '/settings/roles', + // icon: 'dashboard', + // sort: 120, + // }, ] }, ], @@ -311,6 +311,31 @@ const data = { // return await collection.modelInit(); // })); + await Page.create({ + title: '登录页面', + path: '/login', + type: 'page', + inherit: false, + template: 'login', + order: 120, + }); + + await Page.create({ + title: '注册页面', + path: '/register', + type: 'page', + inherit: false, + template: 'register', + order: 130, + }); + + await database.getModel('users').create({ + nickname: "admin", + password: "admin", + username: "admin", + token: "38979f07e1fca68fb3d2", + }); + 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 index 09fa7ca70..7222e466f 100644 --- a/packages/app/src/app.ts +++ b/packages/app/src/app.ts @@ -21,4 +21,37 @@ export const request: RequestConfig = { await next(); } ], -}; \ No newline at end of file +}; + +export async function getInitialState() { + const { pathname, search } = location; + console.log(location); + let redirect = ''; + // if (href.includes('?')) { + redirect = `?redirect=${pathname}${search}`; + // } + + if (pathname !== '/login' && pathname !== '/register') { + try { + const { data = {} } = await umiRequest('/users:check', { + method: 'post', + }); + + if (!data.id) { + history.push('/login' + redirect); + return { + currentUser: {}, + }; + } + + return { + currentUser: data, + }; + } catch (error) { + console.log(error) + history.push('/login' + redirect); + } + } + + return {}; +} \ No newline at end of file diff --git a/packages/app/src/components/views/Details.tsx b/packages/app/src/components/views/Details.tsx index 7d047db70..4e910c67e 100644 --- a/packages/app/src/components/views/Details.tsx +++ b/packages/app/src/components/views/Details.tsx @@ -29,9 +29,11 @@ export function Details(props: any) { {loading ? : ( - {fields.map((field: any) => ( - {data[field.name]} - ))} + {fields.map((field: any) => { + return ( + {JSON.stringify(data[field.name])} + ) + })} )} diff --git a/packages/app/src/components/views/Form/DrawerForm.tsx b/packages/app/src/components/views/Form/DrawerForm.tsx index f31cbaf8d..30a5b8b4b 100644 --- a/packages/app/src/components/views/Form/DrawerForm.tsx +++ b/packages/app/src/components/views/Form/DrawerForm.tsx @@ -57,7 +57,8 @@ export const DrawerForm = forwardRef((props: any, ref) => { title={title} footer={[ ]} > diff --git a/packages/app/src/components/views/Form/Login.tsx b/packages/app/src/components/views/Form/Login.tsx new file mode 100644 index 000000000..6464ea898 --- /dev/null +++ b/packages/app/src/components/views/Form/Login.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { Tooltip, Card, Button } from 'antd'; +import { + SchemaForm, + SchemaMarkupField as Field, + createFormActions, + createAsyncFormActions, + Submit, + Reset, + FormButtonGroup, + registerFormFields, + FormValidator, + setValidationLanguage, +} from '@formily/antd'; +import { QuestionCircleOutlined } from '@ant-design/icons'; +import { Link, history, request, useModel, useLocation } from 'umi'; + +export function Login(props: any) { + const actions = createAsyncFormActions(); + const { initialState = {}, loading, error, refresh, setInitialState } = useModel('@@initialState'); + const { redirect } = props.location.query; + + return ( +
+

NocoBase

+

登录

+ { + console.log(values); + const { data = {} } = await request('/users:login', { + method: 'post', + data: values, + }); + if (data.data && data.data.token) { + localStorage.setItem('NOCOBASE_TOKEN', data.data.token); + setInitialState({currentUser :data.data}); + await (window as any).routesReload(); + history.push(redirect||'/'); + } + }} actions={actions} schema={{ + type: 'object', + properties: { + username: { + type: 'string', + title: '', + required: true, + 'x-component-props': { + size: 'large', + placeholder: '用户名', + } + }, + password: { + type: 'password', + title: '', + required: true, + 'x-component-props': { + size: 'large', + style: { + width: '100%', + }, + placeholder: '密码', + } + }, + } + }}> + + 登录 + + + +
+ ); +} diff --git a/packages/app/src/components/views/Form/Register.tsx b/packages/app/src/components/views/Form/Register.tsx new file mode 100644 index 000000000..b27481941 --- /dev/null +++ b/packages/app/src/components/views/Form/Register.tsx @@ -0,0 +1,125 @@ +import React from 'react'; +import { Tooltip, Card, Button, message } from 'antd'; +import { + SchemaForm, + SchemaMarkupField as Field, + createFormActions, + createAsyncFormActions, + Submit, + Reset, + FormButtonGroup, + registerFormFields, + FormEffectHooks, + FormValidator, + setValidationLanguage, +} from '@formily/antd'; +import { QuestionCircleOutlined } from '@ant-design/icons'; +import { Link, history, request, useModel } from 'umi'; + +const { onFieldValueChange$ } = FormEffectHooks + +const useLinkageValidateEffects = () => { + const { setFieldState, getFieldState } = createFormActions() + onFieldValueChange$('*(password,confirm)').subscribe(fieldState => { + const selfName = fieldState.name + const selfValue = fieldState.value + const otherName = selfName == 'password' ? 'confirm' : 'password' + const otherValue = getFieldState(otherName, state => state.value) + setFieldState(otherName, state => { + if (selfValue && otherValue && selfValue !== otherValue) { + state.errors = ['两次密码输入不一致'] + } else { + state.errors = [] + } + }) + setFieldState(selfName, state => { + if (selfValue && otherValue && selfValue !== otherValue) { + state.errors = ['两次密码输入不一致'] + } else { + state.errors = [] + } + }) + }) +} + +export function Register(props: any) { + const actions = createFormActions(); + + return ( +
+

NocoBase

+

注册

+ { + useLinkageValidateEffects() + }} + onSubmit={async (values) => { + console.log(values); + const { data = {} } = await request('/users:register', { + method: 'post', + data: values, + }); + await actions.reset({ + validate: false, + forceClear: true, + }); + message.success('注册成功,将跳转登录页'); + setTimeout(() => { + history.push('/login'); + }, 1000); + }} actions={actions} schema={{ + type: 'object', + properties: { + username: { + type: 'string', + title: '', + required: true, + 'x-component-props': { + size: 'large', + placeholder: '用户名', + } + }, + nickname: { + type: 'string', + title: '', + 'x-component-props': { + size: 'large', + placeholder: '昵称', + } + }, + password: { + type: 'password', + title: '', + required: true, + 'x-component-props': { + size: 'large', + style: { + width: '100%', + }, + placeholder: '密码', + } + }, + confirm: { + type: 'password', + title: '', + required: true, + 'x-component-props': { + size: 'large', + style: { + width: '100%', + }, + placeholder: '确认密码', + } + }, + } + }}> + + 注册 + + + +
+ ); +} diff --git a/packages/app/src/components/views/Form/index.tsx b/packages/app/src/components/views/Form/index.tsx index 2a6832f39..8bb51f3a1 100644 --- a/packages/app/src/components/views/Form/index.tsx +++ b/packages/app/src/components/views/Form/index.tsx @@ -12,6 +12,7 @@ import { FormValidator, setValidationLanguage, } from '@formily/antd'; +import './style.less'; setup(); setValidationLanguage('zh-CN'); diff --git a/packages/app/src/components/views/Form/style.less b/packages/app/src/components/views/Form/style.less new file mode 100644 index 000000000..f9bde90a5 --- /dev/null +++ b/packages/app/src/components/views/Form/style.less @@ -0,0 +1,5 @@ +.users-form { + max-width: 368px; + margin: 0 auto; + padding-top: 100px; +} \ No newline at end of file diff --git a/packages/app/src/components/views/SimpleTable.tsx b/packages/app/src/components/views/SimpleTable.tsx index f6202c1ce..b464695eb 100644 --- a/packages/app/src/components/views/SimpleTable.tsx +++ b/packages/app/src/components/views/SimpleTable.tsx @@ -86,7 +86,7 @@ export function SimpleTable(props: SimpleTableProps) { {...tableProps} /> {paginated && ( -
+
)} diff --git a/packages/app/src/components/views/Table.tsx b/packages/app/src/components/views/Table.tsx index 96582ff5a..56f6448a8 100644 --- a/packages/app/src/components/views/Table.tsx +++ b/packages/app/src/components/views/Table.tsx @@ -102,7 +102,7 @@ export function Table(props: TableProps) { {...tableProps} /> {paginated && ( -
+
)} diff --git a/packages/app/src/components/views/index.tsx b/packages/app/src/components/views/index.tsx index 2850e07bf..eecd1bb6a 100644 --- a/packages/app/src/components/views/index.tsx +++ b/packages/app/src/components/views/index.tsx @@ -7,6 +7,9 @@ import { SimpleTable } from './SimpleTable'; import { Table } from './Table'; import { Form, DrawerForm } from './Form/index'; import { Details } from './Details'; +import './style.less'; +import { Login } from './Form/Login'; +import { Register } from './Form/Register'; const TEMPLATES = new Map(); @@ -24,6 +27,8 @@ registerView('Form', Form); registerView('Table', Table); registerView('SimpleTable', SimpleTable); registerView('Details', Details); +registerView('Login', Login); +registerView('Register', Register); export interface ViewProps { resourceName: string; diff --git a/packages/app/src/components/views/style.less b/packages/app/src/components/views/style.less new file mode 100644 index 000000000..e5ffb9cf1 --- /dev/null +++ b/packages/app/src/components/views/style.less @@ -0,0 +1,13 @@ +.ant-table-cell:not(.ant-table-selection-column) { + min-width: 100px; + white-space: nowrap; +} +.ant-table-wrapper { + overflow: auto; +} +.table-pagination { + margin-top: 24px; + ul { + float: right; + } +} \ No newline at end of file diff --git a/packages/app/src/pages/login.tsx b/packages/app/src/pages/login.tsx new file mode 100644 index 000000000..6f7d0eee7 --- /dev/null +++ b/packages/app/src/pages/login.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { Link } from 'umi'; +import { Page } from '@/components/pages'; +import ViewFactory from '@/components/views'; + +export default (props: any) => { + return ( +
+ +
+ ); +}; \ No newline at end of file diff --git a/packages/app/src/pages/register.tsx b/packages/app/src/pages/register.tsx new file mode 100644 index 000000000..3cbb41744 --- /dev/null +++ b/packages/app/src/pages/register.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { Link } from 'umi'; +import { Page } from '@/components/pages'; +import ViewFactory from '@/components/views'; + +export default (props: any) => { + return ( +
+ +
+ ); +}; \ No newline at end of file diff --git a/packages/app/src/templates.tsx b/packages/app/src/templates.tsx index f4c70232b..fbbf16b32 100644 --- a/packages/app/src/templates.tsx +++ b/packages/app/src/templates.tsx @@ -10,4 +10,6 @@ export default { 'page2': require('@/pages/page2').default, 'page3': require('@/pages/page3').default, 'page4': require('@/pages/page4').default, + login: require('@/pages/login').default, + register: require('@/pages/register').default, }; diff --git a/packages/plugin-users/package.json b/packages/plugin-users/package.json index 96e24fb82..11bc4de47 100644 --- a/packages/plugin-users/package.json +++ b/packages/plugin-users/package.json @@ -7,6 +7,8 @@ "umi": "^3.2.23" }, "devDependencies": { + "@nocobase/actions": "^0.3.0-alpha.0", + "crypto-random-string": "^3.3.0", "umi": "^3.2.23" }, "dependencies": { diff --git a/packages/plugin-users/src/actions/check.ts b/packages/plugin-users/src/actions/check.ts new file mode 100644 index 000000000..e8b32d307 --- /dev/null +++ b/packages/plugin-users/src/actions/check.ts @@ -0,0 +1,6 @@ +import { actions } from '@nocobase/actions'; + +export default async (ctx: actions.Context, next: actions.Next) => { + ctx.body = ctx.state.currentUser; + await next(); +} diff --git a/packages/plugin-users/src/actions/login.ts b/packages/plugin-users/src/actions/login.ts new file mode 100644 index 000000000..3864e3ec6 --- /dev/null +++ b/packages/plugin-users/src/actions/login.ts @@ -0,0 +1,28 @@ +import { actions } from '@nocobase/actions'; +import cryptoRandomString from 'crypto-random-string'; +import { Password } from "@nocobase/database"; + +export default async (ctx: actions.Context, next: actions.Next) => { + const { values } = ctx.action.params; + // console.log(values); + const User = ctx.db.getModel('users'); + const user = await User.findOne({ + where: { + username: values.username, + }, + }); + if (!user) { + ctx.throw(401, 'Unauthorized'); + } + if (!Password.verify(values.password, user.password)) { + ctx.throw(401, 'Unauthorized'); + } + if (!user.token) { + user.token = cryptoRandomString({length: 20}); + await user.save(); + } + ctx.body = { + data: user, + }; + await next(); +} \ No newline at end of file diff --git a/packages/plugin-users/src/actions/logout.ts b/packages/plugin-users/src/actions/logout.ts new file mode 100644 index 000000000..13024fb22 --- /dev/null +++ b/packages/plugin-users/src/actions/logout.ts @@ -0,0 +1,6 @@ +import { actions } from '@nocobase/actions'; + +export default async (ctx: actions.Context, next: actions.Next) => { + ctx.body = {}; + await next(); +} \ No newline at end of file diff --git a/packages/plugin-users/src/actions/register.ts b/packages/plugin-users/src/actions/register.ts new file mode 100644 index 000000000..cb9509e3d --- /dev/null +++ b/packages/plugin-users/src/actions/register.ts @@ -0,0 +1,11 @@ +import { actions } from '@nocobase/actions'; + +export default async (ctx: actions.Context, next: actions.Next) => { + const User = ctx.db.getModel('users'); + const { values } = ctx.action.params; + const user = await User.create(values); + ctx.body = { + data: user, + }; + await next(); +} diff --git a/packages/plugin-users/src/collections/users.ts b/packages/plugin-users/src/collections/users.ts index eafeccfd7..70b55c51b 100644 --- a/packages/plugin-users/src/collections/users.ts +++ b/packages/plugin-users/src/collections/users.ts @@ -5,9 +5,64 @@ export default { title: '用户', fields: [ { + interface: 'string', type: 'string', name: 'username', title: '用户名', + unique: true, + component: { + type: 'string', + showInTable: true, + showInDetail: true, + showInForm: true, + }, + }, + { + interface: 'string', + type: 'string', + name: 'nickname', + title: '昵称', + component: { + type: 'string', + showInTable: true, + showInDetail: true, + showInForm: true, + }, + }, + { + interface: 'phone', + type: 'string', + name: 'phone', + unique: true, + title: '手机号', + component: { + type: 'string', + showInTable: true, + showInDetail: true, + showInForm: true, + }, + }, + { + interface: 'password', + type: 'password', + name: 'password', + title: '密码', + hidden: true, + component: { + type: 'password', + showInForm: true, + }, + }, + { + interface: 'password', + type: 'string', + name: 'token', + title: 'Token', + unique: true, + hidden: true, + component: { + type: 'hidden', + }, }, ], actions: [ @@ -41,6 +96,18 @@ export default { title: '表单', template: 'DrawerForm', }, + { + type: 'form', + name: 'login', + title: '登录', + template: 'Login', + }, + { + type: 'form', + name: 'register', + title: '注册', + template: 'Register', + }, { type: 'details', name: 'details', diff --git a/packages/plugin-users/src/resources/users.ts b/packages/plugin-users/src/resources/users.ts new file mode 100644 index 000000000..70160325c --- /dev/null +++ b/packages/plugin-users/src/resources/users.ts @@ -0,0 +1,32 @@ +import { ResourceOptions } from "@nocobase/resourcer"; + +export default { + name: 'users', + middlewares: [ + { + // only: ['check'], + handler: async (ctx, next) => { + const token = ctx.get('Authorization').replace(/^Bearer\s+/gi, ''); + console.log(ctx.action.params.actionName); + const { actionName } = ctx.action.params; + if (actionName !== 'check') { + return next(); + } + if (!token) { + ctx.throw(401, 'Unauthorized'); + } + const User = ctx.db.getModel('users'); + const user = await User.findOne({ + where: { + token, + }, + }); + if (!user) { + ctx.throw(401, 'Unauthorized'); + } + ctx.state.currentUser = user; + await next(); + }, + }, + ], +} as ResourceOptions; diff --git a/packages/plugin-users/src/server.ts b/packages/plugin-users/src/server.ts index 16e788eff..d6099caf7 100644 --- a/packages/plugin-users/src/server.ts +++ b/packages/plugin-users/src/server.ts @@ -1,6 +1,10 @@ import path from 'path'; import Database from '@nocobase/database'; import Resourcer from '@nocobase/resourcer'; +import login from './actions/login'; +import register from './actions/register'; +import logout from './actions/logout'; +import check from './actions/check'; export default async function (options = {}) { const database: Database = this.database; @@ -9,4 +13,15 @@ export default async function (options = {}) { database.import({ directory: path.resolve(__dirname, 'collections'), }); + + resourcer.registerHandlers({ + 'users:login': login, + 'users:register': register, + 'users:logout': logout, + 'users:check': check, + }); + + resourcer.import({ + directory: path.resolve(__dirname, 'resources'), + }); }