前言
最近在做部门系统平台的改版工作。不同于去年经手的 1.0 到 2.0 的 UI 改版工作,这次迭代,是将整个可视化平台抽离模块,将原本耦合很严重的各个模块抽离出来单独形成个项目。
这就需要对整个庞大的项目代码有很好的认识。
很惭愧的是,这一两年时间都没好好看全整个项目的代码,对整体项目的运行都不甚了解。
也很幸运的是,这次改版由我主导独立实现,这就很利于自身技术的提升。
整个系统平台项目的模块分为平台、用户中心、数据中心、权限中心、展示端和编辑器。其中数据中心、权限中心在当时部署的时候就考虑到以后需要拆分的打算,所以都是独立的,比较困难的就是拆分和平台耦合很深的用户中心、展示端和编辑器。
为了方便对整个平台项目代码有更好的认识,我计划的是按照平台代码架构先自己实现一套类似的 vue 项目,特意在便签上列了计划固定在桌面上
![43ac7e4a85d0ba9d0e52538e86025f68.png](https://i-blog.csdnimg.cn/blog_migrate/00154e62c248e7ad4a198b0098e90296.png)
按进度进行,目前也算是实现了一小阶段,正好记录一下。
引入 ts
之前有文章写过了,点击这里查看
但是有反馈说其中 webpack 的配置按照文中进行,最后运行会有报错。我在附篇文章里重新给出完整的配置文件。
路由
目前我的项目中 src 下目录结构是这样的:
![9824f9db9b10a42a92c3c4a53c5817b7.png](https://i-blog.csdnimg.cn/blog_migrate/c145d94933a1cb5d2874e3441766f88a.png)
所有的页面代码放在 views 中,对应的在 router 中有相应页面路由。
例如登陆页面,router 中新建个 login 的路由文件,代码如下:
import Login from "../views/login/index.vue";
export const loginRouter = {
path: "/login",
key: "login",
name: "login",
component: Login
};
那路由的 index 中直接引入就可以了,代码如下:
import Vue from "vue";
import Router from "vue-router";
import { loginRouter } from "./login";
import { layoutRouter } from "./layout";
Vue.use(Router);
export const mainRoutes = [loginRouter, layoutRouter];
export default new Router({
routes: mainRoutes
});
以后每多加个页面,都是在 views 中新建个页面文件夹,再在 router 中新建个页面路由文件,最后要在 index 中引入,加在 mainRoutes 里。
api
这里我以登录页测试,直接引用了部门已有的后端接口。如果有水平自己写 java 接口的话,只需要在 apiConfig 文件中修改个后端接口的 url 就可以了。
新建个 api 文件夹,专门存放所有的后端接口。
首先需要个入口,得清楚调用哪里后端的接口,就像个大门,这块逻辑写在个 apiConfig 的文件里,当然这个文件名随便起,代码如下:
const serverUrl = "https://api:port";
export const apiPrefix: string = `${serverUrl}/`; // 项目请求网址
export const serverNamesPrefix = {
dataServer: "",
portal: "",
fileServer: "",
jobServer: ""
};
只需要将 serverUrl 替换成你的后端服务地址,没有也没事,并不影响接下来前端的实现,只是影响了最后查看效果时候调用后端接口失败而已。
大门有了,下面就需要搞把钥匙,没有钥匙咋进门用到后端服务拿到数据库里的数据呢。
这里钥匙实现的方式是用的 axios,首先项目里安装一下,
yarn add axios
新建个 base 文件,这里写用 axios 发送 ajax 请求的实现代码:
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
import { merge } from "lodash";
import { apiPrefix } from "./apiConfig";
import { urlUtils } from "../utils/url";
const apiOpenList: string[] = [
// 登录
"auth/password"
];
// http request 拦截器
axios.interceptors.request.use(
(config: AxiosRequestConfig) => {
const { url, headers } = config;
if (!headers["jwt-token"]) {
const index = apiOpenList.findIndex(apiUrl => {
if (url.indexOf(apiUrl) > -1) {
return true;
} else {
return false;
}
});
if (index === -1) {
urlUtils.noAuthRedirect();
}
}
return config;
},
error => {
return Promise.reject(error);
}
);
// http Response响应拦截器
axios.interceptors.response.use(
response => {
if (response.data) {
switch (response.data.status) {
case 10014: // 返回401,清除token信息并跳转到登录页面
case 10013: // auth.authz.jwt.signature.error
case 10001: // auth.authz.jwt.signature.error
api.clearJwtToken();
urlUtils.noAuthRedirect();
break;
}
}
return response;
},
(error: any) => {
if (error.response) {
return Promise.reject(error.response.data);
} else {
return Promise.reject({
status: 0,
message: error.message
});
}
}
);
class API<T> {
// 请求的参数
public ajaxMap = {};
// jwt-token操作
public setJwtToken(token: string) {
window.sessionStorage.setItem("jwt-token", token);
}
public getJwtToken() {
window.sessionStorage.getItem("jwt-token") || "";
}
public clearJwtToken() {
window.sessionStorage.removeItem("jwt-token");
}
// 请求方式
public get(url: string, params?: any): Promise {return this.baseRequest(url, { method: "GET", params: params || {} });
}
public post(url: string, params?: any): Promise {return this.baseRequest(url, { method: "POST", data: params || {} });
}
private baseRequest(url: string, options?: AxiosRequestConfig): Promise {const defaultConfig: AxiosRequestConfig = {method: "GET",timeout: 300000,url: `${apiPrefix}${url}`,headers: {"Content-Type": "application/json"
},withCredentials: true
};const config = merge(defaultConfig, options || {});return new Promise(resolve => {
axios(config)
.then((res: AxiosResponse) => {
resolve(res.data);
})
.catch(error => {const result: AxiosResponse = {data: {success: false,status: error.status || 500,data: "",message: `${error.message}`,msg: `${error.message}`
},status: 0,statusText: "",headers: "",config: {}
};
resolve(result.data);
});
});
}
}export const api = new API();
在 base 中还加了个拦截器的实现,不需要的话可以直接删除那两段代码。
这里拦截器的代码里主要是判断了登录的情况,根据约定好的后端接口名,首次登录时判断,进行路由重定向,将首页的路由修改成登录页的。
哇,终于点题了。
路由重定向
直接复制如上代码到 base 里一定是会报错的,毕竟还有文件没引用。
就是这个 urlUtils。
src 先新建个 utils 文件夹,再在里面新建个 url 文件,代码如下:
const { origin, href } = window.location;
let baseUrl = origin;
class UrlUtils {
// 重定向
public redirect(url: string) {
window.location.replace(url);
}
public goCallbackUrl(url: string) {
this.redirect(`${url}`);
}
public noAuthRedirect() {
this.redirect(
`${baseUrl}/#/login?redirect=${encodeURIComponent(href)}`
);
}
public logout() {
this.redirect(`${baseUrl}/#/login`);
}
}
export const urlUtils = new UrlUtils();
base 应该还有报错,最后 new API 的时候它的类型报错了。
因为没有声明呀。
在 src 下的 types 下的 global.d.ts 中,添加如下代码:
/**
* 内部接口返回格式
* success:接口调用状态 成功或者失败
* status: 接口返回状态值 0成功 10006 没有获取到数据
* data: 返回的数据 默认为任何值,根据具体接口设置不同的值类型
* mes:返回的错误信息。
*/
declare interface ApiResponse {success: boolean;
status: number;
data: T;
msg?: string;
[k: string]: any;
}
这样,base.ts 就没问题了。
顺便根据后端提供的接口文档,在 api 中新建个登录接口文件,就是那个 auth/password 的接口名:
import { api } from "./base";
export class Auth {
// 登录
public static login(params: any): Promise {return api.post("auth/password", params);
}
}
当然了,关于的后端的都需要根据个人实际情况来操作,你又用不到我这儿的后端环境。
关于为啥用路由重定向,又如何用了重定向,这里简单说一下。
在路由文件中,layout 的路由 path 是"/",login 的路由 path 是 "/login",那就是应该在浏览器输入http://localhost:8080/#/login才可以访问,不然默认的话就是 layout 的页面。
然而首次登录的时候,就需要默认展示登录页,用户登录之后才可以进入 layout。
就像如下这个样子:(我在 login 页写了"登陆页面",layout 页写了"主页面")
![7298eb9c5b643939448b111d01b6ba56.gif](https://i-blog.csdnimg.cn/blog_migrate/c3cff9b5feb396ba79642a9791c76b94.gif)
按理说,输入 localhost:8080 之后,路由渲染的应该是 layout 页面,页面应该出现"主页面的字样",但实际上是自动跳转到了 login。
前文中说到了 api 的 base 中,重定向的实现是在拦截器里,其实就是那句是否首次登录判断里调用了 urlUtils.noAuthRedirect(),在输入 localhost:8080 之后,调用 layout 页面中需要有 api 的调用,才能触发 base 中代码。