Skip to content

模板项目

本节带来一套基于 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官方公众号

在公众号中,作者会发布文章和资料,介绍Jiess在实际项目中的应用,感谢大家的支持