模板项目
本节带来一套基于 vue3 + element + jiess
的一套深度封装模板项目
sh
│ .env
│ .env.development
│ .env.production
│ .gitignore
│ index.html
│ jiess.config.js
│ package.json
│ README.md
│ vite.config.js
│ windi.config.js
│
├─public
│
└─src
│ .jiess # Jiess基础库
│ App.vue
│ main.js
│
├─assets # 资源目录
│
├─components
│ └─layouts # 系统布局组件
│
├─jiessConfig
│ │ consts.js
│ │ index.js # Jiess注册配置
│ │
│ └─http
│ index.js
│ interceptor.js # 请求拦截器
│
├─router # 路由相关
│ │ index.js
│ │ main.js
│ │
│ ├─interceptor
│ │ index.js
│ │
│ ├─modules
│ │
│ └─utils
│ index.js
│
├─services # 接口配置目录
│ api-common.js
│
├─store # pinia全局状态管理
│ index.js
│
└─views
├─common
│ └─login # 登录页
│
├─exception # 异常页
│ 403.vue
│ 404.vue
│ 500.vue
│
└─main
├─base
│ ├─BaseDialog # 弹框示例页
│ │
│ └─BaseForm # 表单示例页
│
├─home # 首页
│
└─table
├─SuperTable # SuperTable列表示例
│
└─JiessTable # JiessTable列表示例
本项目是一个后台管理系统,实现了较完整的功能,以下是各功能点介绍
接口调用
采用@jiess/utils
库的JiessApi作为后端资源请求工具,与axios
相比,配置更简单
定义接口
接口放置在src/services
目录中,建议以模块或微服务为维度新建接口文件
js
// src/services/api-common.js
// 导入JiessApi实例
import { api1 } from '@/jiessConfig/http';
// 列表管理接口组
export const mangerTableApi = api1.create({
list: ['POST', '/list'],
add: ['POST', '/add'],
del: ['DELETE', '/del'],
edit: ['PUT', '/edit'],
detail: ['GET', '/detail'],
});
以上使用resetFul规范构建了增删改查和列表共5个接口
JiessApi实例化
定义接口时,引入了JiessApi实例,下面介绍其实例化时的参数配置
js
// src/jiessConfig/http
import JiessApi from '@jiess/utils/lib/JiessApi';
// 引入拦截器,控制请求和响应数据
import { requestFunc, responseErrorFunc, responseSuccessFunc } from './interceptor.js';
const baseURL = 'https://fc-mp-2cb6262c-c104-4694-a95a-162884375feb.next.bspapp.com/manger-table';
// 基础的请求配置
export const httpBaseConfig = {
timeout: 5000,
interceptors: {
responseSuccess: responseSuccessFunc,
responseError: responseErrorFunc,
request: requestFunc,
},
};
// 有时会根据不同baseURL构建多套配置,这里只用到一套
const httpConfig1 = { baseURL, ...httpBaseConfig };
export const api1 = new JiessApi(httpConfig1);
JiessApi拦截器
实例化接口时,引入了JiessApi拦截器,下面介绍其拦截过程
js
// src/jiessConfig/http/interceptor
import { ElNotification } from 'element-plus';
import Loading from '@jiess/utils/lib/JiessLoading';
export function responseSuccessFunc({ responseText, config }) {
// 响应时参数更新Loading状态
Loading.response(config);
let response = JSON.parse(responseText);
if (response.code === 0) {
// 将code为0视为正确响应
return response.data;
} else {
// 其他code视为错误响应
errorHandler(response);
return Promise.reject(response);
}
function errorHandler(res) {
const { code, message } = res;
// 根据不同的错误码,定制不同的错误逻辑处理
if (code === '401') {
// token校验失败
} else {
ElNotification({ message, type: 'error' })
}
}
}
export function responseErrorFunc({ responseText, config }) {
// 响应时参数更新Loading状态
Loading.response(config);
ElNotification({ message: responseText, type: 'error' })
}
export function requestFunc(params) {
// 请求时参数更新Loading状态
let requestObj = Loading.request(params);
// 发起请求前,可对请求参数统一处理
return requestObj;
}
JiessLoading加载器
定义拦截器时,引入了JiessLoading加载器,下面介绍它的原理和用法
绑定加载监听
- 单一接口的请求响应状态控制Loading的加载
js
import { exampleApi } from '@/services/api-common.js'
// 获取exampleApi中list接口的标识key
const apiTag = exampleApi.listKey;
// 同一接口只要在同一时间不被不同参数调用即可
new JiessLoading(apiTag, [apiTag], boo => {
// 响应式更新加载组件的状态
spinning.value = boo;
});
- 多个接口的请求响应状态控制Loading的加载
js
import { testApi1,testApi2 } from '@/services/api-common.js';
// 获取list接口的标识key
const tag1 = testApi1.listKey;
// 获取detail接口的标识key
const tag2 = testApi1.detailKey;
// 获取list接口的标识key
const tag3 = testApi2.listKey;
// 组合为标识数组
const tags = [tag1,tag2,tag3];
// 构建一个唯一标识,即任意字符串
const uniqueKey = tags.join();
// 其中一个接口发起请求则更新为加载状态
// 三个接口全部响应,则更新为加载完成
new JiessLoading(uniqueKey, tags, boo => {
// 响应式更新加载组件的状态
spinning.value = boo;
});
发起请求时
js
Loading.request(params)
如果对该接口绑定了加载监听,则会更新当前接口为请求状态为true
接口响应时
js
Loading.response(config)
如果对该接口绑定了加载监听,则会更新当前接口为请求状态为false
登录流程
项目中实现了简单的登录流程,登录完成时,自动存储token并跳转主页
登录请求
- 登录页面位于
@views/common/login/index.vue
- 登录页面路由为
/user-login
下面为登录页发起登录时的实际代码:
js
area.Button({
type: 'primary',
size: 'large',
// 控制按钮自带的loading状态
loading: $val(loadingSure, 'value'),
style: { width: 'calc(50% - 6px)' },
onClick: async () => {
loadingSure.value = true;
// 获取表单数据:loginName 和 password
const values = await action.validateFields();
// 传入参数并调用登录接口
const data = await userInfoApi.login(values);
// 用户完整信息存放在sessionStorage
sessionStorage.userData = JSON.stringify(data);
// 用户的token存放在cookie中
tokenCookie.set(data.accessToken);
loadingSure.value = false;
// 跳转到主页
router.push('/');
}
}, '登录')
登录验证
- 登录验证过程位于路由拦截器
@router/interceptor/index.js
目前仅验证token是否存在,存在则允许进入主页,下面为实际代码:
js
async function toCheckToken(to, router) {
const { noAuth } = to.meta;
if (!noAuth) {
const token = tokenCookie.get();
try {
if (token) {
// token存在,允许进入主页
await handleTokenExist(to, router);
} else {
// token不存在,跳转登录页
await handleTokenNotExist(to);
}
} catch (newto) {
return Promise.reject(newto);
}
}
}
function handleTokenNotExist(to) {
if (to.path !== routeLogin.path) {
return Promise.reject({ path: routeLogin.path });
}
}
权限控制
项目中,将动态路由和权限相结合
路由的配置
- 初始路由仅有登录页
/user-login
和主页/home
- 主体路由位于
@router/modules
目录- 该目录中的文件会被视为路由
- 该目录中的路由会被自动引入
定义主体路由
建议以模块名为维度在@router/modules
中构建路由文件,其内容会自动解析为路由信息
js
// @router/modules/base.js
import { Star, Setting, Notebook, Document, HomeFilled, PictureFilled } from '@element-plus/icons-vue';
import { getMainRoute } from '../utils';
export default {
sort: 200,
name: 'base',
label: '基础示例',
icon: Setting,
authKey: 'forcePass',
routes: [{
...getMainRoute('base-base-dialog', '基础弹框', {
icon: Star
}),
component: () => import('@/views/main/base/BaseDialog')
}, {
...getMainRoute('base-base-form', '基础表单', {
icon: Star
}),
component: () => import('@/views/main/base/BaseForm')
}]
};
以上路由会在菜单中渲染名为基础示例的分组,分组下的routes为路由信息
getMainRoute 和 getRouteItem
这两个函数为基础配置函数,会自动携带默认配置信息
- getRouteItem 应用于不在菜单中显示的页面
- getMainRoute 应用于需要菜单中显示的页面
js
// @router/utils/index.js
import { firstUpperCase } from '@jiess/utils';
// 处理路由的函数
export function getRouteItem(page, meta, custom) {
return getInitRoute(page, meta, {
useAnimation: true,
useLoading: true,
useCache: false,
hideMenu: true,
useTab: false,
noAuth: true,
...custom
});
}
export function getMainRoute(page, meta, custom) {
return getInitRoute(page, meta, {
useAnimation: true,
useLoading: true,
useCache: true,
hideMenu: false,
useTab: true,
noAuth: false,
...custom
});
}
function getInitRoute(page, meta, baseConfig) {
const name = page
.split('-')
.map(str => firstUpperCase(str))
.join('');
if (typeof meta === 'string') {
meta = { ...baseConfig, label: meta, name };
} else {
meta = { ...baseConfig, ...meta, name };
}
return { path: `/${page}`, name, meta };
}
示例代码为这两函数完整代码,可以根据需求,灵活控制配置参数
代码获取
本模板项目是免费开源的,使用Git克隆即可获取
sh
https://gitee.com/wgzimg/jiess-template-project.git
关注公众号
为了更深入的探讨,欢迎大家扫描下面的二维码,关注Jiess官方微信公众号
在公众号中,作者会发布文章和资料,介绍Jiess在实际项目中的应用,感谢大家的支持