From 448d28db2e4947888cdee66c2ba168b5ae9d03f6 Mon Sep 17 00:00:00 2001 From: Soybean <2570172956@qq.com> Date: Wed, 10 Nov 2021 20:44:43 +0800 Subject: [PATCH] =?UTF-8?q?docs(projects):=20=E6=96=87=E6=A1=A3=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/extensions.json | 1 - README.md | 57 ++--- doc/TS写法规范.md | 47 ++++ doc/vue+TS书写规范.md | 73 ------ doc/vue书写规范.md | 209 ++++++++++++++++++ doc/命名规范.md | 59 +++-- doc/目录.md | 101 +++++++++ src/interface/common.ts | 10 + src/interface/index.ts | 1 - src/interface/package.ts | 4 - .../index.vue | 0 src/layouts/index.ts | 4 +- src/router/modules/component.ts | 4 +- src/router/modules/multi-menu.ts | 4 +- src/utils/service/index.ts | 121 ++++++++++ 15 files changed, 563 insertions(+), 132 deletions(-) create mode 100644 doc/TS写法规范.md delete mode 100644 doc/vue+TS书写规范.md create mode 100644 doc/vue书写规范.md create mode 100644 doc/目录.md delete mode 100644 src/interface/package.ts rename src/layouts/{BasicChildLayout => RouterViewLayout}/index.vue (100%) create mode 100644 src/utils/service/index.ts diff --git a/.vscode/extensions.json b/.vscode/extensions.json index d887751d..7f67f881 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -12,7 +12,6 @@ "dbaeumer.vscode-eslint", "miguelsolorio.fluent-icons", "mhutchie.git-graph", - "donjayamanne.githistory", "eamodio.gitlens", "lokalise.i18n-ally", "afzalsayed96.icones", diff --git a/README.md b/README.md index ab394ae5..fdc22444 100644 --- a/README.md +++ b/README.md @@ -12,18 +12,21 @@ ## 简介 -Soybean Admin 是一个基于 Vue3、Vite、Naive UI、TypeScript 的中后台解决方案,它使用了最新的前端技术栈,并提炼了典型的业务模型,页面,包括二次封装组件、动态菜单、权限校验、粒子化权限控制等功能,它可以帮助你快速搭建企业级中后台项目,相信不管是从新技术使用还是其他方面,都能帮助到你。 +Soybean Admin 是一个基于 Vue3、Vite、Naive UI、TypeScript 的免费中后台模版,它使用了最新的前端技术栈,内置丰富的插件,有着极高的代码规范, + +开箱即用的中后台前端解决方案,也可用于学习参考。 ## 特性 - **最新技术栈**:使用 Vue3/vite2 等前端前沿技术开发, 使用高效率的npm包管理器pnpm - **TypeScript**: 应用程序级 JavaScript 的语言 -- **主题**:可配置的主题 -- **代码规范**:丰富的规范插件及极高规范的代码组织 +- **主题**:丰富可配置的主题 +- **代码规范**:丰富的规范插件及极高的代码规范 +- **路由配置**:简易的路由配置 ## 预览 -- [soybean-admin](https://soybean.pro/) - 站点 +- [soybean-admin](https://soybean.pro/) ## 目录规范 @@ -41,8 +44,8 @@ soybean-admin │ └── windicss.ts //css框架插件 ├── doc //项目相关说明文档 ├── public //公共目录 -│ ├── resource //资源文件夹(不会被打包) -│ └── favicon.ico +│ ├── resource //资源文件夹(打包后会保留到dist根目录) +│ └── favicon.ico //网站标签图标 ├── src │ ├── assets //静态资源 │ ├── components //全局组件 @@ -57,7 +60,7 @@ soybean-admin │ │ ├── business.ts //业务相关枚举 │ │ ├── common.ts //通用枚举 │ │ ├── route.ts //路由相关枚举 -│ │ ├── storage.ts //存储相关枚举 +│ │ ├── storage.ts //存储相关枚举 │ │ └── theme.ts //系统主题配置相关枚举 │ ├── hooks //组合式的钩子函数hooks │ │ ├── business //业务相关hooks @@ -67,41 +70,43 @@ soybean-admin │ │ ├── common.ts //通用类型接口 │ │ └── theme.ts //系统主题配置相关类型接口 │ ├── layouts //布局组件 -│ │ ├── BasicLayout //基本布局组件(包含全局头部、侧边栏、底部等) -│ │ └── BlankLayout //空白布局组件 +│ │ ├── BasicLayout //基本布局(包含全局头部、侧边栏、底部等公共部分) +│ │ ├── BlankLayout //空白布局组件(单个页面) +│ │ └── RouterViewLayout //路由组件(用于多级路由之间的桥接) │ ├── plugins //插件 │ │ └── dark-mode.ts //windicss暗黑模式插件 │ ├── router //vue路由 -│ │ ├── cache.ts //缓存的路由 -│ │ ├── components.ts //缓存的路由对应的组件 -│ │ ├── helpers.ts //工具函数 -│ │ ├── menus.ts //菜单 -│ │ ├── permission.ts //路由守卫相关函数 -│ │ └── routes.ts //声明的路由 +│ │ ├── modules //路由页面(按模块划分) +│ │ ├── permission //路由权限(路由守卫) +│ │ ├── routes //声明的路由 +│ │ └── setup //路由挂载函数 │ ├── service //网络请求 -│ │ ├── api //请求接口 +│ │ ├── api //接口api │ │ ├── middleware //请求结果的处理中间件 -│ │ ├── request //封装的请求函数 -│ │ └── utils //请求相关工具函数 -│ ├── settings //项目初始配置 +│ │ └── request //封装的请求函数 +│ ├── settings //项目静态配置 +│ │ ├── constant //常量配置 │ │ └── theme.ts //项目主题初始配置 │ ├── store //状态管理 │ │ └── modules //状态管理划分的模块 │ ├── styles //样式 +│ │ ├── css //css +│ │ └── scss //scss │ ├── typings //TS类型声明文件(*.d.ts) │ ├── utils //全局工具函数 -│ │ ├── auth -│ │ ├── common -│ │ ├── package -│ │ ├── router -│ │ └── storage +│ │ ├── auth //用户鉴权 +│ │ ├── common //通用工具函数 +│ │ ├── package //npm依赖 +│ │ ├── router //路由 +│ │ ├── request //请求工具函数 +│ │ └── storage //存储 │ ├── views //页面 │ │ ├── about │ │ ├── component │ │ ├── dashboard │ │ ├── document │ │ ├── multi-menu -│ │ └── system +│ │ └── system //系统内置页面:登录、异常页等 │ ├── App.vue //vue文件入口 │ ├── AppProvider.vue //配置naive UI的vue文件(国际化,loadingBar、message等组件) │ └── main.ts //项目入口ts文件 @@ -118,7 +123,7 @@ soybean-admin ├── .prettierrc.js //prettier代码格式插件配置 ├── commitlint.config.js //commitlint提交规范插件配置 ├── index.html -├── package.json +├── package.json //npm依赖描述文件 ├── pnpm-lock.yaml //npm包管理器pnpm依赖锁定文件 ├── README.md //项目介绍文档 ├── tsconfig.json //TS配置 diff --git a/doc/TS写法规范.md b/doc/TS写法规范.md new file mode 100644 index 00000000..6befc6dc --- /dev/null +++ b/doc/TS写法规范.md @@ -0,0 +1,47 @@ +### 1.interface和type + +##### interface和type使用优先级:能用interface表示的类型就用interface。 + +### 2.请求函数 + +#### api接口: + +统一以 **fetch** 开头,例如: + +```typescript +/** + * 获取用户信息 + * @param id - 用户唯一标识id + */ +function fetchUserInfo(id:string) { + // *** +} +/** + * 删除列表项 + * @param id - 列表id + */ +function fetchDeleteListItem(id:string) { + // *** +} +``` + +#### middleware中间件: + +统一以 **handle** 开头,例如 + +```typescript +/**接口返回的用户信息 */ +interface ResponseUserInfo { + userId: string; + userName: string; + userAge: number; +} +/** + * 获取用户信息 中间件 + @param data - 返回的用户信息 + */ +function handleUserInfo(data: ResponseUserInfo): UserInfo { + // *** +} +``` + diff --git a/doc/vue+TS书写规范.md b/doc/vue+TS书写规范.md deleted file mode 100644 index fd7a2208..00000000 --- a/doc/vue+TS书写规范.md +++ /dev/null @@ -1,73 +0,0 @@ -### script-setup写法 - -#### 第一部分书写 - -template - -#### 第二部分 - -script - -##### 一、import的顺序 - -1. vue模块 -2. vue相关类型 -3. vue-router模块 -4. vue-router相关类型 -5. UI框架模块 -6. UI框架相关类型 -7. 第三方依赖 -8. 第三方依赖相关类型 -9. @/enum -10. @/setting -11. @/plugins -12. @/layouts -13. @/views -14. @/components -15. @/hooks -16. @/store -17. @/context -18. @/router -19. @/service -20. @/utils -21. @/interface -22. @/assets -23. 相对路径依赖 - -##### 二、TS类型声明 - -##### 三、defineProps、defineEmits、defineExpose、withDefaults - -1. 定义属性,如: - -`interface Props {` - -​ `name: string;` - -​ `age?: number;` - -`}` - -`const props = withDefaults(defineProps(), {` - -​ `age: 24` - -`})` - -其中name是必须的属性,age是可选属性,通过withDefaults添加默认值 - -2. 定义emit事件 - -`const emit = defineEmits<{` - -​ `(e: 'event-name', param: number): void;` - -`}>()` - -##### 四、响应式use函数 - -有些use函数需要传入响应式的变量参数时,则书写在声明的变量下面。 - -##### 五、变量、函数声明 - -##### 六、vue生命周期函数、nextTick执行 diff --git a/doc/vue书写规范.md b/doc/vue书写规范.md new file mode 100644 index 00000000..7e17ad6e --- /dev/null +++ b/doc/vue书写规范.md @@ -0,0 +1,209 @@ +### script-setup写法 + +#### 第一部分 + +##### template + +#### 第二部分 + +##### script + +##### 一、import的顺序, 依次按照下面的顺序。 + +1. vue模块 + + ```typescript + import { } from 'vue'; + ``` + +2. vue相关类型 + + ```typescript + import type { } from 'vue'; + ``` + +3. vue-router模块 + + ```typescript + import { } from 'vue-router'; + ``` + +4. vue-router相关类型 + + ```typescript + import type { } from 'vue-router'; + ``` + +5. UI框架模块 + + ```typescript + import { } from 'naive-ui'; + ``` + +6. UI框架相关类型 + + ```typescript + import type { } from 'naive-ui'; + ``` + +7. 第三方依赖 + + ```typescript + import BScroll from 'bscroll'; + ``` + +8. 第三方依赖相关类型 + + ```typescript + import type { } from 'bscroll'; + ``` + +9. @/enum + + ```typescript + import { } from '@/enum'; + ``` + +10. @/setting + + ```typescript + import { } from '@/setting'; + ``` + +11. @/plugins + + ```typescript + import { } from '@/plugins'; + ``` + +12. @/layouts + + ```typescript + import { } from '@/layouts'; + ``` + +13. @/views + + ```typescript + import { } from '@/views'; + ``` + +14. @/components + + ```typescript + import { } from '@/components'; + ``` + +15. @/hooks + + ```typescript + import { } from '@/hooks'; + ``` + +16. @/store + + ```typescript + import { } from '@/store'; + ``` + +17. @/context + + ```typescript + import { } from '@/context'; + ``` + +18. @/router + + ```typescript + import { } from '@/router'; + ``` + +19. @/service + + ```typescript + import { } from '@/service'; + ``` + +20. @/utils + + ```typescript + import { } from '@/utils'; + ``` + +21. @/interface + + ```typescript + import { } from '@/interface'; + ``` + +22. @/assets + + ```typescript + import { } from '@/assets'; + ``` + +23. 相对路径依赖 + + ```typescript + import { } from './components'; + ``` + +##### 二、TS类型声明 + +```typescript +interface Props { + /**姓名 */ + name: string; + /**年龄 */ + age?: number; +} +interface Emits { + /** + * 删除事件 + * @param id - 删除项的id + */ + (e: 'delete', id: number): void; +} +``` + + +##### 三、defineProps、defineEmits、withDefaults + +1. 定义属性,如: + +```typescript +const props = withDefaults(defineProps(), { + age: 24 +}); +``` + +其中name是必须的属性,age是可选属性,通过withDefaults添加默认值 + +2. 定义emit事件 + +```typescript +const emit = defineEmits(); +``` + +##### 四、响应式use函数 + +有些use函数需要传入响应式的变量参数时,则书写在声明的变量下面。 + +```typescript +const router = useRouter(); +const route = useRoute(); +``` + +```typescript +/**dom引用 */ +const domRef = ref(null); +const { height: domRefHeight } = useElementSize(domRef); //获取domRef的响应式高度 +``` + + + +##### 五、变量、函数声明 + +##### 六、vue生命周期函数、nextTick执行 + +##### 七、defineExpose diff --git a/doc/命名规范.md b/doc/命名规范.md index e74f48a6..9261f5d8 100644 --- a/doc/命名规范.md +++ b/doc/命名规范.md @@ -1,15 +1,28 @@ -### 驼峰式命名法: -**Pascal Case大驼峰式命名法:** -首字母大写。eg:StudentInfo、UserInfo、ProductInfo -**Camel Case 小驼峰式命名法:** -首字母小写。eg:studentInfo、userInfo、productInfo +### 命名法: + +#### 1.驼峰命名法(小驼峰) + +**getUser** + +#### 2.帕斯卡命名法(大驼峰) + + **GlobalHeader** + +#### 3.短横线命名法 + +**user-center** + +#### 4.下划线命名法 + + **MAX_LENGTH** ### 文件、文件夹命名: -1. 文件夹作为**页面**时用小写字母,包含多个单词时,单词之间建议使用半角的连词线 ( - ) 分隔。 -2. 文件夹作为**组件**时用大写驼峰命名。 -3. 文件作为**组件**时用大写驼峰命名。 -4. 文件作为**use函数**时用小驼峰命名。 -5. 其余文件用小写字母,包含多个单词时,单词之间建议使用半角的连词线 ( - ) 分隔。 + +1. 文件夹作为**路由页面**时用小写字母,包含多个单词时,单词之间建议使用半角的连词线 ( - ) 分隔, 即**短横线命名法**,此时vue文件为**index.vue**。 +2. 文件夹作为**vue组件**时用**大写驼峰命名法**。 +3. 文件作为**vue组件**时用**大写驼峰命名法**。 +4. 文件作为**use函数**时用**小驼峰命名法**。 +5. 其余文件用**短横线命名法**。 ### 变量命名: #### 命名方式 : 小驼峰式命名方法 @@ -23,15 +36,13 @@ is | 判断是否为某个值 | 函数返回一个布尔值。true:为某个 get | 获取某个值 | 函数返回一个非布尔值。 set | 设置某个值 | 无返回值、返回是否设置成功或者返回链式对象。 -- 推荐: - ```javascript -//是否可读 +/** 是否可读 */ function canRead(){ return true; } -//获取姓名 +/** 获取姓名 */ function getName(){ return this.name; } @@ -39,17 +50,23 @@ function getName(){ ### 常量 -#### 命名方法 : 全部大写 -**命名规范 : 使用大写字母和下划线来组合命名,下划线用以分割单词。** -- 推荐: - +#### 命名方法 : 使用大写字母和下划线来组合命名,下划线用以分割单词。 ```javascript const MAX_COUNT = 10; const URL = 'http://www.baidu.com'; ``` -### TS类型 +### TS类型接口interface和type -命名统一使用大写驼峰 +##### 命名方法:大写驼峰 -interface和type使用优先级:能用interface表示的类型就用interface。 +```typescript +interface PersonInfo { + /**姓名 */ + name: string; + /**性别 '0':男; '1': 女; '2': 未知 */ + gender: '0' | '1' | '2'; + /**年龄 */ + age: 25; +} +``` diff --git a/doc/目录.md b/doc/目录.md new file mode 100644 index 00000000..915c952a --- /dev/null +++ b/doc/目录.md @@ -0,0 +1,101 @@ + +## 目录规范 + +```javascript +qitan-pc +├── build //vite构建相关配置和插件 +│ ├── define //定义的全局常量,通过vite构建时注入 +│ ├── env //.env环境文件内容加载插件 +│ └── plugins //构建插件 +│ ├── html.ts //html插件(注入变量,压缩代码等) +│ ├── iconify.ts //iconify图标插件 +│ ├── visualizer.ts //构建的依赖大小占比分析插件 +│ ├── vue.ts //vue相关vite插件 +│ └── windicss.ts //css框架插件 +├── doc //项目相关说明文档 +├── public //公共目录 +│ ├── resource //资源文件夹(不会被打包) +│ └── favicon.ico //网站标签图标 +├── src +│ ├── assets //静态资源 +│ ├── components //全局组件 +│ │ ├── business //业务相关组件 +│ │ ├── common //公共组件 +│ │ └── custom //自定义组件 +│ ├── context //全局上下文(通过provide和inject实现) +│ │ ├── app //从app.vue注入的上下文 +│ │ └── part //局部组件注入的上下文 +│ ├── enum //TS枚举 +│ │ ├── animate.ts //动画枚举 +│ │ ├── business.ts //业务相关枚举 +│ │ ├── common.ts //通用枚举 +│ │ ├── route.ts //路由相关枚举 +│ │ ├── storage.ts //存储相关枚举 +│ │ └── theme.ts //系统主题配置相关枚举 +│ ├── hooks //组合式的钩子函数hooks +│ │ ├── business //业务相关hooks +│ │ └── common //通用hooks +│ ├── interface //TS类型接口 +│ │ ├── business.ts //业务相关类型接口 +│ │ ├── common.ts //通用类型接口 +│ │ └── theme.ts //系统主题配置相关类型接口 +│ ├── layouts //布局组件 +│ │ ├── BasicLayout //基本布局(包含全局头部、侧边栏、底部等公共部分) +│ │ ├── BlankLayout //空白布局组件(单个页面) +│ │ └── RouterViewLayout //路由组件(用于多级路由之间的桥接) +│ ├── plugins //插件 +│ │ └── dark-mode.ts //windicss暗黑模式插件 +│ ├── router //vue路由 +│ │ ├── modules //路由页面(按模块划分) +│ │ ├── permission //路由权限(路由守卫) +│ │ ├── routes //声明的路由 +│ │ └── setup //路由挂载函数 +│ ├── service //网络请求 +│ │ ├── api //接口api +│ │ ├── middleware //请求结果的处理中间件 +│ │ └── request //封装的请求函数 +│ ├── settings //项目初始配置 +│ │ └── theme.ts //项目主题初始配置 +│ ├── store //状态管理 +│ │ └── modules //状态管理划分的模块 +│ ├── styles //样式 +│ │ ├── css //css +│ │ └── scss //scss +│ ├── typings //TS类型声明文件(*.d.ts) +│ ├── utils //全局工具函数 +│ │ ├── auth //用户鉴权 +│ │ ├── common //通用工具函数 +│ │ ├── package //npm依赖 +│ │ ├── router //路由 +│ │ ├── request //请求工具函数 +│ │ └── storage //存储 +│ ├── views //页面 +│ │ ├── about +│ │ ├── component +│ │ ├── dashboard +│ │ ├── document +│ │ ├── multi-menu +│ │ └── system //系统内置页面:登录、异常页等 +│ ├── App.vue //vue文件入口 +│ ├── AppProvider.vue //配置naive UI的vue文件(国际化,loadingBar、message等组件) +│ └── main.ts //项目入口ts文件 +├── .cz-config.js //git cz提交配置 +├── .editorconfig //统一编辑器配置 +├── .env //环境文件 +├── .env.development //环境文件(开发模式) +├── .env.production //环境文件(生产模式) +├── .env.staging //环境文件(自定义staging模式) +├── .eslintignore //忽略eslint检查的配置文件 +├── .eslintrc.js //eslint配置文件 +├── .gitignore //忽略git提交的配置文件 +├── .husky //git commit提交钩子,提交前检查代码格式和提交commit内容的格式 +├── .prettierrc.js //prettier代码格式插件配置 +├── commitlint.config.js //commitlint提交规范插件配置 +├── index.html +├── package.json //npm依赖描述文件 +├── pnpm-lock.yaml //npm包管理器pnpm依赖锁定文件 +├── README.md //项目介绍文档 +├── tsconfig.json //TS配置 +├── vite.config.ts //vite配置 +└── windi.config.ts //windicss框架配置 +``` diff --git a/src/interface/common.ts b/src/interface/common.ts index 77998143..0abe53ca 100644 --- a/src/interface/common.ts +++ b/src/interface/common.ts @@ -18,13 +18,23 @@ interface RouteMeta { order?: number; } +/** 路由配置 */ export type CustomRoute = RouteRecordRaw & { meta: RouteMeta }; +/** 路由路径 */ export type RoutePathKey = keyof typeof EnumRoutePath; +/** 菜单项配置 */ export type GlobalMenuOption = MenuOption & { routeName: string; routePath: string; }; +/** 登录模块 */ export type LoginModuleType = keyof typeof EnumLoginModule; + +/** npm依赖包版本信息 */ +export interface VersionInfo { + name: string; + version: string; +} diff --git a/src/interface/index.ts b/src/interface/index.ts index 7ddaf2c2..81b9d66a 100644 --- a/src/interface/index.ts +++ b/src/interface/index.ts @@ -1,4 +1,3 @@ export * from './business'; export * from './theme'; export * from './common'; -export * from './package'; diff --git a/src/interface/package.ts b/src/interface/package.ts deleted file mode 100644 index 2393e33d..00000000 --- a/src/interface/package.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface VersionInfo { - name: string; - version: string; -} diff --git a/src/layouts/BasicChildLayout/index.vue b/src/layouts/RouterViewLayout/index.vue similarity index 100% rename from src/layouts/BasicChildLayout/index.vue rename to src/layouts/RouterViewLayout/index.vue diff --git a/src/layouts/index.ts b/src/layouts/index.ts index e83dee99..42c5dca9 100644 --- a/src/layouts/index.ts +++ b/src/layouts/index.ts @@ -1,5 +1,5 @@ import BasicLayout from './BasicLayout/index.vue'; -import BasicChildLayout from './BasicChildLayout/index.vue'; import BlankLayout from './BlankLayout/index.vue'; +import RouterViewLayout from './RouterViewLayout/index.vue'; -export { BasicLayout, BasicChildLayout, BlankLayout }; +export { BasicLayout, BlankLayout, RouterViewLayout }; diff --git a/src/router/modules/component.ts b/src/router/modules/component.ts index 7321dd61..291cb232 100644 --- a/src/router/modules/component.ts +++ b/src/router/modules/component.ts @@ -1,6 +1,6 @@ import type { CustomRoute } from '@/interface'; import { EnumRoutePath, EnumRouteTitle } from '@/enum'; -import { BasicLayout, BasicChildLayout } from '@/layouts'; +import { BasicLayout, RouterViewLayout } from '@/layouts'; import { ROUTE_NAME_MAP, setRouterCacheName } from '@/utils'; import ComponentMap from '@/views/component/map/index.vue'; import ComponentVideo from '@/views/component/video/index.vue'; @@ -46,7 +46,7 @@ const COMPONENT: CustomRoute = { { name: ROUTE_NAME_MAP.get('component_editor'), path: EnumRoutePath.component_editor, - component: BasicChildLayout, + component: RouterViewLayout, redirect: { name: ROUTE_NAME_MAP.get('component_editor_quill') }, meta: { requiresAuth: true, diff --git a/src/router/modules/multi-menu.ts b/src/router/modules/multi-menu.ts index 6590ad6c..f92c60c5 100644 --- a/src/router/modules/multi-menu.ts +++ b/src/router/modules/multi-menu.ts @@ -1,6 +1,6 @@ import type { CustomRoute } from '@/interface'; import { EnumRoutePath, EnumRouteTitle } from '@/enum'; -import { BasicLayout, BasicChildLayout } from '@/layouts'; +import { BasicLayout, RouterViewLayout } from '@/layouts'; import { ROUTE_NAME_MAP, setRouterCacheName } from '@/utils'; import MultiMenuFirstSecond from '@/views/multi-menu/first/second/index.vue'; @@ -19,7 +19,7 @@ const MULTI_MENU: CustomRoute = { { name: ROUTE_NAME_MAP.get('multi-menu_first'), path: EnumRoutePath['multi-menu_first'], - component: BasicChildLayout, + component: RouterViewLayout, redirect: { name: ROUTE_NAME_MAP.get('multi-menu_first_second') }, meta: { keepAlive: true, diff --git a/src/utils/service/index.ts b/src/utils/service/index.ts new file mode 100644 index 00000000..54c1b4b7 --- /dev/null +++ b/src/utils/service/index.ts @@ -0,0 +1,121 @@ +import FormData from 'form-data'; +import { isArray } from '../common'; + +type HandleFunc = (...arg: any) => T; +type RequestError = any; +type RequestData = any; +type RequestResult = [RequestError, RequestData]; +/** + * 对请求的结果数据进行格式化的处理 + * @param handleFunc - 处理函数 + * @param requests - 请求结果 + */ +export function handleResponse(handleFunc: HandleFunc, ...requests: RequestResult[]) { + let handleData: any = null; + let error: any = null; + const hasError = requests.some(item => { + const isError = Boolean(item[0]); + if (isError) { + [error] = item; + } + return isError; + }); + if (!hasError) { + handleData = handleFunc(...requests.map(item => item[1])); + } + return [error, handleData] as [any, T]; +} + +/** + * 接口为上传文件的类型时数据转换 + * @param file - 单文件或多文件 + * @param key - 文件的属性名 + */ +export async function transformFile(file: File[] | File, key: string) { + const formData = new FormData(); + if (isArray(file)) { + await Promise.all( + (file as File[]).map(item => { + formData.append(key, item); + return true; + }) + ); + } else { + await formData.append(key, file); + } + return formData; +} + +const ERROR_STATUS = { + 400: '400: 请求出现语法错误', + 401: '401: 用户未授权~', + 403: '403: 服务器拒绝访问~', + 404: '404: 请求的资源不存在~', + 405: '405: 请求方法未允许~', + 408: '408: 网络请求超时~', + 500: '500: 服务器内部错误~', + 501: '501: 服务器未实现请求功能~', + 502: '502: 错误网关~', + 503: '503: 服务不可用~', + 504: '504: 网关超时~', + 505: '505: http版本不支持该请求~' +}; +type ErrorStatus = keyof typeof ERROR_STATUS; + +/** + * 网络请求错误状态处理 + * @param error - 错误 + */ +export function errorHandler(error: any): void { + const { $message: Message } = window; + if (error.response) { + const status = error.response.status as ErrorStatus; + Message?.error(ERROR_STATUS[status]); + return; + } + if (error.code === 'ECONNABORTED' && error.message.includes('timeout')) { + Message?.error('网络连接超时~'); + return; + } + if (!window.navigator.onLine || error.message === 'Network Error') { + Message?.error('网络不可用~'); + return; + } + Message?.error('请求错误~'); +} + +/** + * 连续的请求错误依此显示 + * @param duration - 上一次弹出错误消息到下一次的时间(ms) + */ +export function continuousErrorHandler(duration: number) { + let errorStacks: string[] = []; + function pushError(id: string) { + errorStacks.push(id); + } + function removeError(id: string) { + errorStacks = errorStacks.filter(item => item !== id); + } + function handleError(id: string, callback: Function) { + callback(); + setTimeout(() => { + removeError(id); + }, duration); + } + + function handleContinuousError(callback: Function) { + const id = Date.now().toString(36); + const { length } = errorStacks; + if (length > 0) { + pushError(id); + setTimeout(() => { + handleError(id, callback); + }, duration * length); + } else { + pushError(id); + handleError(id, callback); + } + } + + return handleContinuousError; +}