浏览代码

Merge branch 'develop' of http://163.228.141.122:3000/TEAMMODEL/TEAMModelOS into develop

CrazyIter_Bin 5 月之前
父节点
当前提交
b75cf31382

+ 4 - 1
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/ClientApp/package.json

@@ -8,10 +8,13 @@
     "lint": "vue-cli-service lint"
   },
   "dependencies": {
+    "axios": "^1.7.9",
     "clean-webpack-plugin": "^4.0.0",
     "compression-webpack-plugin": "^11.1.0",
     "core-js": "^3.8.3",
-    "vue": "^2.6.14"
+    "element-ui": "^2.15.14",
+    "vue": "^2.6.14",
+    "vue-router": "^3.6.5"
   },
   "devDependencies": {
     "@babel/core": "^7.12.16",

+ 14 - 15
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/ClientApp/src/App.vue

@@ -1,28 +1,27 @@
 <template>
-  <div id="app">
-    <img alt="Vue logo" src="./assets/logo.png">
-    <HelloWorld msg="Welcome to Your Vue.js App"/>
-  </div>
+    <div id="app">
+        <router-view></router-view>
+    </div>
 </template>
 
 <script>
-import HelloWorld from './components/HelloWorld.vue'
+import HelloWorld from "./components/HelloWorld.vue"
 
 export default {
-  name: 'App',
-  components: {
-    HelloWorld
-  }
+    name: "App",
+    components: {
+        HelloWorld,
+    },
 }
 </script>
 
 <style>
 #app {
-  font-family: Avenir, Helvetica, Arial, sans-serif;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-  text-align: center;
-  color: #2c3e50;
-  margin-top: 60px;
+    font-family: Avenir, Helvetica, Arial, sans-serif;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+    text-align: center;
+    color: #2c3e50;
+    margin-top: 60px;
 }
 </style>

+ 294 - 0
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/ClientApp/src/api/http.js

@@ -0,0 +1,294 @@
+import axios from 'axios'
+import { app } from '@/main';
+import { Loading } from 'element-ui';
+
+// 不需携带access_token
+const NO_ACCESS_API = [
+    '/activity/get-website',
+    '/activity/login-portal',
+    '/activity/list-portal',
+]
+// 需要携带access_token 不需要auth-token
+const NO_AUTH_API = []
+// 不进行错误提示白名单
+const NO_WARNING = []
+
+
+// token刷新中,挂载的请求
+let requestStack = []
+// 记录token刷新中发起的请求
+function track(prms) {
+    requestStack.push(prms)
+}
+// token刷新完成触发挂起的请求继续请求
+function trigger() {
+    let index = requestStack.length
+    while(index > 0) {
+        requestStack[index - 1]()
+        requestStack.pop()
+        index--
+    }
+}
+
+let refreshing = false //是否刷新token中
+axios.defaults.timeout = 30000 //设置超时时长
+axios.defaults.baseURL = ''
+
+let loading = undefined
+/* Loading.service({
+    lock: true,
+    text: '加载中',
+    background: 'rgba(0, 0, 0, 0.7)'
+}) */
+
+// http request 拦截器
+axios.interceptors.request.use(config => {
+    loading = Loading.service({
+        lock: true,
+        text: '加载中',
+        background: 'rgba(0, 0, 0, 0.7)'
+    })
+    let isNeedAccess = true
+    // 1. 登录及登录前,不检查API是否需要accesstoken
+    for (let apiUrl of NO_ACCESS_API) {
+        if(config.url.includes(apiUrl)) {
+            isNeedAccess = false
+            break
+        }
+    }
+    if(!isNeedAccess) return config
+
+    // 2. 登录后,
+    // 检查操作时间
+    let webEndTime = localStorage.getItem('webEndTime')
+    let time_now = new Date().getTime()
+    if(webEndTime && time_now > webEndTime) {
+        loginOut()
+        sessionStorage.setItem('loginOut', '长时间未操作,重新登录')
+        return
+    }
+    // 检查是否有access_token
+    let access_token = localStorage.getItem('access_token')
+    if(!access_token) {
+        loginOut()
+        sessionStorage.setItem('loginOut', 'token无效')
+        return
+    }
+    // 检查是否快到期
+    let isExpired = checkToken()
+    // token未过期
+    if(!isExpired) {
+        return handleHeader(config)
+    }
+    // 刷新token
+    let handleRefresh = new Promise((resolve, reject) => {
+        track(() => {
+            handleHeader(config)
+            resolve(config)
+        })
+    })
+    sessionStorage.setItem('apiCount', requestStack.length)
+    if(!refreshing) {
+        refreshing = true
+        refreshToken()
+    }
+    return handleRefresh
+}, error => {
+    return Promise.reject(error)
+})
+
+// http response 拦截器
+axios.interceptors.response.use(response => {
+    if(response.data.errCode === 2) {
+        /* router.push({
+            path: '/login',
+            query: {
+                redirect: router.currentRoute.fullPath
+            }
+        }) */
+        console.log('errCode');
+    }
+    // 保存最新的服务端时间
+    if(response.headers.date) {
+        localStorage.setItem('serverTime', new Date(response.headers.date).getTime())
+    }
+    // 四小时没操作过则需重新登录
+    let endTime = (new Date().getTime() + (4 * 60 * 60 * 1000))
+    localStorage.setItem('webEndTime', endTime)
+    setTimeout(() => {
+        loading.close()
+    }, 1500)
+    return response
+}, error => {
+    console.log('vbfbbtfnt', error);
+    if(!error.response) {
+        if(!NO_WARNING.includes(error.config.url)) {
+            app.$message({
+                type: 'error',
+                message: 'http.error'
+            })
+        }
+    } else if(error.response && error.response.status === 401) {
+        localStorage.clear()
+        sessionStorage.setItem('loginOut', error.config.url + ':API401,重新登录')
+        sessionStorage.setItem('APIInfo', JSON.stringify(error))
+        console.log('loginOut', error.config.url + ':API401,重新登录');
+        window.location.href = '/home/homePage'
+        app.$message({
+            type: 'error',
+            message: 'http.error401'
+        })
+    } else if(error.response.status === 500) {
+        app.$message({
+            type: 'error',
+            message: 'http.error500'
+        })
+    } else if(error.response.status === 404) {
+        app.$message({
+            type: 'error',
+            message: 'http.error404'
+        })
+    } else if(error.response.status === 501) {
+        app.$message({
+            type: 'error',
+            message: 'http.error501'
+        })
+    } else {
+        app.$message({
+            type: 'error',
+            message: 'http.error400'
+        })
+    }
+    loading.close()
+    return Promise.reject(error)
+})
+
+function handleHeader(config) {
+    config.headers['Authorization'] = 'Bearer ' + localStorage.getItem('access_token')
+    config.headers['Content-Type'] = 'application/json'
+    config.headers['lang'] = localStorage.getItem('local') || navigator.language.toLowerCase()
+
+    let isNeedAuth = true
+    for (let apiUrl of NO_AUTH_API) {
+        if(config.url.includes(apiUrl)) {
+            console.log('auth-token无效', config)
+            isNeedAuth = false
+            break
+        }
+    }
+    if(!isNeedAuth) return config
+
+    // 检查auth-token是否存在
+    let identity = sessionStorage.getItem('identity')
+    // 正式使用要调整为个人和专家
+    let auth_token = identity === 'student' ? localStorage.getItem('stu_auth_token') : localStorage.getItem('auth_token')
+    if(!auth_token) {
+        console.log('auth_token失败', config)
+        loginOut()
+        sessionStorage.setItem('loginOut', 'localStorage没有auth_token:auth_token失败,重新登录')
+        return
+    }
+    // 通过验证,设置对应参数
+    config.headers['X-Auth-AuthToken'] = auth_token
+    return config
+}
+
+// 检查token是否快过期
+function checkToken() {
+    if(!localStorage.getItem('expires_in')) return false
+
+    var nowTime = new Date()
+    var offset = nowTime.getTimezoneOffset() / 60
+    let cT = Date.parse(nowTime)
+    let eT = Date.parse(localStorage.getItem('expires_in'))
+    let oT = 0
+    let btw = eT - oT - cT
+    if(btw > 10 * 60 * 1000) {
+        return false
+    } else {
+        return true
+    }
+}
+
+// 刷新token
+function refreshToken() {
+    refreshing = true
+    let areaRoute = window.location.pathname.split('/')
+    axios.post('/activity/login-portal', {
+        "route": areaRoute[1],
+        "token": localStorage.getItem('auth_token')
+    }).then(res => {
+        if(res.data.code === 200) {
+            localStorage.setItem("auth_token", res.data.token)
+            localStorage.setItem("access_token", res.data.auth_token.access_token)
+            localStorage.setItem("expires_in", res.data.auth_token.expires_in)
+            // token刷新完成,触发挂载的API
+            trigger()
+            refreshing = false
+        } else {
+            refreshing = false
+            requestStack = []
+            loginOut()
+            sessionStorage.setItem('loginOut', 'Token验证失败')
+        }
+    },err => {
+        refreshing = false
+        requestStack = []
+        loginOut()
+        sessionStorage.setItem('loginOut', 'token刷新失败,退出重新登录')
+    })
+}
+
+// 超时退出重新登录
+function loginOut() {
+    localStorage.clear()
+    console.log('超时退出');
+    // router.push({path: '/home/homePage'})
+    window.location.href = window.location.origin + '/home/homePage'
+}
+
+/**
+ * 封装get方法
+ * @param url
+ * @param data
+ * @returns {Promise}
+ */
+export function fetch(url, params) {
+    let data = {}
+    data.method = url
+    data.params = params
+    data.lang = localStorage.getItem('local')
+    return new Promise((resolve, reject) => {
+        axios.get(url, data).then(response => {
+            resolve(response.data)
+            app.$message({
+                type: 'success',
+                message: '数据访问成功!'
+            })
+        }).catch(err => {
+            reject(err)
+        })
+    })
+}
+
+/**
+ * 封装post请求
+ * @param url
+ * @param data
+ * @returns {Promise}
+ */
+export function post(url, params) {
+    let data = {}
+    data.method = url
+    data.params = params
+    data.lang = localStorage.getItem('local')
+    return new Promise((resolve, reject) => {
+        axios.post(url, params).then(response => {
+            if(response) {
+                resolve(response.data)
+            }
+        }, err => {
+            reject(err)
+        })
+    })
+}

二进制
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/ClientApp/src/assets/fengjing.jpg


二进制
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/ClientApp/src/assets/qrCode.png


+ 13 - 0
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/ClientApp/src/main.js

@@ -1,8 +1,21 @@
 import Vue from 'vue'
 import App from './App.vue'
+import router from './router/index'
+import apiTools from '@/api/http'
 
+import ElementUI from 'element-ui'
+import 'element-ui/lib/theme-chalk/index.css'
+import axios from 'axios'
+
+Vue.use(ElementUI)
 Vue.config.productionTip = false
 
+Vue.prototype.$api = apiTools
+Vue.prototype.$axios = axios
+
+
 new Vue({
+  el: '#app',
+  router,
   render: h => h(App),
 }).$mount('#app')

+ 44 - 0
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/ClientApp/src/router/index.js

@@ -0,0 +1,44 @@
+import Vue from 'vue'
+import VueRouter from 'vue-router'
+
+Vue.use(VueRouter)
+
+const routes = [
+    {
+        path: '/',
+        name: 'home',
+        component: () => import('@/view/login/Admin.vue')
+    },
+    {
+        path: '/about',
+        name: 'about',
+        // route level code-splitting
+        // this generates a separate chunk (about.[hash].js) for this route
+        // which is lazy-loaded when the route is visited.
+        component: () => import('@/view/login/Student.vue')
+    },
+    {
+        path: '/login',
+        component: () => import("@/view/login/Index.vue"),
+        children: [
+            {
+                path: 'admin',
+                name: 'admin',
+                component: () => import('@/view/login/Admin.vue')
+            },
+            {
+                path: 'student',
+                name: 'student',
+                component: () => import('@/view/login/Student.vue')
+            },
+        ]
+    }
+]
+
+const router = new VueRouter({
+    mode: 'history',
+    base: process.env.BASE_URL,
+    routes
+})
+
+export default router

+ 10 - 0
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/ClientApp/src/view/login/Admin.vue

@@ -0,0 +1,10 @@
+<template>
+    <div>教师端</div>
+</template>
+
+<script>
+export default {}
+</script>
+
+<style>
+</style>

+ 10 - 0
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/ClientApp/src/view/login/Index.vue

@@ -0,0 +1,10 @@
+<template>
+    <router-view></router-view>
+</template>
+
+<script>
+export default {}
+</script>
+
+<style>
+</style>

+ 10 - 0
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/ClientApp/src/view/login/Student.vue

@@ -0,0 +1,10 @@
+<template>
+    <div>学生端</div>
+</template>
+
+<script>
+export default {}
+</script>
+
+<style>
+</style>