本我的项目次要基于Elux+Antd
构建,蕴含React
版本和Vue
版本,旨在提供给大家一个简略根底、开箱即用的后盾管理系统通用模版,次要蕴含运行环境、脚手架、代码格调、根本Layout、状态治理、路由治理、增删改查逻辑、列表、表单等。
为放弃工程简略清新,不便二次开发,只提供根本版式和通用组件,不集成各种目迷五色的组件,须要的时候本人加进去就行了,Antd自身也自带很多组件。
在线预览
http://admin-react-antd.eluxj...
Git仓库
React版本
- github: https://github.com/hiisea/elu...
- gitee: https://gitee.com/hiisea/elux...
Vue版本
- github: https://github.com/hiisea/elu...
- gitee: https://gitee.com/hiisea/elux...
装置办法
- 应用Git命令clone相应的库:
git clone xxx
- 也能够应用Elux提供的命令:
npm create elux@latest 或 yarn create elux
你看得见的UI
- 提供通用的Admin零碎Layout(包含注册、登录、遗记明码等)。
- 动静获取Menu菜单、轮询最新消息等。
- 反对第一次后退溢出,主动回到首页;再次后退则弹出提醒:
您确定要来到本站?
避免用户误操作。\ 提供<DocumentHead>组件,不便在SinglePage中保护document title、keyword、description等,该组件也可用于SSR,例如:
<DocumentHead title={(id?'批改':'新增')+'用户'} />
提供
配置式
查问表单, 还带TS类型验证哦,再也不放心写错字段名:const formItems: SearchFromItems<ListSearchFormData> = [ {name: 'name', label: '用户名', formItem: <Input placeholder="请输出关键字" />}, {name: 'nickname', label: '呢称', formItem: <Input placeholder="请输出呢称" />}, {name: 'status', label: '状态', formItem: <Select placeholder="请抉择用户状态" />}, {name: 'role', label: '角色', formItem: <Select placeholder="请抉择用户状态" />}, {name: 'email', label: 'Email', formItem: <Input placeholder="请输出Email" />},];
- 提供开展与暗藏高级搜寻:开展高级 / 暗藏高级
- 提供跨页选取、从新搜寻后选取、review已选取:跨页选取
提供
配置式
批量操作等性能,如:批量操作const batchActions = { actions: [ {key: 'delete', label: '批量删除', confirm: true}, {key: 'resolved', label: '批量通过', confirm: true}, {key: 'rejected', label: '批量回绝', confirm: true}, ], handler: (item: {key: string}, ids: (string | number)[]) => { if (item.key === 'delete') { deleteItems(ids as string[]); } else if (item.key === 'resolved') { alterItems(ids as string[], {status: Status.审核通过}); } else if (item.key === 'rejected') { alterItems(ids as string[], {status: Status.审核回绝}); } }, };
提供
资源选择器
,并封装成select,可单选、多选、选满主动提交,如:创立文章时,查问并抉择责任编辑<FormItem {...fromDecorators.editors}> <MSelect<MemberListSearch> placeholder="请抉择责任编辑" selectorPathname="/admin/member/list/selector" fixedSearch={{role: Role.责任编辑, status: Status.启用}} limit={[1, 2]} returnArray showSearch ></MSelect></FormItem>
- 提供收藏夹书签性能,用其代替Page选项卡,操作更灵便。点击左上角【+珍藏】试试...
- 提供页内刷新性能。点击右上角【刷新按钮】试试...
虚构Window
- 路由跳转时能够在新的虚构窗口中关上,相似于target='_blank',然而虚构Window哦,如:新窗口关上 / 本窗口关上
- 窗口中能够再开新窗口,最多可达10级
- 弹窗再弹弹窗体验不好?多层弹窗时自动隐藏上层弹窗,敞开下层弹窗主动复原上层弹窗,保障每一时刻始终之会呈现一层弹窗
- 实现真正意义上的Window(非简略的Dialog),每个窗口不仅领有独立的Dom、状态治理Store、还主动保护独立的
历史记录栈
- 提供窗口工具条:后退、刷新、敞开,如:文章列表 => 点击题目 => 点击作者 => 点击文章数。而后你能够顺次回退每一步操作,也可一次性全副敞开。
- 提供窗口最大化、最小化按钮,如:文章详情,窗口左上角按钮;并反对默认最大化,如:创立文章
- 窗口能够通过Url发送,如将
http://admin-react-antd.eluxjs.com/admin/member/item/edit/50?__c=_dialog
发送给好友后,其能够通过Url还原窗口。 - 实现
keep-alive
。keep-alive长处是用户体验好,毛病是太占资源(须要缓存所有Dom元素还有相干内存变量),当初应用虚构Windw,你想keep-alive就在新窗口中关上,不想keep-alive就在原窗口中关上,敞开窗口就主动销毁keep-alive
基于形象的增删改查逻辑:
- 业务逻辑通过类的继承复用,如果是规范的增删改查基本上
不必写代码
,否则能够本人笼罩父类中的某些办法:
export class Model extends BaseResource<MemberResource> { protected api = api; protected defaultListSearch = defaultListSearch;}
- UI逻辑通过
Hooks
复用。 - 将视图形象成为2大类:列表(List)和单条(Item),抽取其共性。
在此基础上引入视图
渲染器(Render)
概念,类别名+渲染器=具体某个业务视图,如:- type=list,render=maintain(列表+保护),如:/admin/member/list/maintain
- type=list,render=index(列表+展现),如:/admin/article/list/index
- type=list,render=selector(列表+抉择),如:/admin/member/list/selector
- type=item,render=detail(单条+展现),如:/admin/member/item/detail/49
- type=item,render=edit(单条+编辑),如:/admin/member/item/edit/49
- 业务逻辑通过类的继承复用,如果是规范的增删改查基本上
你看不见的幕后
应用微模块架构,将业务性能封装成独立微模块,想要哪个性能就装置哪个模块,是一种粒度更细的微前端
你以前的SRC长这样??? │ ├─ src │ ├─ api # API接口治理 │ ├─ assets # 动态资源文件 │ ├─ components # 全局组件 │ ├─ config # 全局配置项 │ ├─ directives # 全局指令文件 │ ├─ enums # 我的项目枚举 │ ├─ hooks # 罕用 Hooks │ ├─ language # 语言国际化 │ ├─ layout # 框架布局 │ ├─ routers # 路由治理 │ ├─ store # store │ ├─ styles # 全局款式 │ ├─ typings # 全局 ts 申明 │ ├─ utils # 工具库 │ ├─ views # 我的项目所有页面 │ ├─ App.vue # 入口页面 │ └─ main.ts # 入口文件
快来援救你的SRC,
应用微模块后SRC长这样!!! │ ├─ src │ ├─ moddules # 各业务微模块 │ │ ├─ user │ │ ├─ article │ │ ├─ comment │ ├─ Project.vue # 各微模块聚合配置 │ └─ index.ts # 入口文件
- 微模块反对同步/异步加载
- 微模块反对本地目录、反对公布成NPM包,反对独立部署(微前端)
- 微模块反对整体TS类型验证与提醒
内置最强状态治理框架(^-^):
- 同时反对
React/Vue
的状态治理框架。 - 最大水平简化action和store的写法
export class Model extends BaseMode { @reducer //相似Vuex的mutations public putCurUser(curUser: CurUser) { this.state.curUser = curUser; // vue中可间接批改 //this.state = {...this.state, curUser} react中 } @effect() //相似Vuex的action public async login(args: LoginParams) { const curUser = await api.login(args); this.dispatch(this.actions.putCurUser(curUser)); this.getRouter().relaunch({url: AdminHomeUrl}, 'window'); }}
- 与路由联合,反对Store多实例。
- 路由跳转时主动清理Store,再也不必放心State有限累积。
- 为action引入线程机制,反对在解决action的过程中,在派生出新的action线程。
- action执行中反对异步操作:
@effect()public async updateItem(id: string, data: UpdateItem) { await this.api.updateItem!({id, data}); //调用后盾API await this.getRouter().back(1, 'window'); //路由后退一步(到列表页) message.success('编辑胜利!'); //提醒 await this.getRouter().back(0, 'page'); //back(0)示意刷新当前页(列表页)}
- 反对
awiat dispatch(action)
执行,如在UI中期待login这个action的执行后果:
const onSubmit = (values: HFormData) => { const result = dispatch(stageActions.login(values)); //stageActions.login()中蕴含异步申请,返回Promise result.catch(({message}) => { //如果出错(明码谬误),在form中展现出错信息 form.setFields([{name: 'password', errors: [message]}]); });};
- 为action引入事件机制,dispatch一个action反对多处监听,独特合作实现一个长流程业务。例如:ModelA 和 ModelB 都想监听
用户切换
这个Action:
// ModelA:export class ModelA extends BaseResource { @effect() public async ['stage.putCurUser'](user: User) { if (user.hasLogin) { this.dispath(this.actions.xxx()); } else { this.dispath(this.actions.xxx()); } }}// ModelB:export class ModelB extends BaseResource{ @effect() public async ['stage.putCurUser'](user: User) { if (user.hasLogin) { this.dispath(this.actions.xxx()); } else { this.dispath(this.actions.xxx()); } }}
- 路由跳转前会主动派发
stage._testRouteChange
的action,你能够监听它,阻止路由跳转:
@effect(null)protected async ['this._testRouteChange']({url, pathname}) { if (!this.state.curUser.hasLogin && this.checkNeedsLogin(pathname)) { throw new CustomError(CommonErrorCode.unauthorized, '请登录!'); }}
- 反对catch action执行过程中的谬误,并决定持续或终止以后action执行:
@effect(null)protected async ['this._error'](error: CustomError) { if (error.code === CommonErrorCode.unauthorized) { this.getRouter().push({url: '/login'}, 'window'); }else{ alert(error.message); } throw error;}
最不便的注入loading状态,想要跟踪异步action的执行状况?只须要在申明办法中传人key名就行了,如:
@effect('this.listLoading') //将该异步action的执行状态注入this.state.listLoading中public async fetchList(listSearchData?: TDefineResource['ListSearch']) { const listSearch = listSearchData || this.state.listSearch || this.defaultListSearch; const {list, listSummary} = await this.api.getList!(listSearch); this.dispatch(this.privateActions.putList(listSearch, list, listSummary));}
- 武装到牙齿的Typescript智能提醒和主动补全(并且类型主动生成,无需手写):
- 同时反对
提供基于双栈单链的虚构路由。
- 领有2维历史记录栈,相当于在
SinglePage
中虚构了一个残缺的浏览器,页面能够在原窗口中关上,也能够新开一个虚构窗口
关上。
router.push({url: '/login'}, 'page') //在原窗口中关上router.push({url: '/login'}, 'window') //在新窗口中关上
- 基于虚构路由,不再间接关联原生路由,两头能够转换映射。
- 跨平台,可用于浏览器、服务器SSR、小程序、原生利用。
- 跨框架,可用于React、Vue,
- 不依赖其它路由框架,如react-router、vue-router
- 可残缺保留历史快照,实现
Keepalive
,包含Store和Dom元素 - 可拜访和查找历史记录,不再只是一个history.length
const length = router.getHistoryLength(); //获取历史栈中的记录数const list = router.getHistory(); //获取所有历史记录const record = router.findRecordByStep(10); //获取10步之前的历史记录const record2 = router.findRecordByKey('8_1'); //获取编号为8_1的历史记录
例如登录窗口中点击“勾销登录”你须要回退到前一个页面,但此时如果前一个页面就是须要登录的页面,那么登录窗口又会被从新弹出。所以点击“勾销登录”该当回退到最近的不须要登录的页面:
@effect()public async cancelLogin(): Promise<void> { //在历史栈中找到第一条不须要登录的记录 //如果简略的back(1),前一个页面须要登录时会引起循环 this.getRouter().back((record) => { return !this.checkNeedsLogin(record.location.pathname); }, 'window');}
反对路由拦挡和路由守卫
@effect(null)protected async ['this._testRouteChange']({url, pathname}) { if (!this.state.curUser.hasLogin && this.checkNeedsLogin(pathname)) { throw new CustomError(CommonErrorCode.unauthorized, '请登录!'); }}
- 反对后退溢出时重定向,比方避免用户后退过多,不小心退出了本站:
@effect(null)protected async ['this._error'](error: CustomError): Promise<void> { if (error.code === ErrorCodes.ROUTE_BACK_OVERFLOW) {//后退溢出时会抛出 const redirect: string = HomeUrl; //如果曾经时主页,则提醒用户是否退出本站? if (this.getRouter().location.url === redirect && window.confirm('确定要退出本站吗?')){ //留神: back('')能够退出本站 setTimeout(() => this.getRouter().back(''), 0); } else { //如果不是在主页,则先回到主页 setTimeout(() => this.getRouter().relaunch({url: redirect}), 0); } };}
- 可跟踪和期待路由跳转实现。例如批改用户后,须要返回列表页面并刷新:
@effect()public async updateItem(id: string, data: UpdateItem) { await this.api.updateItem!({id, data}); await this.getRouter().back(1, 'window'); //可await路由后退 message.success('编辑胜利!'); this.getRouter().back(0, 'page'); //back(0)可刷新页面}
- 提供更多路由跳转办法
router.push(location, target); //新增router.replace(location, target); //替换router.relaunch(location, target); //重置router.back(stepOrCallback, target) //后退或刷新
- 领有2维历史记录栈,相当于在
- 提供与我的项目同构的本地MockServer,MockServer也应用Typescript,但无需再写类型文件,间接从
src/
上面与我的项目共享,反对批改主动重启。 - 开箱即用的脚手架。提供封装好的
Cli命令行
脚手架,不必本人折腾: 根本的
eslint/stylelint/babel
都曾经帮你打包好了,不必装置各种插件和写一大堆依赖:"devDependencies": { "@elux/babel-preset": "^1.0.2", "@elux/eslint-plugin": "^1.2.2", "@elux/stylelint-config": "^1.1.1"}
- 未完待续...
不应用NPM治理微模块
我的项目中的微模块
默认是应用NPM包来治理的,每个微模块下都有一个package.json
文件,例如:src/modules/admin/package.json
,开发时应用了workspace
和monorepo
来治理:
"workspaces": [ "./src/modules/*" ],
跨微模块
援用时,用的是npm包名,例如:
import {ListSearch} from '@elux-admin-antd/member/entity';
微模块
最大的益处还是在于高内聚,低耦合,不肯定要应用NPM形式来治理。如果你不想绕这么一个圈,也能够分分钟改成一般的单体构造:
//import {ListSearch} from '@elux-admin-antd/member/entity';import {ListSearch} from '@/modules/member/entity';
只须要在src/tsconfig.json
中退出paths别名就能够了:
"paths": { "@/*": ["./*"]}
Vue版特地阐明
从React版本到Vue版本大略花了2天就实现了,Vue版/React版放弃同步,因为Elux践行“模型驱动”的架构理念,View被刻意写得很薄,很多逻辑写在了Model中(因为Model与UI框架无关、Vue和React都能够复用)。
所以须要重构的只是View,因为Vue3中能够应用steup+tsx
,并且antd-vue
与antd-react
格调和api根本保持一致,所以能2个版本的差别就更小了。Vue版全程应用tsx编写,你也能够本人改成template形式,脚手架曾经内置了对.vue文件的反对。也欢送有空的小伙伴奉献源码,将其重构为template版
。
更多相干文章
- 从"微前端"到“微模块”
- 不想当Window的Dialog不是一个好Modal,弹窗翻身记
- 手撸Router,还要啥Router框架?让react-router/vue-router躺一边凉爽去
- 一种比css_scoped和css_module更优雅的防止css命名抵触小妙招
感激关注,欢送参加
开源我的项目,欢送参加奉献源码(^V^)!感觉好用别忘了Github给个Star哦(-_-)...