diff --git a/package.json b/package.json index e9ea0a7..138f7da 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "dingtalk-jsapi": "^3.0.20", "echarts": "5.2.2", "element-ui": "2.15.6", + "jsonwebtoken": "^9.0.2", "nprogress": "0.2.0", "screenfull": "5.2.0", "serve": "^14.2.0", diff --git a/src/App.vue b/src/App.vue index 52c4cdc..a0bdecd 100644 --- a/src/App.vue +++ b/src/App.vue @@ -10,6 +10,8 @@ import setIntroduction from "@/utils/setIconfont.js"; import { Local } from "@/utils/storage.js"; import Setings from "@/layout/navBars/breadcrumb/setings.vue"; import Upgrade from "@/layout/upgrade/index.vue"; +import jsonwebtoken from 'jsonwebtoken' + export default { name: "App", components: { Setings, Upgrade }, @@ -17,6 +19,10 @@ export default { this.initSetIconfont(); this.openSetingsDrawer(); this.getLayoutThemeConfig(); + console.log('jsonwebtoken') + const token = 'eyJ4NXQjUzI1NiI6IkRIS0NvOTNWbURZd0tRNk5xX3BEOFdnNDUtRTBzQ2h6c3VKNThjbmVzeDAiLCJraWQiOiJkc3dqIiwiYWxnIjoiUlMyNTYifQ.eyJjYW1wdXNJbmZvIjpbeyJjYW1wdXNJZCI6ODUxNDY2OTMyLCJjYW1wdXNOYW1lIjoi6IOW6IOW5pWZ6IKy6ZuG5ZuiIiwiY2xhc3NJbmZvIjpbeyJjbGFzc0lkIjo4NTEzODI3ODYsImNsYXNzTmFtZSI6IuWkp-ePrTHnj60iLCJjbGFzc1JvbGVMaXN0IjpbeyJyb2xlIjoiZ3VhcmRpYW4iLCJpc0FkdmlzZXIiOjB9LHsicm9sZSI6InRlYWNoZXIiLCJpc0FkdmlzZXIiOjB9XX1dfV0sInN1YiI6IuW8oOS4iSIsImlzcyI6Imh0dHA6Ly9hdXRoLmNwb2xhci5jbiIsInVzZXJpZCI6IjIxMzE1ODQ2MzkzMjMxNjkwMSIsImF1dGhvcml0aWVzIjpbInVzZXIucmVhZCIsInRlc3QiLCJ1c2VyX2luZm8iLCJvcGVuaWQiLCJwcm9maWxlIiwiU0NPUEVfdGVzdCIsIlNDT1BFX3Rlc3QucmVhZCJdLCJhdWQiOiJ0ZXN0LWNsaWVudCIsIm5iZiI6MTcxMDEyOTA5NSwicm9sZUluZm8iOlt7InJvbGVJZCI6Mzc1OTk0NzEzMiwicm9sZU5hbWUiOiLkuLvnrqHnkIblkZgiLCJhdXRob3JpdGllcyI6WyJTQ09QRV90ZXN0IiwiU0NPUEVfdGVzdC5yZWFkIl19LHsicm9sZUlkIjozNzU5OTQ3MTM1LCJyb2xlTmFtZSI6IuS4u-euoSIsImF1dGhvcml0aWVzIjpbXX0seyJyb2xlSWQiOjM3NTk5NDcxMzcsInJvbGVOYW1lIjoi5Zut6ZW_IiwiYXV0aG9yaXRpZXMiOltdfSx7InJvbGVJZCI6Mzc3MjEyODMzNSwicm9sZU5hbWUiOiJ0ZXN0IiwiYXV0aG9yaXRpZXMiOltdfV0sInNjb3BlIjpbInVzZXIucmVhZCIsInRlc3QiLCJ1c2VyX2luZm8iLCJvcGVuaWQiLCJwcm9maWxlIl0sImV4cCI6MTcxMDIxNTQ5NSwiZGVwdEluZm8iOltdLCJpYXQiOjE3MTAxMjkwOTV9.LCMTCclfbhVJVzU1EjdrY7gpR7W7VKzt3-9nwTcZjv2A1bCigmUQjRI6S8dMMF_mIfptiUer5nCk1CBAYm-xQ-Rj86bOf1fqseg6UDTM1_ehiSYR5OSYHcbTMSxvA9RtaA05kT9kReCpUyMeAgdV4-Uj453cTEiGlTrK2TXNthosgIcwgRZooNcMEGGsV-YMkYv69Nmv9e9F8d2lQ26w8MZ-Rke5BUL2fvsvy-XBW8ygvkcarUWfdwml9XDXSLRK1sRCSad1ghvR84EkS8qK_Ii5LoKaxuZ7j4OSJifGeWNuTYveaTTtUy9ZG7cCbD7-kSdOxpgeNS0MjZDc22Xdhg' + console.log(jsonwebtoken.decode(token)) + }, methods: { // 设置初始化,防止刷新时恢复默认 diff --git a/src/com/index.js b/src/com/index.js index 4515e85..94eef5a 100644 --- a/src/com/index.js +++ b/src/com/index.js @@ -15,3 +15,56 @@ export function formatDate (timeStr = (new Date()), separator = '-', type){ } } +/* +重置表单对象 + @param { Object } form(必填) 需要重置的表单 + @param { Object } rules(选填) 指定需要重置的值默认设为null 试例rules:{ name: 1 } name字段重置为1 + @return { Object } 重置后的值 +*/ +export function resetForm(form, rules = {}) { + for (let key in form) { + if (checkType(form[key]) == "Array") { + if (rules.hasOwnProperty(key)) { + // 是否包含需要更改的 + form[key] = rules[key]; + } else { + form[key] = []; + } + } else if (checkType(form[key]) == "Object") { + if (rules.hasOwnProperty(key)) { + // 是否包含需要更改的 + form[key] = rules[key]; + } else { + form[key] = {}; + } + } else if (checkType(form[key]) == "Boolean") { + if (rules.hasOwnProperty(key)) { + // 是否包含需要更改的 + form[key] = rules[key]; + } else { + form[key] = false; + } + } else { + if (rules.hasOwnProperty(key)) { + // 是否包含需要更改的 + form[key] = rules[key]; + } else { + form[key] = null; + } + } + } + return form; +} + +/* 手机号验证 + @param { Number } IdcardVal(必填) 手机号 + @return { Boolean } true 通过 false 失败 +*/ +export function rulesPhone(phoneVal) { + let phoneReg = /(^1\d{10}$)|(^[0-9]\d{7}$)/; + if (!phoneReg.test(phoneVal)) { + return false; + } else { + return true; + } +} diff --git a/src/i18n/pages/login/en.js b/src/i18n/pages/login/en.js index 8265adf..62d9463 100644 --- a/src/i18n/pages/login/en.js +++ b/src/i18n/pages/login/en.js @@ -1,8 +1,8 @@ // 定义内容 export default { login: { - placeholder1: 'The user name admin or not is test', - placeholder2: 'Password: 123456', + placeholder1: 'Please enter your phone number', + placeholder2: 'Please enter the verification code', placeholder3: 'Please enter the verification code', btnText: 'Sign in', link: { diff --git a/src/i18n/pages/login/zh-cn.js b/src/i18n/pages/login/zh-cn.js index e463f7d..e31747f 100644 --- a/src/i18n/pages/login/zh-cn.js +++ b/src/i18n/pages/login/zh-cn.js @@ -1,8 +1,8 @@ // 定义内容 export default { login: { - placeholder1: '用户名 admin 或不输均为 test', - placeholder2: '密码:123456', + placeholder1: '请输入手机号', + placeholder2: '请输入验证码', placeholder3: '请输入验证码', btnText: '登 录', link: { diff --git a/src/i18n/pages/login/zh-tw.js b/src/i18n/pages/login/zh-tw.js index e154a39..2a7112e 100644 --- a/src/i18n/pages/login/zh-tw.js +++ b/src/i18n/pages/login/zh-tw.js @@ -1,8 +1,8 @@ // 定义内容 export default { login: { - placeholder1: '用戶名admin或不輸均為test', - placeholder2: '密碼:123456', + placeholder1: '請輸入驗證碼', + placeholder2: '請輸入驗證碼', placeholder3: '請輸入驗證碼', btnText: '登 录', link: { diff --git a/src/router/index copy.js b/src/router/index copy.js new file mode 100644 index 0000000..caa9a39 --- /dev/null +++ b/src/router/index copy.js @@ -0,0 +1,291 @@ +import Vue from 'vue'; +import store from '../store'; +import VueRouter from 'vue-router'; +import NProgress from 'nprogress'; +import 'nprogress/nprogress.css'; +import { Session } from '@/utils/storage'; +import { PrevLoading } from '@/utils/loading.js'; +import { useMenuApi } from '@/api/menu'; +import adminbendirout from '../../public/admin'; + +const menuApi = useMenuApi(); + +// 解决 `element ui` 导航栏重复点菜单报错问题 +const originalPush = VueRouter.prototype.push; +VueRouter.prototype.push = function push(location) { + return originalPush.call(this, location).catch((err) => err); +}; + +// 安装 VueRouter 插件 +Vue.use(VueRouter); + +// 定义动态路由 +const dynamicRoutes = [ + { + path: '/', + name: '/', + component: 'layout/index', + redirect: '/home', + meta: { + isKeepAlive: true, + }, + children: [], + }, +]; + +// 定义静态路由 +const staticRoutes = [ + { + path: '/login', + name: 'login', + component: () => import('@/views/login'), + meta: { + title: '登录', + }, + }, + { + path: '/404', + name: 'notFound', + component: () => import('@/views/error/404.vue'), + meta: { + title: 'message.staticRoutes.notFound', + }, + }, + { + path: '/401', + name: 'noPower', + component: () => import('@/views/error/401.vue'), + meta: { + title: 'message.staticRoutes.noPower', + }, + }, +]; + +// 加载静态路由 +const createRouter = () => + new VueRouter({ + routes: staticRoutes, + }); + +// 创建路由 +const router = createRouter(); + +// 加载 loading +PrevLoading.start(); + +// 多级嵌套数组处理成一维数组 +export function formatFlatteningRoutes(arr) { + if (arr.length <= 0) return false; + for (let i = 0; i < arr.length; i++) { + if (arr[i].children) { + arr = arr.slice(0, i + 1).concat(arr[i].children, arr.slice(i + 1)); + } + } + return arr; +} + +// 处理 tagsViewList 数据,默认路由全部缓存 +// isKeepAlive 处理 `name` 值,进行路由缓存 +export function formatTwoStageRoutes(arr) { + if (arr.length <= 0) return false; + const newArr = []; + const cacheList = []; + arr.forEach((v) => { + newArr.push({ ...v }); + cacheList.push(v.name); + store.dispatch('keepAliveNames/setCacheKeepAlive', cacheList); + }); + return newArr; +} + +// 判断路由 meta.roles 中是否包含当前登录用户权限字段 +export function hasAuth(roles, route) { + if (route.meta && route.meta.roles) return roles.some((role) => route.meta.roles.includes(role)); + else return true; +} + +// 递归过滤有权限的路由 +export function setFilterMenuFun(routes, role) { + const menu = []; + routes.forEach((route) => { + const item = { ...route }; + if (hasAuth(role, item)) { + if (item.children) item.children = setFilterMenuFun(item.children, role); + menu.push(item); + } + }); + return menu; +} + +// 缓存多级嵌套数组处理后的一维数组(tagsView、菜单搜索中使用:未过滤隐藏的(isHide)) +export function setCacheTagsViewRoutes(arr) { + // 先处理有权限的路由,否则 tagsView、菜单搜索中无权限的路由也将显示 + let rolesRoutes = setFilterMenuFun(arr, store.state.userInfos.userInfos.roles); + // 添加到 vuex setTagsViewRoutes 中 + store.dispatch('tagsViewRoutes/setTagsViewRoutes', formatTwoStageRoutes(formatFlatteningRoutes(rolesRoutes))); +} + +// 递归处理多余的 layout : ,让需要访问的组件保持在第一层 layout 层。 +// 因为 `keep-alive` 只能缓存二级路由 +// 默认初始化时就执行 +export function keepAliveSplice(to) { + if (to.matched && to.matched.length > 2) { + to.matched.map((v, k) => { + if (v.components.default instanceof Function) { + v.components.default().then((components) => { + if (components.default.name === 'parent') { + to.matched.splice(k, 1); + router.push({ path: to.path, query: to.query }); + keepAliveSplice(to); + } + }); + } else { + if (v.components.default.name === 'parent') { + to.matched.splice(k, 1); + keepAliveSplice(to); + } + } + }); + } +} + +// 处理后端返回的 `component` 路径,拼装实现懒加载 +export function loadView(path) { + /** + * 打包成一个 js、一个 css + */ + // if (path.indexOf('layout') > -1) return () => Promise.resolve(require(`@/${path}`)); + // else return () => Promise.resolve(require(`@/views/${path}`)); + + /** + * 打包成多个 js、多个 css + */ + if (path.indexOf('layout') > -1) return () => import(`@/${path}`); + else return () => import(`@/views/${path}`); +} + +// 递归处理每一项 `component` 中的路径 +export function dynamicRouter(routes) { + return routes.map((view) => { + if (view.component) view.component = loadView(view.component); + if (view.children) dynamicRouter(view.children); + return view; + }); +} + +// 添加路由,模拟数据与方法,可自行进行修改 admin +// 添加动态路由,`{ path: '*', redirect: '/404' }` 防止页面刷新,静态路由丢失问题 +// next({ ...to, replace: true }) 动态路由 addRoute 完毕后才放行,防止刷新时 NProgress 进度条加载2次 +// 文档地址:https://router.vuejs.org/zh/api/#router-addroutes +export function adminUser(router, to, next) { + resetRouter(); + // 发请求获取路由表操作 + // menuApi + // .getMenuAdmin() + // .then(async (res) => { + // // 获取路由表 + // console.log(res.data); + // // 读取用户信息,获取对应权限进行判断 + // store.dispatch('userInfos/setUserInfos'); + // store.dispatch('routesList/setRoutesList', setFilterMenuFun(res.data, store.state.userInfos.userInfos.roles)); + // dynamicRoutes[0].children = res.data; + // const awaitRoute = await dynamicRouter(dynamicRoutes); + // [...awaitRoute, { path: '*', redirect: '/404' }].forEach((route) => { + // router.addRoute({ ...route }); + // }); + // setCacheTagsViewRoutes(JSON.parse(JSON.stringify(res.data))); + // next({ ...to, replace: true }); + // }) + // .catch(() => {}); + + // 本地数据路由表操作 + console.log(adminbendirout); + store.dispatch('userInfos/setUserInfos'); + store.dispatch('routesList/setRoutesList', setFilterMenuFun(adminbendirout.data, store.state.userInfos.userInfos.roles)); + dynamicRoutes[0].children = adminbendirout.data; + const awaitRoute = dynamicRouter(dynamicRoutes); + [...awaitRoute, { path: '*', redirect: '/404' }].forEach((route) => { + router.addRoute({ ...route }); + }); + setCacheTagsViewRoutes(JSON.parse(JSON.stringify(adminbendirout.data))); + next({ ...to, replace: true }); +} + +// 添加路由,模拟数据与方法,可自行进行修改 test +// 添加动态路由,`{ path: '*', redirect: '/404' }` 防止页面刷新,静态路由丢失问题 +export function testUser(router, to, next) { + resetRouter(); + menuApi + .getMenuTest() + .then(async (res) => { + // 读取用户信息,获取对应权限进行判断 + store.dispatch('userInfos/setUserInfos'); + store.dispatch('routesList/setRoutesList', setFilterMenuFun(res.data, store.state.userInfos.userInfos.roles)); + dynamicRoutes[0].children = res.data; + const awaitRoute = await dynamicRouter(dynamicRoutes); + [...awaitRoute, { path: '*', redirect: '/404' }].forEach((route) => { + router.addRoute({ ...route }); + }); + setCacheTagsViewRoutes(JSON.parse(JSON.stringify(res.data))); + next({ ...to, replace: true }); + }) + .catch(() => {}); +} + +// 重置路由 +export function resetRouter() { + router.matcher = createRouter().matcher; +} + +// 延迟关闭进度条 +export function delayNProgressDone(time = 300) { + setTimeout(() => { + NProgress.done(); + }, time); +} + +// 动态加载后端返回路由路由(模拟数据) +export function getRouterList(router, to, next) { + if (!Session.get('userInfo')) return false; + if (Session.get('userInfo').userName === 'admin') adminUser(router, to, next); + else if (Session.get('userInfo').userName === 'test') testUser(router, to, next); +} + +// 路由加载前 +router.beforeEach((to, from, next) => { + keepAliveSplice(to); + NProgress.configure({ showSpinner: false }); + if (to.meta.title && to.path !== '/login') NProgress.start(); + let token = Session.get('token'); + if (to.path === '/login' && !token) { + NProgress.start(); + next(); + delayNProgressDone(); + } else { + if (!token) { + NProgress.start(); + next('/login'); + Session.clear(); + delayNProgressDone(); + } else if (token && to.path === '/login') { + next('/home'); + delayNProgressDone(); + } else { + if (Object.keys(store.state.routesList.routesList).length <= 0) { + getRouterList(router, to, next); + } else { + next(); + delayNProgressDone(0); + } + } + } +}); + +// 路由加载后 +router.afterEach(() => { + PrevLoading.done(); + delayNProgressDone(); +}); + +// 导出路由 +export default router; diff --git a/src/router/index.js b/src/router/index.js index caa9a39..f4d330f 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -246,9 +246,11 @@ export function delayNProgressDone(time = 300) { // 动态加载后端返回路由路由(模拟数据) export function getRouterList(router, to, next) { - if (!Session.get('userInfo')) return false; - if (Session.get('userInfo').userName === 'admin') adminUser(router, to, next); - else if (Session.get('userInfo').userName === 'test') testUser(router, to, next); + if (!localStorage.getItem('token')) return false; + if (localStorage.getItem('token') ) { + console.log('777777777777') + adminUser(router, to, next) + }else if (localStorage.getItem('userInfo').userName === 'test') testUser(router, to, next); } // 路由加载前 @@ -256,24 +258,30 @@ router.beforeEach((to, from, next) => { keepAliveSplice(to); NProgress.configure({ showSpinner: false }); if (to.meta.title && to.path !== '/login') NProgress.start(); - let token = Session.get('token'); + let token = localStorage.getItem('token'); if (to.path === '/login' && !token) { + console.log(111111111) NProgress.start(); next(); delayNProgressDone(); } else { if (!token) { + console.log(2222222222) NProgress.start(); next('/login'); Session.clear(); delayNProgressDone(); } else if (token && to.path === '/login') { + console.log(33333333333) next('/home'); delayNProgressDone(); } else { + console.log(4444444444) if (Object.keys(store.state.routesList.routesList).length <= 0) { + console.log(55555) getRouterList(router, to, next); } else { + console.log(6666666666) next(); delayNProgressDone(0); } diff --git a/src/store/index.js b/src/store/index.js index e7df148..67d3e9b 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,6 +1,10 @@ import Vue from 'vue'; import Vuex from 'vuex'; import { Session } from '@/utils/storage'; +import { login } from '../views/login/api.js' +// import { login } from '@/login/api.js' +import jsonwebtoken from 'jsonwebtoken' + Vue.use(Vuex); @@ -18,8 +22,46 @@ const state = { uploadHeader: { Authorization: `${Session.get('token')}` }, - campusCalendarTempentUrl: process.env.VUE_APP_BASE_API, // + campusCalendarTempentUrl: process.env.VUE_APP_BASE_API, // + token: localStorage.getItem('token') || '', + refreshToken: localStorage.getItem('refreshToken') || '', // 刷新token + user: localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user')) : {}, // 用户信息 + } -export default new Vuex.Store({ state, modules }); +const mutations = { + changeToken( state, newToken ) { + state.token = newToken + localStorage.setItem('token', newToken) + }, + changeRefreshToken( state, newRefreshToken ) { + state.refreshToken = newRefreshToken + localStorage.setItem('refreshToken', newRefreshToken) + }, + changeUser( state, newUser ) { + state.user = newUser + localStorage.setItem('user', JSON.stringify(newUser) ) + }, + +} +const actions = { + loginIn(store, obj) { + return new Promise((resolve, reject) =>{ + login(obj).then(res =>{ + console.log('=========================') + console.log(res) + store.commit('changeToken',res.token_type + ' ' + res.access_token ) + store.commit('changeRefreshToken', res.refresh_token ) + console.log(jsonwebtoken.decode(res.access_token)) + store.commit('changeUser', jsonwebtoken.decode(res.access_token) ) + resolve(res) + }).catch(err =>{ + reject(err) + }) + }) + + } +} + +export default new Vuex.Store({ state, modules, mutations, actions }); diff --git a/src/store/modules/loginConfig.js b/src/store/modules/loginConfig.js new file mode 100644 index 0000000..f4ba02d --- /dev/null +++ b/src/store/modules/loginConfig.js @@ -0,0 +1,16 @@ +const loginConfig = { + namespaced: true, + state: { + loginConfig: { // 登录配置信息 + client_id: 'test-client', + client_secret: '123456', + grant_type: 'dswj_multiple', + login_type: 'sms_login', + }, + }, + mutations: { + }, + +}; + +export default loginConfig; diff --git a/src/utils/request.js b/src/utils/request.js index 20dabae..de9cb2a 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -3,6 +3,7 @@ import router, { resetRouter } from '@/router/index'; import axios from 'axios'; import { Message, MessageBox } from 'element-ui'; import { Session } from '@/utils/storage'; +const getToken = localStorage.getItem('token'); const errorCode = { '401': '登录状态已过期,即将返回首页重新登录。', '403': '当前操作没有权限', @@ -19,9 +20,10 @@ const service = axios.create({ // 添加请求拦截器 service.interceptors.request.use( (config) => { - // 在发送请求之前做些什么 token - if (Session.get('token')) { - config.headers.common['Authorization'] = `${Session.get('token')}`; + // 是否需要设置 token + const isToken = (config.headers || {}).isToken === false + if (getToken && !isToken) { + config.headers['Authorization'] = localStorage.getItem('token') // 让每个请求携带自定义token 请根据实际情况自行修改 } return config; }, @@ -34,6 +36,7 @@ service.interceptors.request.use( // 添加响应拦截器 service.interceptors.response.use( (res) => { + console.log(res) // 对响应数据做点什么 const response = res.data; const code = res.data.code || 200; @@ -65,13 +68,14 @@ service.interceptors.response.use( } }, (error) => { + console.log(error.response.error) // 获取错误状态码 // 对响应错误做点什么 if (error.message.indexOf('timeout') != -1) { Message.error('网络超时'); } else if (error.message == 'Network Error') { Message.error('网络连接错误'); } else { - Message.error(error.response.data.message); + Message.error(error.response.data.msg || error.response.data.error); } return Promise.reject(error); } diff --git a/src/views/login/api.js b/src/views/login/api.js new file mode 100644 index 0000000..37a882f --- /dev/null +++ b/src/views/login/api.js @@ -0,0 +1,29 @@ +import request from '@/utils/request'; + +// 登录 +export function login (data) { + return request({ + url: '/oauth2/token', + method: 'post', + headers: { + isToken: false, + }, + data + }) +} + + +// 获取验证码 +export function getSmsCaptcha (phone) { + return request({ + url: '/getSmsCaptcha', + method: 'get', + headers: { + isToken: false, + }, + params: { + phone + } + }) +} + diff --git a/src/views/login/index.vue b/src/views/login/index.vue index 51751c0..2903cbd 100644 --- a/src/views/login/index.vue +++ b/src/views/login/index.vue @@ -23,46 +23,49 @@
-
请使用钉钉小程序扫码登录
+