乐趣区

关于react.js:React版Vue版都齐了不一样的开源后台管理系统坐等你来开箱

本我的项目次要基于 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) // 后退或刷新
  • 🚀 提供与我的项目同构的本地 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,开发时应用了 workspacemonorepo来治理:

  "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-vueantd-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 哦(-_-)…

退出移动版