docs(projects): 文档更新

This commit is contained in:
Soybean 2021-11-10 20:44:43 +08:00
parent 6a543422ff
commit 448d28db2e
15 changed files with 563 additions and 132 deletions

View File

@ -12,7 +12,6 @@
"dbaeumer.vscode-eslint",
"miguelsolorio.fluent-icons",
"mhutchie.git-graph",
"donjayamanne.githistory",
"eamodio.gitlens",
"lokalise.i18n-ally",
"afzalsayed96.icones",

View File

@ -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配置

47
doc/TS写法规范.md Normal file
View File

@ -0,0 +1,47 @@
### 1.interface和type
##### interface和type使用优先级能用interface表示的类型就用interface。
### 2.请求函数
#### api接口
统一以 **fetch** 开头,例如:
```typescript
/**
* 获取用户信息
* @param id - 用户唯一标识id
*/
function fetchUserInfo(idstring) {
// ***
}
/**
* 删除列表项
* @param id - 列表id
*/
function fetchDeleteListItem(idstring) {
// ***
}
```
#### middleware中间件
统一以 **handle** 开头,例如
```typescript
/**接口返回的用户信息 */
interface ResponseUserInfo {
userId: string;
userName: string;
userAge: number;
}
/**
* 获取用户信息 中间件
@param data - 返回的用户信息
*/
function handleUserInfo(data: ResponseUserInfo): UserInfo {
// ***
}
```

View File

@ -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<Props>(), {`
`age: 24`
`})`
其中name是必须的属性age是可选属性通过withDefaults添加默认值
2. 定义emit事件
`const emit = defineEmits<{`
`(e: 'event-name', param: number): void;`
`}>()`
##### 四、响应式use函数
有些use函数需要传入响应式的变量参数时则书写在声明的变量下面。
##### 五、变量、函数声明
##### 六、vue生命周期函数、nextTick执行

209
doc/vue书写规范.md Normal file
View File

@ -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<Props>(), {
age: 24
});
```
其中name是必须的属性age是可选属性通过withDefaults添加默认值
2. 定义emit事件
```typescript
const emit = defineEmits<Emits>();
```
##### 四、响应式use函数
有些use函数需要传入响应式的变量参数时则书写在声明的变量下面。
```typescript
const router = useRouter();
const route = useRoute();
```
```typescript
/**dom引用 */
const domRef = ref<HTMLElement | null>(null);
const { height: domRefHeight } = useElementSize(domRef); //获取domRef的响应式高度
```
##### 五、变量、函数声明
##### 六、vue生命周期函数、nextTick执行
##### 七、defineExpose

View File

@ -1,15 +1,28 @@
### 驼峰式命名法:
**Pascal Case大驼峰式命名法**
首字母大写。egStudentInfo、UserInfo、ProductInfo
**Camel Case 小驼峰式命名法:**
首字母小写。egstudentInfo、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;
}
```

101
doc/目录.md Normal file
View File

@ -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框架配置
```

View File

@ -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;
}

View File

@ -1,4 +1,3 @@
export * from './business';
export * from './theme';
export * from './common';
export * from './package';

View File

@ -1,4 +0,0 @@
export interface VersionInfo {
name: string;
version: string;
}

View File

@ -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 };

View File

@ -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,

View File

@ -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,

121
src/utils/service/index.ts Normal file
View File

@ -0,0 +1,121 @@
import FormData from 'form-data';
import { isArray } from '../common';
type HandleFunc<T> = (...arg: any) => T;
type RequestError = any;
type RequestData = any;
type RequestResult = [RequestError, RequestData];
/**
*
* @param handleFunc -
* @param requests -
*/
export function handleResponse<T>(handleFunc: HandleFunc<T>, ...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;
}