Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/ahyiru/huxy-admin

Huxy Admin is a customizable admin dashboard template based on React. Built with Webpack, @huxy/pack, useRouter, useStore, etc.
https://github.com/ahyiru/huxy-admin

dashboard express full-stack hooks husky low-code mongoose nodejs react responsive template webgl webpack

Last synced: about 2 hours ago
JSON representation

Huxy Admin is a customizable admin dashboard template based on React. Built with Webpack, @huxy/pack, useRouter, useStore, etc.

Awesome Lists containing this project

README

        

## Huxy Admin

[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/ahyiru/huxy-admin/blob/develop/LICENSE)
[![npm version](https://img.shields.io/npm/v/@huxy/router.svg)](https://www.npmjs.com/package/@huxy/router)
[![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/ahyiru/huxy-admin/lint-ci.yml?branch=develop)](https://github.com/ahyiru/huxy-admin/actions/workflows/lint-ci.yml)
[![](https://img.shields.io/badge/blog-ihuxy-blue.svg)](http://ihuxy.com/)

Huxy Admin is a customizable admin dashboard template based on React19. Built with Webpack5, [@huxy/pack](https://www.npmjs.com/package/@huxy/pack), [useRouter](https://www.npmjs.com/package/@huxy/router), [useStore](https://www.npmjs.com/package/@huxy/use), etc.

Huxy Admin is a customizable admin template for efficient and convenient development of maintainable and scalable systems, providing customized software design and development.

Huxy Admin uses MERN full-stack technology to quickly develop responsive web applications.

Huxy Admin 是一个可定制的后台管理模版,用于高效、便捷地开发可维护性和可扩展性强的系统,提供定制化软件设计与开发。Huxy Admin 使用 MERN 全栈技术,可快速开发响应式 Web 应用。

### [系统功能](https://github.com/ahyiru/huxy-admin/blob/develop/doc/functions.md)

1. 主题配置 ✅
2. i18n配置 ✅
3. 路由配置 ✅
4. 权限管理 ✅
5. API管理 ✅
6. 项目管理 ✅
7. 角色管理 ✅
8. 页面监控 ✅
9. 页面日志 ✅
10. 会员管理 ✅
11. 订单管理 ✅
12. 消息管理 ✅
13. 邮件管理 ✅
14. 页面管理 ✅
15. 文件管理 ✅
16. 文档管理 ✅
17. 需求管理 ✅

### 运行

#### 依赖安装:`pnpm i`

#### 项目运行

可自行配置运行项目,如运行 `template` 项目:

```js
npm start --dirname=template

```

运行 `blog` 目录下的 `router` 项目:

```js
npm start --dirname=blog/router

```

默认运行 `app` 目录。`npm start`。

#### 其它

其它一些命令:主要有 打包、文件分析、运行打包的资源、jest 测试、eslint、stylelint、lint-fix、prettier、release 等。

```json
"start": "nodemon scripts/index.js --watch scripts/index.js",
"build": "webpack --config scripts/webpack.production.js --progress",
"analyze": "ANALYZE=1 webpack --config scripts/webpack.production.js --progress",
"server": "nodemon scripts/server.js --watch scripts/server.js",
"test": "jest --colors --coverage",
"eslint": "eslint 'app/**/*.{js,jsx}'",
"stylelint": "stylelint 'app/**/*.{css,less}'",
"lint": "npm run eslint && npm run stylelint",
"lint-fix": "eslint --fix 'app/**/*.{js,jsx}' && stylelint --fix 'app/**/*.{css,less}'",
"prettier": "prettier 'app/**/*' --write --ignore-unknown && npm run lint-fix",
"release": "standard-version",

```

- `npm run build`:打包
- `npm run analyze`:打包文件分析
- `npm test`:单元测试
- `npm run lint`:代码规范检查,包含 `eslint` 和 `stylelint`
- `npm run lint-fix`:代码规范修正
- `npm run prettier`:代码美化
- `npm run release`:自动打 `tag` 并生成 `CHANGELOG`

提供了 `git` 代码提交钩子 `husky` ,`git` 提交信息规范 `commitlint` ,支持 `pnpm` 安装依赖。

### 全局配置

#### 全局配置

主要有 运行端口、资源路径、打包路径、basepath、代理、mock 等。

`configs/app.js`

```js
const app = {
HOST: process.env.IP || 'http://localhost',
PORT: process.env.PORT || 9500, // 开发环境运行端口
PRO_PORT: process.env.PRO_PORT || 9501, // 打包运行端口
BUILD_DIR: './build', // 打包路径
PUBLIC_DIR: '../public', // 静态资源路径
DEV_ROOT_DIR: '/', // dev basepath
PRD_ROOT_DIR: '/', // prod basepath
PROXY: {
url: 'http://api.ihuxy.com', // 服务代理
prefix: '/api', // 代理前缀
},
MOCK: 'http://localhost:9502', // mock 地址
SERVER_PORT: 9503, // nodejs 本地服务端口
projectName: '...', // 项目名称
};
```

### 项目配置

主要是路由、导航栏、主题、i18ns 等配置。

`app/configs`

#### 环境配置

路由类型、basepath 通过全局配置获取。

```js
export const browserRouter = !process.env.isDev;

export const basepath = browserRouter ? PRD_ROOT_DIR : DEV_ROOT_DIR;
```

#### 路由

`app/configs/router`:路由钩子 `beforeRender` 可控制路由跳转。

```js
const beforeRender = (input, next) => {
const {path, prevPath} = input;
const validPath = path.split('?')[0];
if (validPath === initPath) {
return next({path: '/'});
}
if (!token && !whiteRoutes.includes(validPath)) {
return next({path: '/user/signin'});
}
next();
};

export default {
browserRouter,
beforeRender,
basepath,
};
```

#### 导航栏

`app/configs/nav`:导航栏可分为左侧导航栏和右侧导航栏。

```js
export const leftNav = () => {
const left = getIntls('nav.left', {});
return [
{
key: 'collapse',
name: left?.collapse ?? 'collapse',
type: 'collapse',
Custom: () => ,
},
];
};
export const rightNav = () => {
const right = getIntls('nav.right', {});
return [
{
key: 'username',
name: user?.name ?? right?.user,
img: user?.avatar ?? defUser,
children: [
{
key: 'profile',
name: right?.profile ?? '个人中心',
icon: 'UserOutlined',
path: '/profile',
},
{
key: 'settings',
name: right?.settings ?? '设置',
icon: 'SettingOutlined',
path: '/profile',
},
{
divider: true,
key: 'logout',
name: right?.logout ?? '退出',
icon: 'PoweroffOutlined',
handle: item => {
logout();
},
},
],
},
];
};
```

#### 主题

`app/configs/theme`:主题列表包含主题名和主题配置表等。

```js
const themeList = getIntls => [
{
name: getIntls('theme.dark', 'dark'),
key: 'dark',
list: dark,
type: 'theme',
},
{
name: getIntls('theme.dark1', 'dark1'),
key: 'dark1',
list: dark1,
type: 'theme',
},
{
name: getIntls('theme.dark', 'light'),
key: 'light',
list: light,
type: 'theme',
},
{
name: getIntls('theme.light1', 'light1'),
key: 'light1',
list: light1,
type: 'theme',
},
{
name: getIntls('theme.portal', 'portal'),
key: 'portal',
list: portal,
type: 'theme',
},
];
```

#### 语言

`app/configs/i18ns`:

```js
const langList = [
{
key: 'zh',
name: '汉语',
icon: zh_icon,
},
{
key: 'en',
name: '英语',
icon: en_icon,
},
{
key: 'jp',
name: '日语',
icon: jp_icon,
},
];
```

### Api 请求

`app/apis`

#### 请求参数处理

可自行配置路径 `prefix` 、`headers`、`token` 、`credentials` 等。

```js
const getToken = () => ({Authorization: `test ${storage.get('token') || ''}`});

const fetch = ({method, url, ...opt}) => fetchApi(method)(`${TARGET}${url}`, {...opt, headers: getToken(), credentials: 'omit'});
```

#### 返回结果处理

根据约定返回 `code` 码配置不同操作。可设置通用提示信息、数据格式化等。

```js
const success_code = [200, 10000];

const handler = response => {
return response
.json()
.then(result => {
result.code = result.code ?? response.status;
result.msg = result.message ?? result.msg ?? response.statusText;
const {msg, code} = result;
if (code === 401) {
message.error(msg);
logout(true);
throw {code, message: msg};
}
if (!success_code.includes(code)) {
throw {code, message: msg};
}
return result;
})
.catch(error => {
message.error(error.message);
throw error.message;
});
};
```

#### 接口配置

```js
const apiList = [
{
name: 'login',
url: '/auth/signup',
method: 'post',
},
{
fnName: 'getProfile',
url: '/users/profile',
},
];
```

- fnName:自定义前端调用的函数名
- name:默认前端调用的函数名 `${name}Fn`
- url:接口地址
- method:请求方式,默认 `get`
- type:数据类型,json\formData\form\query,默认 json。

可根据后台提供 API 文档来配置。`schema` 数据,方便同构。

### 路由

`app/routes`

支持动态路由、权限路由、路由白名单等。

#### 基本配置

```js
{
path: '/demo',
name: 'demo',
icon: 'MergeCellsOutlined',
denied: !isDev,
component: () => import('@app/views/demo'),
},
```

可通过 `denied` 属性来控制是否渲染路由。可将 `staticRoutes` 设置为白名单路由。

可根据后台返回路由配置来做控制,也可根据后台返回权限码来控制。白名单一般就是登录注册页面,也可自行配置。

```js
const allRoutes = [
{
path: '/',
component: () => import('@app/commons/layout'),
children: dynamicRoutes,
},
...staticRoutes,
];
```

#### 组件加载方式

路由组件可按需加载,可根据目录地址加载。

```js
export const playgroundRoutes = {
path: '/playground',
name: ' Playground',
icon: 'ConsoleSqlOutlined',
children: [
{
path: '/demo',
name: 'demo',
icon: 'MergeCellsOutlined',
denied: browserRouter,
component: () => import('@app/views/demo'),
},
{
path: '/icons',
name: 'icons',
icon: 'PictureOutlined',
componentPath: '/demo/icons',
},
{
path: '/panel',
name: 'panel',
component: PanelDemo,
},
],
};
```

详情见[简单实现 react router](https://zhuanlan.zhihu.com/p/106989011)

#### useRouter 使用:

```js
const output = useRouter(input);
```

通过传入一些配置(主要是路由类型、路由列表、路由拦截函数等),返回给我们当前路径下的渲染组件以及 menu 菜单的处理数据和面包屑数据。

提供 `Link` 、 `useRoute` 。`Link` 为路由跳转组件,`useRoute` 可获取到当前路由下的信息。

详情见[useRouter 使用](https://zhuanlan.zhihu.com/p/373920768)

### 主题配置

`app/theme`

```js
const sizes = {
'--maxWidth': '100vw',
'--menuWidth': '240px',
'--collapseWidth': '68px',
'--collapseMenuWidth': '180px',
'--headerHeight': '68px',
'--footerHeight': '60px',
'--breadHeight': '50px',
'--menuItemHeight': '48px',
};
const colors = {
'--bannerBgColor': '#37424c',
'--navBgColor': '#3c4752',
'--menuBgColor': '#37424c',
'--appBgColor': '#303841',
'--pageBgColor': '#303841',
'--panelBgColor': '#36404a',
'--appColor': '#8c98a5',
'--linkColor': '#9097a7',
'--pageLinkColor': '#8c98a5',
'--linkHoverColor': '#c8cddc',
'--linkActiveColor': '#ffffff',
'--borderColor': '#424e5a',
};
```

通过 `css` 变量自行设置大小和颜色,在系统中直接使用即可。

### layout 配置

`layout` 可自己设计,整体框架无非就是头部导航栏和侧边菜单栏。

我提供了一个管理平台 `layout` 模板,菜单通过路由返回 `menu` 数据渲染,头部导航栏可根据 `nav` 配置数据渲染,面包屑也在路由返回数据 `current` 里面。

#### 通用配置

`commons/layout`

```js
const layoutConfigs = {
MainTop: () => {},
MenuBottom: () => {},
Link: () => {},
handleNavClick: () => {},
logo: '',
leftList: [],
rightList: [],
headerStyle: {},
menuStyle: {},
...
};
```

#### 全局 UI `i18n` 配置

```jsx
const Index = props => (



);
```

### i18ns 配置

`app/i18ns`

#### 加载

`i18ns` 目录存放语言包,以语言 `key` 命名,通过 `key` 值按需加载语言包。

```js
const getI18n = async () => {
const language = getLang();
let i18ns = await import(`@app/i18ns/${language}`);
i18ns = i18ns.default ?? i18ns;
return {i18ns, language};
};
```

语言包配置,可以使用页面或模块命名文件,然后分别做配置,如:

- zh
- main
- nav
- router
- theme
- login

#### 具体配置

```js
const layout = {
saveConfig: '保存配置',
copyConfig: '拷贝配置',
menuType: '菜单类型:',
vertical: '纵向',
horizontal: '横向',
compose: '横纵组合',
fontSize: '字体大小:',
layoutDesign: '布局',
sizeDesign: '大小',
colorDesign: '颜色',
save_cfg_msg: '主题保存成功!',
copy_cfg_msg: '主题拷贝成功!',
data_valid_msg: '请输入合法数据!',
data_px_msg: '请输入500-5000内数据!',
data_percent_msg: '请输入50-100内数据!',
menu_width_msg: '请输入0-300内数据!',
};
```

#### 使用

提供 3 种使用方式:

**_组件_**

```jsx
邮箱
```

通过 `key` 来获取语言数据,如果获取失败则展示 `children` 文本。

**_hook_**

```jsx
const getIntls = useIntls();
message.success(getIntls('main.layout.save_cfg_msg', '成功!'));
```

`hook` 返回一个函数 `getIntls` ,第一个参数为文本 `key` 值,第二个参数是返回为空时的默认值。

**_函数_**

`getIntls` 函数和 `useIntls` 返回的函数一致,不同的是此函数可在任何地方使用,如纯 `js` 代码里。

### 状态管理

`app/store`

基于 `flux` 理念,使用发布订阅模式来实现。主要提供 3 个函数:

- getState:获取数据
- setState:设置数据
- subscribe:监听数据

#### 通用方式

```js
const {useStore} = props;

const [list, update, subscribe] = useStore('userList', {});
```

- key:userList
- 默认值:{}
- list:value 值
- update:更新函数
- subscribe:监听函数

```js
const Page1 = props => {
const [list, update] = useStore('userList', []);
const deleteUse = async id => {
await fetchDel({id});
update();
};
};

const Page2 = props => {
const [, , subscribe] = useStore('userList', []);
useEffect(() => {
subscribe(result => {
console.log(result);
});
}, []);
};
```

#### 组合

有 2 种使用方式,函数和 `hook`。当创建时使用的是同一个 `store` 时,二者数据可通用。如:

```js
// a.jsx 可检测到数据更新
const [state] = useStore('store-test');
// b.jsx 设置数据
store.setState({'store-test', 'test data'});
```

#### 创建 `store`

```js
import createStore from '@huxy/utils/createStore';
import createContainer from '@huxy/utils/createContainer';
import createUseContainer from '@huxy/use/createContainer';

export const container = createStore();

export const store = createContainer(container);
export const useStore = createUseContainer(container);
```

#### 数据名(key)

```js
export const langName = 'lang-store';
export const themeName = 'theme-store';
export const menuTypeName = 'menuType-store';
export const i18nsName = 'i18ns-store';
export const userInfoName = 'userInfo-store';
export const permissionName = 'permission-store';
export const routersName = 'routers-store';
```

#### 创建函数(hooks)

```js
export const langStore = store(langName);
export const themeStore = store(themeName);
export const menuTypeStore = store(menuTypeName);
export const i18nsStore = store(i18nsName);
export const userInfoStore = store(userInfoName);
export const permissionStore = store(permissionName);

export const useLangStore = initState => useStore(langName, initState);
export const useThemeStore = initState => useStore(themeName, initState);
export const useMenuTypeStore = initState => useStore(menuTypeName, initState);
export const useI18nsStore = initState => useStore(i18nsName, initState);
export const useUserInfoStore = initState => useStore(userInfoName, initState);
export const usePermissionStore = initState => useStore(permissionName, initState);
```

#### 使用

```jsx
// useI18nsStore
const Intls = ({keys, children}) => {
const [i18ns] = useI18nsStore();
return (keys && i18ns?.getValue(keys)) ?? children ?? '';
};
// i18nsStore
export const getIntls = (keys, def) => (keys && i18nsStore.getState()?.getValue(keys)) ?? def ?? '';
```

### utils、components、hooks

组件可分为基础组件、业务组件,基础组件粒度低、通用性高,业务组件包含了一些业务场景,在某一领域模型内通用,通常是基础组件组合而来。

#### utils

例如:格式化树

```js
// formatTree
const fixIcon = router =>
router.map(item => {
item.key = item.key || item.path;
item.icon = ;
return item;
});

const formatTree = arr =>
traverItem(item => {
if (!isValidArr(item.children)) {
item.isLeaf = true;
}
})(arr2TreeByPath(fixIcon(arr)));

export default formatTree;
```

详情见 [utils](https://github.com/ahyiru/utils)

#### components

例如:超出宽度文本自动省略并展示 `tooltip`

```jsx
const style = {
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
display: 'inline-block',
width: '100%',
};

const Ellipsis = ({children, ttProps}) => {
const span = useRef();
const [ellipsis, setEllipsis] = useState(false);
const state = useEleResize(span, 250);
useEffect(() => {
if (span.current) {
const {width: tWidth} = getTextSize(children);
const {width} = getPosition(span.current);
const isEllipsis = tWidth > width;
if (isEllipsis !== ellipsis) {
setEllipsis(isEllipsis);
}
}
}, [children, state.width]);
return (

{ellipsis ? (

{children}

) : (
{children}
)}

);
};
```

**_Input_**

```jsx
const Input = ({className, ...rest}) => {
const cls = ['h-input', ...(className?.split(' ') ?? [])]
.filter(Boolean)
.map(c => styles[c])
.join(' ');
return ;
};

export default Input;
```

**_Radio_**

```jsx
const Radio = ({options, name, value: checked, onChange, ...rest}) => (


{options.map(({value, label}) => (
onChange?.(value, e)} style={{display: 'flex', alignItems: 'center', marginRight: '12px', cursor: 'pointer'}}>

{value}

))}

);

export default Radio;
```

**_Tooltip_**

```jsx
const Tooltip = ({children, title, placement}) => (

{children}

);

export default Tooltip;
```

**_Drawer_**

```jsx
const Drawer = ({visible, onClose, footer, header, className, children, width = '300px'}) => {
const cls = ['drawer-wrap', visible ? 'open' : '', ...(className?.split(' ') ?? [])]
.filter(Boolean)
.map(c => styles[c])
.join(' ');
return (





{header}

{children}

{footer ?
{footer}
: null}



);
};

export default Drawer;
```

详情见 [components](https://github.com/ahyiru/components)

#### hooks

例如:请求列表数据 `hook` ,包含返回结果处理、分页、搜索、更新等。

useFetchList:

```jsx
const useFetchList = (fetchList, initParams = null, handleResult, commonParams = null) => {
const [result, updateResult] = useAsync({});
const update = useCallback(params => updateResult({res: fetchList({...commonParams, ...params})}, handleResult), []);
useEffect(() => {
update({...initParams});
}, []);
const {res} = result;
const isPending = !res || res.pending;

return [{isPending, data: res?.result}, update];
};
```

useHandleList:

```jsx
const useHandleList = (fetchList, initParams = null, handleResult, commonParams = null) => {
const {current, size, ...rest} = initParams || {};
const search = useRef(rest || {});
const page = useRef({current: current || 1, size: size || 10});
const [result, update] = useFetchList(fetchList, {...page.current, ...search.current}, handleResult, commonParams);

const pageChange = (current, size) => {
page.current = {current, size};
update({
...page.current,
...search.current,
});
};
const searchList = values => {
search.current = values;
page.current = {...page.current, current: 1};
update({...page.current, ...search.current});
};
const handleUpdate = params => {
const {current, size, ...rest} = params;
page.current = {current: current ?? page.current.current, size: size ?? page.current.size};
search.current = {...search.current, ...rest};
update({...page.current, ...search.current});
};

return [result, handleUpdate, pageChange, searchList];
};
```

详情见 [use](https://github.com/ahyiru/use)