Project Address:

  • medux-react-admin

  • Click online preview

This project is mainly used to show how to apply the @ medux web background management system, at the UI you may not see too much, nor too much special, online so many open source background front-end system, even every minute you will be able to set up their own a, because the generalization of the backend interface does not like this. However, the system architecture concept, code organization style, engineering modularity, routing design and so on behind the scenes work is like eight imitations across the sea, each show wisdom.

Therefore, you may need to understand the design idea of the project and get deep into the engineering structure and code to feel the special aspect of the project.

Maybe you should see it firstmeduxThe framework is introduced

Page lists

No need to log in to accessible pages:

  • /login
  • /register
  • /article/home
  • /article/service
  • /article/about

Pages that require login to access:

  • /admin/home
  • /admin/member
  • /admin/role
  • /admin/post

Compromise between customization and standardization

Often UI/UE designers who are looking for the ultimate user experience may ask front-end developers to customize various UIs, and you may complain that this design will mess with your modularity ideas, or complicate things, or lose code reuse… But in their eyes, you might just be slacking off… Speechless…

User experience is important, of course, but the robustness of your application is just as important as its maintainability, and without them, the best user experience is just hanging gardens. Don’t forget that the industrial revolution started with mass manufacturing of standard parts, and Rolls-Royce will never become a mainstream consumer.

Therefore, we need to make a trade-off between customization and standardization to maintain a good user experience while being able to cater to more common business scenarios. One idea is to divide and conquer the vast majority of scenarios from the few. If a UI solution can fit 90% of the business scenarios, why distort it to accommodate a few scenarios?

Having said all that, I just want to say that the purpose of this project is to provide a common backend for most business scenarios.

Independent status management

Medux advocates writing the business logic in the Model as much as possible and not coupling it to the lifecycle hooks of a particular UI component:

Generic engineering structures

The main structure of the development catalogue of this project is as follows:

SRC ├─ assets // Public Static Resources ├─ Entity // Store Business Entity Type Definition ├─ common // Store public code ├─ Components // Store UI Public Component ├─ modules │ ├─ App / / the Main Module │ │ ├ ─ ─ components │ │ ├ ─ ─ views │ │ │ ├ ─ ─ the Main │ │ │ ├ ─ ─ LoginForm │ │ │ └ ─ ─... │ │ ├ ─ ─ model. Ts │ │ └ ─ ─ but ts │ ├ ─ ─ the admin / / module classification, Module │ │ ├── adminHome │ │ ├─ adminLayout │ ├─ adminMember │ │ ├─ adminPost │ ├─ adminRole │ ├─ Article //module category, Module │ │ ├── Bass School - - bass School - - bass School - - bass School - - bass School ├ ─ ├ ─ 1.02 // └─ 1.02 // └─ 1.02 // └─ 1.02 //Copy the code


The first step is to discover and define the types and data structures of various business entities, which can be called entities, and place them under SRC/Entity


Components typically fall into two categories:

  • Global public Component: components common to each Module, placed under SRC/Components
  • Internal Module public Component: A public component used only by a Module, placed under Module /components, that can be loaded with the Module on demand


Static resources, like the components above, are classified as global public and Module public:

  • Global static resources are stored under SRC/Assets
  • Module Internal static resources are stored in Module/Assets


Pages can be divided into two categories in terms of user accessibility:

  • Pages that require a prior login to view, as in this examplePersonal centerI put them all insrc/modules/admin
  • Pages that can be viewed without prior login, as in this caseHelp manualI put them all insrc/modules/articleOf course, this only says that you do not need to log in in advance, some functions are still required.Log in on demand, such asHelp center- “Consult now” button in Banner


If careful, the login interface should also be divided into two types:

  • Independent Page, route redirects to the login Page. Often such a separate login page is ritualistic and personal, but interrupts the current flow of operations
  • Pop popover, lightweight login interface. Using popovers preserves the current flow of action. For example, you might spend a lot of time filling out a form, click Submit and find no login (perhaps because the session has expired), and then the application automatically routes the current page to/loginObviously, the current form content will be lost (of course, you can also encode and save). At this time, the better user experience is to keep the current page state, and then Pop login popover directly, so that the user can continue the previous operation flow after login, as shown in the figure below


Do you want to refresh the page after login/logout?

  • Refreshing the page is 100% effective, of course, but the user experience may not be as good.

  • It’s best not to refresh the page, but you may have to manually clean up and replace a bunch of failed states, which can sometimes make problems and code cumbersome and bug-prone. Can the user experience be sacrificed? In fact, login and logout is not a high-frequency operation for the same user, refreshing the page in addition to the time of waiting, it seems that there is no major side effect, so refresh the page.

    But there is a scenario: When the user submits the form and finds that the session has expired, a Pop login popup should be displayed to let the user log in again. After the re-login, the user can judge that if the session expires only in a short period of time, the user data will not be invalid. In this case, the page can not be refreshed, so that the form data filled by the user will not be lost.


How to keep the synchronization of user status between client and server, usually need a socket long connection push or Ajax polling, in order to reduce concurrency pressure usually use a channel is enough, you can define the data structure of this channel. It’s usually just used to push incremental differentiating data.


In singlePage single-page applications, the previous page usually overwrites the next page, and without opening the user experience in a new window, it becomes difficult when I want to compare two pages.

For example, if I want to quickly compare the list results of different search terms, when you search again with the second search term, it overwrites the original results.

Of course you could design multi-tab Windows like browsers, but that would complicate the question, should the Dom be destroyed? Considering that this requirement may not be very high frequency, this project uses a function similar to browser favorites to realize multi-window in disguise, as shown in the figure

Resource oriented maintenance

Inspired by Restful, complex business rules in reality can be regarded as resource-oriented maintenance, that is, adding, deleting, modifying and searching resources. In fact, our UI development can also be developed around this theme. For example, adminRole, adminMember, and adminPost in this project are all maintenance of a resource.


We first defined each resource to be maintained as a separate Module, then defined some abstract types of CommonResource in SRC/Entity /index.ts. A common CommonResourceState would look like this:

interface CommonResourceState {
  routeParams: Resource['RouteParams']; // Query criteria are placed in route parameters
  list: Resource['ListItem'] [];// Display a search list of resources
  listSummary: Resource['ListSummary']; // Search list summary information for resources
  selectedRows: Resource['ListItem'] [];// Which list items are currently selected
  currentItem: any; // Which resource is being operated on
Copy the code


An index, or list query, of a resource is usually an entry view of a resource presentation that includes search criteria, a return list, and summary information

// General query conditions
interfaceBaseListSearch { pageCurrent? :number; pageSize? :number; term? :string; // Real-time fuzzy searchsorterOrder? :'ascend' | 'descend'; sorterField? :string;
// A generic list data structure
interface BaseListItem {
  id: string; // Different Resource lists have different structures, but all have an ID
// A generic list query summary
interface BaseListSummary {
  pageCurrent: number;
  pageSize: number;
  totalItems: number;
  totalPages: number;
Copy the code


If you’ve read @medux routing, you know that Medux treats routes as State, so we put the list query conditions in RouteParams, which can be controlled by both redux and URL. The route parameter should look like this:

// Generic routing parameters
interface BaseRouteParams {
  listSearch: BaseListSearch; // Query conditions
  listView: string; // Which list view to use to display data
  _listKey: string; / / refresh hash
Copy the code

Notice that listSearch is easy to understand. What are listView and _listKey?

  • ListKey you can think of it as a version control over the current search criteria. If _listKey changes, it forces a new search even if the search criteria remain unchanged, similar to how we force static resource urls to update by adding a random number. In addition to addThe prefix stores this data in the hash, see@ medux routing
  • ListView indicates which view to use to display the list data, as discussed in detail below:

ListView with the Selector

Different business scenarios may have different views to render the same data. In the figure above, we can see that there are two ways to display the list of roles:

  • Figure 1 might be a normal list search on its own page, by role name and user permissions.
  • Figure 2 it pops up as a drop-down box on the user list page. It is used in a different module, but still belongs to the role list:The search criteria is the role name, and the list item has only one field, the role name. So we can think of the search drop-down control as another ListView for the role list.

By extension, any Resource can have at least two lists, one for its own maintenance list and the other for how it is selected and referenced by other resources. We can name them “List” and “Selector”, respectively. For complex selectors, we may need multiple query criteria, such as selecting “responsible editing” from “Information List” in the following figure:

The id and the name

interface SelectedItem {
  id: string;
  name: string;
Copy the code


There are usually two entry methods for displaying details:

  • The most common way to enter is by clicking the “Details” button from the listView resource list
  • You can enter the IP address directly from the route using the ID. The advantage of this is that you can share the IP address with other users through the URL for easy communication. For example, if ID issuperadminResources can be accessed as follows:/admin/member/list/detail/superadmin

In addition to the entry, the detail view itself usually presents itself in two ways:

  • Standalone page presentation: Compared to heavyweight interaction, the advantage is that you can show more content, but the disadvantage is that you break the original page and have to refresh the original page again when you return.
  • Pop-ups: Lightweight interaction with the advantage of maintaining other elements of the current page, such as search lists; The disadvantage is that the display area is relatively small. Can pop popovers be reached via routing? Also can, of course, such as: / admin/member/list/detail/superadmin


A Form can be created and modified using the same Form, with an empty ID and a value. But sometimes the required fields for two operations are different, so reuse them if you can.


ItemView: ItemView, ItemView, ItemView, ItemView, ItemView, ItemView, ItemView, ItemView, ItemView

// The generic route parameter becomes
interface BaseRouteParams {
  listSearch: BaseListSearch; // Query conditions
  listView: string; // Which listView to use to display data
  _listKey: string; / / refresh hash
  itemId: string;
  itemView: string; // Which itemView is used to display the data
  _itemKey: string; / / refresh hash
Copy the code
  • Details, itemView for detail: / admin/member/list/detail/superadmin
  • Modify, itemView for edit: / admin/member/list/edit/superadmin


Other actions, such as “enable/disable”, “approve/reject”, etc., can be abstracted as Status changes to resources.

Universal interactive logic

Some general details to note:

A list of the query

  • Too many search criteria can be folded to expand
  • Operations are classified into single operations and batch operations
  • Click search, Sort, or change pageSize to automatically return to page 1


  • After a successful addition, the list should be refreshed by creating a chronological order to ensure that changes are seen in the list
  • After the modification is successful, you should refresh the list with the current search criteria to ensure that you see changes in the list

A list of choices

  • After you select multiple items in the list, turn the page and search again to keep the current number of selected items
  • After you select multiple items in the list, keep the current number of selected items when modifying a certain item of data
  • After you select multiple items in the list, delete a data item to clear the current selected items

Extract common code

The reason for summarizing and extracting so much common logic is to achieve abstraction and reuse in your code.

The model of reuse

I define CommonResourceState, CommonResourceHandlers, and CommonResourceAPI in/SRC /common/ resource-. Ts, which basically cover common resource-oriented operations. Inherit it from model as a base class, and you’ll find a lot of code wrapped in the base class, such as adminMember’s Model:


export interface State extends CommonResourceState<Resource> {}

export const initModelState: State = {routeParams: defaultRouteParams};

export class ModelHandlers extends CommonResourceHandlers<Resource, State, RootState> {
  constructor(moduleName: string, store: any) {
    super({defaultRouteParams, api, enableRoute: {list: true, detail: true, edit: true}}, moduleName, store); }}Copy the code

You can see how little code there is….

The view of reuse

Because view is an external representation, it can reuse less code than Model, but there is a lot of interactive code that can be extracted, especially with React hooks that can be reused in more detail. I put them in the SRC /hooks directory, for example: useSelector, useMTable, useDetail, etc. See code for details.

Installation & operation see the next section

Setup & Run