草庐IT

React版/Vue版都齐了,开源一套【特别】的后台管理系统...

EluxJS 2023-03-28 原文

本项目主要基于Elux+Antd构建,包含React版本和Vue版本,旨在提供给大家一个简单基础开箱即用的后台管理系统通用模版,主要包含运行环境、脚手架、代码风格、基本Layout、状态管理、路由管理、增删改查逻辑、列表、表单等。

为保持工程简单清爽,方便二次开发,只提供基本版式和通用组件,不集成各种眼花缭乱的组件,需要的时候自己加进去就行了,Antd本身也自带很多组件。

在线预览

http://admin-react-antd.eluxjs.com/

Git仓库

安装方法

  • 使用Git命令clone相应的库:git clone xxx
  • 也可以使用Elux提供的命令:npm create elux@latest 或 yarn create elux

⚠️注意事项

安装请注意!安装请注意!安装请注意!重要的事情说三遍,因为使用了workspace,所以请保证你的安装环境:

  • Node版本 >= 14.0.0
  • 推荐使用 yarn 安装依赖
  • 如果使用 npm 安装依赖,npm版本 >= 7.0
  • 本项目代码风格检查以 LF 作为换行符,如果你在windows中使用git clone,最好关闭autocrlf

    git config --global core.autocrlf false

你看得见的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;
    }
    

你看不见的幕后

  • ? 使用微模块架构,将业务功能封装成独立微模块,想要哪个功能就安装哪个模块,是一种粒度更细的微前端

     你以前的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版

更多相关文章

感谢关注,欢迎参与

开源项目,欢迎参与贡献源码(V)!觉得好用别忘了Github给个Star哦(-_-)...

有关React版/Vue版都齐了,开源一套【特别】的后台管理系统...的更多相关文章

  1. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  2. ruby - 如何在 ruby​​ 中运行后台线程? - 2

    我是ruby​​的新手,我认为重新构建一个我用C#编写的简单聊天程序是个好主意。我正在使用Ruby2.0.0MRI(Matz的Ruby实现)。问题是我想在服务器运行时为简单的服务器命令提供I/O。这是从示例中获取的服务器。我添加了使用gets()获取输入的命令方法。我希望此方法在后台作为线程运行,但该线程正在阻塞另一个线程。require'socket'#Getsocketsfromstdlibserver=TCPServer.open(2000)#Sockettolistenonport2000defcommandsx=1whilex==1exitProgram=gets.chomp

  3. ruby-on-rails - Ruby 长时间运行的进程对队列事件使用react - 2

    我有一个将某些事件写入队列的Rails3应用。现在我想在服务器上创建一个服务,每x秒轮询一次队列,并按计划执行其他任务。除了创建ruby​​脚本并通过cron作业运行它之外,还有其他稳定的替代方案吗? 最佳答案 尽管启动基于Rails的持久任务是一种选择,但您可能希望查看更有序的系统,例如delayed_job或Starling管理您的工作量。我建议不要在cron中运行某些东西,因为启动整个Rails堆栈的开销可能很大。每隔几秒运行一次它是不切实际的,因为Rails上的启动时间通常为5-15秒,具体取决于您的硬件。不过,每天这样做几

  4. (附源码)vue3.0+.NET6实现聊天室(实时聊天SignalR) - 2

    参考文章搭建文章gitte源码在线体验可以注册两个号来测试演示图:一.整体介绍  介绍SignalR一种通讯模型Hub(中心模型,或者叫集线器模型),调用这个模型写好的方法,去发送消息。  内容有:    ①:Hub模型的方法介绍    ②:服务器端代码介绍    ③:前端vue3安装并调用后端方法    ④:聊天室样例整体流程:1、进入网站->调用连接SignalR的方法2、与好友发送消息->调用SignalR的自定义方法 前端通过,signalR内置方法.invoke()  去请求接口3、监听接受方法(渲染消息)通过new signalR.HubConnectionBuilder().on

  5. ruby-on-rails - 使用 Kernel#fork 进行后台进程,专业人士?缺点? - 2

    我想知道使用fork{}从Rails应用程序“后台”处理是否是个好主意...从我收集到的fork{my_method;Process#setsid}实际上做了它应该做的事情。1)创建另一个具有不同PID的进程2)不中断调用过程(例如它继续w/o等待fork完成)3)执行子进程直到它完成..这很酷,但这是个好主意吗?fork到底在做什么?它会在内存中创建我的整个railsmongrel/passenger实例的重复实例吗?如果是这样那就太糟糕了。或者,它是否以某种方式在不消耗大量内存的情况下完成。我的最终目标是取消我的后台守护进程/队列系统,转而支持这些进程的fork(主要是发送电子邮件

  6. python - 开源 Twitter 克隆(在 Ruby/Python 中) - 2

    关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。关闭6年前。Improvethisquestion是否有任何用Ruby或Python编写的生产就绪的开源Twitter克隆?我对功能丰富的实现更感兴趣,而不仅仅是简单的Twitter消息(例如:API、FBconnect、通知等)谢谢!

  7. ruby - 单个 EventMachine react 器中的多个服务器 - 2

    是否可以在单个事件机器中运行多个服务器?我的意思是,多个服务可以由一个客户端连接同时使用。例如,登录服务器对用户进行身份验证,然后用户可以同时使用聊天室和简单的游戏,例如带有单个客户端套接字的跳棋?或者我是否需要为每个服务使用多个事件机器react器? 最佳答案 我试过了,它正在工作:#!/usr/bin/envrubyrequire'eventmachine'moduleEchoServerdefpost_initputs"--someoneconnectedtotheechoserver!"enddefreceive_datad

  8. Gradle 自动化构建开源工具 - 2

    文章目录写在前面1、下载与安装(windows)1.1、idea中配置gradle2、基础知识(Gradle6.9为例)2.1、Gradle脚本语法2.1.1、dependsOn2.1.2、创建动态任务2.1.3、增加任务行为2.1.4、参数2.1.5、Ant任务2.1.6、方法2.1.7、默认任务2.1.6、依赖任务的不同输出3、java项目中使用3.1、在已有项目中构建gradle3.2、在新建项目时构建gradle(idea)3.3、gradle项目目录结构3.4、build.gradle3.4.1、plugins3.4.2、repositories3.4.3、dependencies3

  9. vue 实现内容超出两行显示展开更多功能,可依据需求自定义任意行数! - 2

    平时开发中我们经常会遇到这样的需求,在一个不限高度的盒子中会有很多内容,如果全部显示用户体验会非常不好,所以可以先折叠起来,当内容达到一定高度时,显示展开更多按钮,点击即可显示全部内容,先来看看效果图: 这样做用户体验瞬间得到提升,接下来看看具体细节。0">主要操作在内容这里{{item.username}},……展开更多样式大家可依据自己项目需求进行设计,这里就不贴了,主要说几个关键的。1、在data中定义三个属性isShowMore:false, //控制展开更多的显示与隐藏textHeight:null, //框中内容的高度status:false, //内容状态是否打开2.计算内容是否

  10. vue3.0 + vite2.0+如何兼容低版本浏览器 - 2

    这里写自定义目录标题一、问题二、解决三、解决方案四、打包预览一、问题在使用vue3.2和vite2开发一个移动端或者钉钉端H5微服务iosapp内置浏览器打开没问题安卓app内置浏览器打开空白页面vconsole打印出现报错globalthisundefind二、解决内置浏览器版本比较低打印出来是63vue3代码不兼容低版本浏览器三、解决方案步骤一:vite.config.ts里build.target配置项指定构建目标为es2015或者步骤二:安装@vitejs/plugin-legacy安装完报错也还在指定版本可以解决“@vitejs/plugin-legacy”:“1.8.0”,步骤三:

随机推荐