跨域: 非同源(协议, 域名, 端口一致)
解决开发环境的跨域问题
vue-cli配置webpack的反向代理
在vue.config.js
里设置 module.exports = {
devServer: {
}
处理token:
在src/utils/auth.js中:
import Cookies from 'js-cookie' // 本地存储
const TokenKey = 'hrsaas-ihrm-token' // 设定一个独一无二的key
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token) {
return Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}
在store/user.js中:
import { getToken, setToken, removeToken } from '@/utils/auth'
import { login } from '@/api/user'
const state = {
token: getToken(), // 设置token为共享状态, 初始化vuex的时候, 就先从缓存中读取
}
const mutations = {
setToken(state, token) {
state.token = token
setToken(token)
},
removeToken(state) {
state.token = null // 先删除vuex的token
removeToken() // 再清除本地存储中的
}
}
const actions = {
async login(context, data) {
const result = await login(data)
// 判断是否成功 是否有token值
// if (result.data.success) {
// const token = result.data.data
// context.commit('setToken', token)
// }
// 已经在响应拦截器处理过了
context.commit('setToken', result)
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
处理axios的响应拦截器
在utils/request.js中:
import axios from 'axios'
import { Message } from 'element-ui'
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 5000, // 设置超时时间
})
service.interceptors.request.use()
// 响应拦截器
service.interceptors.response.use(response => {
// axios默认加了一层data
const { success, message, data } = response.data
// 要根据success的成功与否决定下面的操作
if (success) {
return data
} else {
// 失败了不能进then 应该进catch
Message.error(message) // 提示错误信息
return Promise.reject(new Error(message))
}
}, error => {
Message.error(error.message) // 提示错误信息
return Promise.reject(error) // 返回执行错误 让当前的执行链跳出成功 直接进入catch
})
export default service
主页的token拦截处理
在src/permission.js
中:
// 权限拦截在路由跳转 导航守卫
import router from "@/router"; // 引入路由实例
import store from "@/store"; // 引入vuex store实例
import NProgress from 'nprogress' // 引入一份进度条插件
import 'nprogress/nprogress.css' // 引入进度条样式
// 不需要导出 因为已经在main.js引入了 只需要让代码执行即可
// 前置守卫
// next是前置守卫必须执行的钩子 next必须执行 如果不执行 页面就死了
// next() 放过
// next(false) 跳转终止
// next(地址) 跳转到某个地址
const whiteList = ['/login', '/404'] // 定义白名单
router.beforeEach((to, from, next) => {
NProgress.start() // 开启进度条
if (store.getters.token) {
// 有token
if (to.path === 'login') {
// 如果访问的是 登录页
next('/') // 跳到主页
} else {
next() // 不去登录页 想去哪就去哪 直接放行 跳转
}
} else {
// 没有token
if (whiteList.includes(to.path)) {
// 表示要去的地址在白名单
next()
} else {
next('/login')
}
}
NProgress.done() // 手动强制关闭一次 为了解决 手动切换地址时 进度条的不关闭的问题
})
// 后置守卫
router.afterEach(function () {
NProgress.done() // 关闭进度条
})
将数组数据转化成树形结构
实现:
methods: {
async getDepartments() {
const result = await getDepartments();
this.company = { name: result.companyName, manager: "负责人" };
this.departs = result.depts; // 需要将其转化成树形结构
const data = tranListToTreeData(result.depts, "");
console.log(data);
},
},
// function testFn(list, pid) {
// var arr = []
// list.forEach(item => {
// if (item.pid === pid) {
// arr.push(item)
// }
// })
// return arr
// }
export function tranListToTreeData(list, pid) {
var arr = []
list.forEach(item => {
if (item.pid === pid) {
// 只要进到if里的 都是一级的数据(pid:'')
// 在这里需要去判断一级的数据的id有没有其他人作为pid, 如果有就说明这个一级是有二级目录的
// const children = testFn(list, item.id)
const children = tranListToTreeData(list, item.id)
if (children.length > 0) {
item.children = children
}
arr.push(item)
}
})
return arr
}
自定义校验规则, 筛选:
// 现在定义一个函数 这个函数的目的是 去找 同级部门下 是否有重复的部门名称
const checkNameRepeat = async (rule, value, callback) => {
// value就是表单里用户输入的值
const { depts } = await getDepartments();
var arr = depts.filter((item) => {
return item.pid === this.treeNode.id;
});
var flag = arr.some((item) => {
return item.name === value;
});
flag ? callback(new Error(`同级部门已经有${value}部门了`)) : callback();
};
// 检查编码重复
const checkCodeRepeat = async (rule, value, callback) => {
const { depts } = await getDepartments();
var flag = depts.some((item) => {
return item.code === value && value;
});
flag
? callback(new Error(`组织架构中已经有部门使用${value}编码了`))
: callback();
};
使用:
{ trigger: "blur", validator: checkCodeRepeat },
sync修饰符
只要用sync修饰,就可以省略父组件的监听和方法,直接将值赋值给showDialog
// 子组件 update:固定写法 (update:props名称, 值)
this.$emit('update:showDialog', false) //触发事件
// 父组件 sync修饰符
<child :showDialog.sync="showDialog" />
// this.$parent 可以直接调用到父组件的实例 实际上就是父组件this
this.$parent.getEmployeeList(); 这样可以直接调用父的方法
导出:
// 导出所有
exportAll() {
const headers = {
手机号: "mobile",
姓名: "username",
入职日期: "timeOfEntry",
聘用形式: "formOfEmployment",
转正日期: "correctionTime",
工号: "workNumber",
部门: "departmentName",
};
import("@/vendor/Export2Excel").then(async (excel) => {
// console.log(excel);
const { rows } = await getEmployeeList({ page: 1, size: 1000000 });
const res = this.formatJson(headers, rows);
excel.export_json_to_excel({
header: res.headerKey,
data: res.arr,
filename: "员工数据",
multiHeader: [["基本信息", "", "", "", "", "", "部门"]],
merges: ["A1:F1", "G1:G2"],
// 复杂表头: 合并
});
});
},
formatJson(headers, rows) {
const headerKey = Object.keys(headers);
const arr = [];
rows.forEach((item) => {
const itemArr = [];
headerKey.forEach((key) => {
if (["timeOfEntry", "correctionTime"].includes(headers[key])) {
item[headers[key]] = new Date(item[headers[key]]).toLocaleString();
}
if (headers[key] === "formOfEmployment") {
const flag = EmployeeEnum.hireType.find(
(obj) => obj.id === item[headers[key]]
);
return flag ? flag.value : "未知";
}
itemArr.push(item[headers[key]]);
});
arr.push(itemArr);
});
return {
headerKey,
arr,
};
},
处理数据格式:
async success({ header, results }) {
// console.log(data);
// 现在的数据: [{入职日期:'xxx', 姓名:'xx'}]
// 期望的数据: [{timeOfEntry:'xxx', mobile:'xx'}]
const userRelations = {
入职日期: "timeOfEntry",
手机号: "mobile",
姓名: "username",
转正日期: "correctionTime",
工号: "workNumber",
};
const arr = [];
results.forEach((item) => {
const userInfo = {};
// 遍历 取出所有的键
Object.keys(item).forEach((key) => {
userInfo[userRelations[key]] = item[key];
if (["correctionTime", "timeOfEntry"].includes(userRelations[key])) {
userInfo[userRelations[key]] = this.formatDate(item[key], "-");
} else {
userInfo[userRelations[key]] = item[key];
}
});
arr.push(userInfo);
});
await importEmployee(arr); // 调用导入接口
this.$router.back();
},
日期时间处理:
formatDate(numb, format) {
const time = new Date((numb - 1) * 24 * 3600000 + 1);
time.setYear(time.getFullYear() - 70);
const year = time.getFullYear() + "";
const month = time.getMonth() + 1 + "";
const date = time.getDate() - 1 + "";
if (format && format.length === 1) {
return year + format + month + format + date;
}
return (
year +
(month < 10 ? "0" + month : month) +
(date < 10 ? "0" + date : date)
);
},
文件上传的三种方式:
1.
<!-- el-upload组件 该组件内部会自己使用原生的xhr进行请求的发送 -->
<!-- list-type 是列表的类型 可选值text/picture/picture-card -->
<!-- action 是上传的地址 -->
<!-- name 是上传的文件字段名 -->
<!-- headers 是请求头的信息 -->
<!-- on-success 是上传成功的钩子函数, 这里可以获取到服务器返回的数据 -->
<el-upload
action="http://124.233.14.236:8060/admin/common/upload?type=images"
list-type="picture-card"
name="file"
:headers="{ 'x-token': token }"
:on-success="onSuccess"
>
</el-upload>
2.
<!-- auto-upload 自动上传功能, 默认为true -->
<!-- http-request 覆盖默认的上传行为, 自己写上传的逻辑功能 -->
<!-- file-list 是默认展示的图片内容, 一般用于做回显功能, file-list 的数据不是双向绑定的 -->
<el-upload
action="#"
list-type="picture-card"
:http-request="httpRequest"
:file-list="fileList"
:on-remove="onRemove"
>
<i class="el-icon-plus"></i>
</el-upload>
methods: {
onRemove(file) {
console.log(file);
},
httpRequest(file) {
// 准备数据
let fd = new FormData();
fd.append("file", file);
// 发送请求
axios
.post(
"http://124.233.14.236:8060/admin/common/upload?type=images",
fd,
{
headers: { "x-token": this.token },
}
)
.then((res) => {
console.log(res.data);
});
},
},
3.
<el-upload
list-type="picture-card"
:file-list="fileList"
:on-preview="onPreview"
:on-remove="onRemove"
:on-change="onChange"
:before-upload="beforeUpload"
:http-request="httpRequest"
action="#"
:class="{ hidden: fileComputed }"
>
onRemove移除事件
onPreview预览事件
onChange(file, fileList) {
this.fileList = fileList.map((item) => item);
},
// 文件发送改变的时候触发==> 添加文件, 上传成功和上传失败时都会被调用
// file 是选择的文件信息
// fileList 是最新的上传列表
// 不能使用push, 原因是因为会调用很多次, 就会不断push到里面去
beforeUpload上传前的操作
httpRequest上传
RBAC权限设计思想:
用户--角色--权限