本项目主要基于Elux+Antd
构建,包含React
版本和Vue
版本,旨在提供给大家一个简单基础、开箱即用的后台管理系统通用模版,主要包含运行环境、脚手架、代码风格、基本Layout、状态管理、路由管理、增删改查逻辑、列表、表单等。
为保持工程简单清爽,方便二次开发,只提供基本版式和通用组件,不集成各种眼花缭乱的组件,需要的时候自己加进去就行了,Antd本身也自带很多组件。
http://admin-react-antd.eluxjs.com/
git clone xxx
npm create elux@latest 或 yarn create elux
安装请注意!安装请注意!安装请注意!重要的事情说三遍,因为使用了workspace
,所以请保证你的安装环境:
yarn
安装依赖npm
安装依赖,npm版本 >= 7.0git clone
,最好关闭autocrlf
git config --global core.autocrlf false
🚀 提供通用的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
历史记录栈
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;
}
Hooks
复用。渲染器(Render)
概念,类别名+渲染器=具体某个业务视图,如:
🚀 使用微模块架构,将业务功能封装成独立微模块,想要哪个功能就安装哪个模块,是一种粒度更细的微前端
你以前的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 # 入口文件
🚀 内置最强状态管理框架(^-^):
React/Vue
的状态管理框架。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');
}
}
@effect()
public async updateItem(id: string, data: UpdateItem) {
await this.api.updateItem!({id, data}); //调用后台API
await this.getRouter().back(1, 'window'); //路由后退一步(到列表页)
message.success('编辑成功!'); //提示
this.getRouter().back(0); //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:// 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, '请登录!');
}
}
@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智能提示和自动补全(并且类型自动生成,无需手写):
🚀 提供基于双栈单链的虚拟路由。
router.push({url: '/login'}, 'page') //在原窗口中打开
router.push({url: '/login'}, 'window') //在新窗口中打开
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('', 'window'), 0);
} else {
//如果不是在主页,则先回到主页
setTimeout(() => this.getRouter().relaunch({url: redirect}, 'window'), 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); //back(0)可刷新页面
}
router.push(location, target); //新增
router.replace(location, target); //替换
router.relaunch(location, target); //重置
router.back(stepOrCallback, target) //后退或刷新
🚀 提供与项目同构的本地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包来管理的,每个微模块下都有一个package.json
文件,例如:src/modules/admin/package.json
,开发时使用了workspace
和monorepo
来管理:
"workspaces": [
"./mock",
"./public",
"./src/modules/*"
],
跨微模块
引用时,用的是npm包名,例如:
import {ListSearch} from '@elux-admin-antd/member/entity';
微模块
最大的好处还是在于高内聚,低耦合,至于是否要用npm来管理,不是必须的。如果你不想绕这么一个圈,也可以分分钟改成普通的单体结构:
import {ListSearch} from '@/modules/member/entity';
只需要在src/tsconfig.json
中加入paths别名就可以了:
"paths": {
"@/*": ["./*"]
}
Vue版/React版保持同步,由于Elux践行“模型驱动”的架构理念,View被刻意写得很薄,很多逻辑写在了Model中(因为Model与UI框架无关、Vue和React都可以复用)。
所以需要重构的只是View,由于Vue3中可以使用steup+tsx
,并且antd-vue
与antd-react
风格和api基本保持一致,所以能2个版本的差异就更小了。Vue版全程使用tsx编写,你也可以自己改成template方式,脚手架已经内置了对.vue文件的支持。也欢迎有空的小伙伴贡献源码,将其重构为template
版。
eluxjs.com | 掘金专栏 | QQ/微信交流群
开源项目,欢迎参与贡献源码(^V^)!觉得好用别忘了Github给个Star哦(-_-)...
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。