# 端口号 | # 端口号 | ||||
VITE_PORT = 3050 | VITE_PORT = 3050 | ||||
VITE_SERVER = "/pilot/admin" | |||||
VITE_SERVER = "/pilot/admin" | |||||
VITE_PLATFORM = "tuoheng-dsp-web" |
VITE_APP_USE_MOCK = false | VITE_APP_USE_MOCK = false | ||||
# proxy | # proxy | ||||
VITE_PROXY = [["/api","http://192.168.11.11:9055"]] | |||||
VITE_PROXY = [["","http://192.168.11.11:7011"]] | |||||
# base api | # base api | ||||
VITE_APP_GLOB_BASE_API = '/api' | |||||
VITE_APP_GLOB_BASE_API = '' | |||||
VITE_AUTHORITY = 'http://oidc.dev.t-aaron.com' | |||||
VITE_CLIENT_ID = 'tuoheng-pilot-admin' | |||||
VITE_CLIENT_SECRET = 'WB0CZ1c6bZLiYP6jLtDFsA==' | |||||
VITE_REDIRECT_URI = 'http://192.168.11.11:7011/login' |
VITE_APP_GLOB_BASE_API = '/api-local' | VITE_APP_GLOB_BASE_API = '/api-local' | ||||
# mock base api | # mock base api | ||||
VITE_APP_GLOB_BASE_API_MOCK = '/api-mock' | |||||
VITE_APP_GLOB_BASE_API_MOCK = '/api-mock' | |||||
VITE_AUTHORITY = 'http://oidc.dev.t-aaron.com' | |||||
VITE_CLIENT_ID = 'tuoheng-pilot-admin' | |||||
VITE_CLIENT_SECRET = 'WB0CZ1c6bZLiYP6jLtDFsA==' | |||||
VITE_REDIRECT_URI = 'http://192.168.12.8:3050/login' |
VITE_PROXY = [["/api","http://127.0.0.1:8002/api"]] | VITE_PROXY = [["/api","http://127.0.0.1:8002/api"]] | ||||
# base api | # base api | ||||
VITE_APP_GLOB_BASE_API = '/api' | |||||
VITE_APP_GLOB_BASE_API = '/api' | |||||
VITE_AUTHORITY = 'http://oidc.dev.t-aaron.com' | |||||
VITE_CLIENT_ID = 'tuoheng-pilot-admin' | |||||
VITE_CLIENT_SECRET = 'WB0CZ1c6bZLiYP6jLtDFsA==' | |||||
VITE_REDIRECT_URI = 'http://192.168.11.11:7011/home' |
VITE_APP_USE_MOCK = false | VITE_APP_USE_MOCK = false | ||||
# proxy | # proxy | ||||
VITE_PROXY = [["","http://192.168.11.11:9055"]] | |||||
VITE_PROXY = [["","http://192.168.11.241:9089"]] | |||||
# base api | # base api | ||||
VITE_APP_GLOB_BASE_API = '' | |||||
VITE_APP_GLOB_BASE_API = '' | |||||
VITE_AUTHORITY = 'http://oidc.test.t-aaron.com' | |||||
VITE_CLIENT_ID = 'tuoheng-pilot-admin' | |||||
VITE_CLIENT_SECRET = 'WB0CZ1c6bZLiYP6jLtDFsA==' | |||||
VITE_REDIRECT_URI = 'http://192.168.11.241:9089/home' |
"chart-all": "^1.0.2", | "chart-all": "^1.0.2", | ||||
"dayjs": "^1.11.2", | "dayjs": "^1.11.2", | ||||
"mockjs": "^1.1.0", | "mockjs": "^1.1.0", | ||||
"oidc-client": "^1.11.5", | |||||
"pinia": "^2.0.13", | "pinia": "^2.0.13", | ||||
"pinia-plugin-persist": "^1.0.0", | "pinia-plugin-persist": "^1.0.0", | ||||
"tinymce": "^5.10.2", | "tinymce": "^5.10.2", |
*/ | */ | ||||
export function getUser() { | export function getUser() { | ||||
return request({ | return request({ | ||||
url: '/index/getUserInfo', | |||||
url: '/user/getUserInfo', | |||||
method: 'get' | method: 'get' | ||||
}) | }) | ||||
} | } |
<n-image height="18" src="/logo.png" preview-disabled /> | <n-image height="18" src="/logo.png" preview-disabled /> | ||||
</div> | </div> | ||||
<n-dropdown trigger="hover" :options="options" @select="handleSelect"> | |||||
<n-dropdown v-if="getUserInfo.hasLogin" trigger="hover" :options="options" @select="handleSelect"> | |||||
<div class="user_msg"> | <div class="user_msg"> | ||||
<n-image | |||||
<!-- <n-image | |||||
class="user_avatar" | class="user_avatar" | ||||
:src="userInfo.avatar" | |||||
:src="getUserInfo.avatar" | |||||
preview-disabled | preview-disabled | ||||
/> | |||||
<span class="user_name">{{ userInfo.realname }}</span> | |||||
/> --> | |||||
<span class="user_name">{{ getUserInfo.realname }}</span> | |||||
</div> | </div> | ||||
</n-dropdown> | </n-dropdown> | ||||
<div v-if="!getUserInfo.hasLogin" class="header__login" @click="handleLogin">登录</div> | |||||
</n-layout-header> | </n-layout-header> | ||||
</template> | </template> | ||||
import { renderIcon } from '@/utils' | import { renderIcon } from '@/utils' | ||||
import { useUserStore } from '@/store/modules/user.js' | import { useUserStore } from '@/store/modules/user.js' | ||||
import { useSettingStore } from '@/store/modules/setting.js' | import { useSettingStore } from '@/store/modules/setting.js' | ||||
import { signinRedirect, signoutRedirect } from '@/utils/oidc/index.js' | |||||
export default defineComponent({ | export default defineComponent({ | ||||
name: 'LayoutHeader', | name: 'LayoutHeader', | ||||
setup() { | setup() { | ||||
const settingStore = useSettingStore() | const settingStore = useSettingStore() | ||||
const data = reactive({ | const data = reactive({ | ||||
options: [ | options: [ | ||||
{ | |||||
label: '修改密码', | |||||
key: 'edit', | |||||
icon: renderIcon(EditOutlined) | |||||
}, | |||||
// { | |||||
// label: '修改密码', | |||||
// key: 'edit', | |||||
// icon: renderIcon(EditOutlined) | |||||
// }, | |||||
{ | { | ||||
label: '退出登录', | label: '退出登录', | ||||
key: 'out', | key: 'out', | ||||
async function handleSelect(key) { | async function handleSelect(key) { | ||||
switch (key) { | switch (key) { | ||||
case 'out': | case 'out': | ||||
await logOut() | |||||
await signoutRedirect() | |||||
} | } | ||||
} | } | ||||
async function logOut() { | |||||
const res = await userStore.userLogout() | |||||
if (res.code === 0) { | |||||
router.replace('/login') | |||||
const getUserInfo = computed(() => { | |||||
return { | |||||
hasLogin: userStore.hasLogin, | |||||
// avatar: userStore.avatar, | |||||
username: userStore.username, | |||||
realname: userStore.realname | |||||
} | } | ||||
}) | |||||
const handleLogin = () => { | |||||
signinRedirect() | |||||
} | } | ||||
return { | return { | ||||
...toRefs(data), | ...toRefs(data), | ||||
getLogoWidth, | getLogoWidth, | ||||
handleSelect | |||||
getUserInfo, | |||||
handleSelect, | |||||
handleLogin | |||||
} | } | ||||
} | } | ||||
}) | }) |
const menuMode = computed(() => settingStore.getMenuMode) | const menuMode = computed(() => settingStore.getMenuMode) | ||||
const tagsMenuSetting = computed(() => settingStore.getTagsMenuSetting) | const tagsMenuSetting = computed(() => settingStore.getTagsMenuSetting) | ||||
const useUser = useUserStore() | const useUser = useUserStore() | ||||
function getUserNow() { | |||||
useUser.getUserInfo() | |||||
} | |||||
getUserNow() | |||||
// function getUserNow() { | |||||
// useUser.getUserInfo() | |||||
// } | |||||
// getUserNow() | |||||
</script> | </script> | ||||
import { useUserStore } from '@/store/modules/user' | import { useUserStore } from '@/store/modules/user' | ||||
import { usePermissionStore } from '@/store/modules/permission' | import { usePermissionStore } from '@/store/modules/permission' | ||||
import { NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '@/router/routes' | import { NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '@/router/routes' | ||||
import { getToken } from '@/utils/token' | |||||
import { getUserInfo, signinRedirect, signoutRedirect } from '@/utils/oidc/index.js' | |||||
const WHITE_LIST = ['/login', '/redirect'] | |||||
export function createPermissionGuard(router) { | export function createPermissionGuard(router) { | ||||
const userStore = useUserStore() | const userStore = useUserStore() | ||||
const permissionStore = usePermissionStore() | const permissionStore = usePermissionStore() | ||||
router.beforeEach(async(to, from, next) => { | router.beforeEach(async(to, from, next) => { | ||||
// const token = getToken() | |||||
const token = true | |||||
if (token) { | |||||
if (to.path === '/login') { | |||||
next({ path: '/' }) | |||||
const oidcUser = await getUserInfo() | |||||
if (oidcUser) { | |||||
const hasRoutes = !!permissionStore.permissionRoutes.length | |||||
if (hasRoutes) { | |||||
next() | |||||
} else { | } else { | ||||
const hasRoutes = !!permissionStore.permissionRoutes.length | |||||
if (hasRoutes) { | |||||
next() | |||||
} else { | |||||
try { | |||||
// await userStore.getUserInfo() | |||||
const routes = await permissionStore.generateRoutesMock(['admin']) | |||||
routes.forEach((item) => { | |||||
router.addRoute(item) | |||||
}) | |||||
router.addRoute(NOT_FOUND_ROUTE) | |||||
router.addRoute(REDIRECT_ROUTE) | |||||
next({ ...to, replace: true }) | |||||
} catch (error) { | |||||
// removeToken() | |||||
// $message.error(error) | |||||
next({ path: '/login', query: { ...to.query, redirect: to.path }}) | |||||
} | |||||
try { | |||||
// const { role } = oidcUser.profile | |||||
// const roles = role.includes('admin') ? 'admin' : 'flyer' | |||||
const userRole = await userStore.getUserInfos() || 1 | |||||
const roleList = { 1: 'admin', 2: 'flyer' } | |||||
const routes = await permissionStore.generateRoutesMock([roleList[userRole]]) | |||||
routes.forEach((item) => { | |||||
router.addRoute(item) | |||||
}) | |||||
router.addRoute(NOT_FOUND_ROUTE) | |||||
router.addRoute(REDIRECT_ROUTE) | |||||
next({ ...to, replace: true }) | |||||
} catch (error) { | |||||
signinRedirect() | |||||
} | } | ||||
} | } | ||||
} else { | } else { | ||||
if (WHITE_LIST.includes(to.path)) { | |||||
if (to.path === '/login') { | |||||
next() | next() | ||||
} else { | } else { | ||||
next({ path: '/login', query: { ...to.query, redirect: to.path }}) | |||||
signinRedirect() | |||||
} | } | ||||
} | } | ||||
}) | }) |
import { defineStore } from 'pinia' | import { defineStore } from 'pinia' | ||||
import { userLogin, loginOut } from '@/api/login' | |||||
import { getUser } from '@/api/login/index.js' | import { getUser } from '@/api/login/index.js' | ||||
import { setToken, removeToken } from '@/utils/token' | |||||
import { useTagsMenuStore } from './tagsMenu.js' | |||||
import { usePermissionStore } from './permission.js' | |||||
export const useUserStore = defineStore('user', { | export const useUserStore = defineStore('user', { | ||||
persist: true, | |||||
persist: { | |||||
enabled: true | |||||
}, | |||||
state() { | state() { | ||||
return { | return { | ||||
userInfo: {} | |||||
userInfo: { | |||||
hasLogin: false | |||||
} | |||||
} | } | ||||
}, | }, | ||||
getters: { | getters: { | ||||
userInfoMsg() { | |||||
return this.userInfo | |||||
// userId() { | |||||
// return this.userInfo?.id | |||||
// }, | |||||
userName() { | |||||
return this.userInfo?.userName | |||||
}, | |||||
realname() { | |||||
return this.userInfo?.realname | |||||
}, | |||||
avatar() { | |||||
return this.userInfo?.avatar | |||||
}, | |||||
role() { | |||||
return this.userInfo?.role || [] | |||||
}, | |||||
authority() { | |||||
return this.userInfo.authority | |||||
}, | |||||
hasLogin() { | |||||
return this.userInfo.hasLogin | |||||
} | } | ||||
}, | }, | ||||
actions: { | actions: { | ||||
/* 登录 */ | |||||
async getLoginToken(form) { | |||||
try { | |||||
const res = await userLogin(form) | |||||
if (res.code === 0) { | |||||
/* 设置token */ | |||||
setToken(res.data.access_token) | |||||
this.getUserInfo() | |||||
return Promise.resolve(res) | |||||
} else { | |||||
return Promise.reject(res) | |||||
} | |||||
} catch (error) { | |||||
return Promise.reject(error) | |||||
} | |||||
}, | |||||
/* 获取用户信息 */ | |||||
async getUserInfo() { | |||||
const res = await getUser() | |||||
if (res.code === 0) { | |||||
this.setUserInfo(res.data) | |||||
} | |||||
}, | |||||
async userLogout() { | |||||
async getUserInfos() { | |||||
try { | try { | ||||
const res = await loginOut() | |||||
const res = await getUser() | |||||
if (res.code === 0) { | if (res.code === 0) { | ||||
removeToken() | |||||
this.reset() | |||||
return Promise.resolve(res) | |||||
this.setUserInfo({ hasLogin: true, ...res.data }) | |||||
return Promise.resolve(res.data.type) | |||||
} else { | } else { | ||||
return Promise.reject(res) | |||||
this.setUserInfo() | |||||
} | } | ||||
} catch (error) { | } catch (error) { | ||||
return Promise.reject(error) | |||||
console.error(error) | |||||
this.setUserInfo() | |||||
} | } | ||||
}, | }, | ||||
reset() { | |||||
this.$reset() | |||||
const tagsMenuStore = useTagsMenuStore() | |||||
const permissionStore = usePermissionStore() | |||||
tagsMenuStore.$reset() | |||||
permissionStore.$reset() | |||||
}, | |||||
setUserInfo(userInfo = {}) { | |||||
setUserInfo(userInfo = { hasLogin: false }) { | |||||
this.userInfo = { ...this.userInfo, ...userInfo } | this.userInfo = { ...this.userInfo, ...userInfo } | ||||
} | } | ||||
} | } |
import { router } from '@/router' | |||||
import { getToken, removeToken } from '@/utils/token' | |||||
import { isWithoutToken } from './help' | import { isWithoutToken } from './help' | ||||
import { getUserInfo, signoutRedirect } from '@/utils/oidc/index.js' | |||||
export function setupInterceptor(service) { | export function setupInterceptor(service) { | ||||
service.interceptors.request.use( | service.interceptors.request.use( | ||||
// 处理不需要token的请求 | // 处理不需要token的请求 | ||||
if (isWithoutToken(config)) { | if (isWithoutToken(config)) { | ||||
return config | return config | ||||
} else { | |||||
const userInfo = await getUserInfo() | |||||
if (userInfo) { | |||||
const { token_type, access_token } = userInfo | |||||
config.headers.Authorization = `${token_type} ${access_token}` | |||||
return config | |||||
} else { | |||||
signoutRedirect() | |||||
return Promise.reject({ response: { status: 401, message: '未登录' }}) | |||||
} | |||||
} | } | ||||
// const token = getToken() | |||||
const token = 'token' | |||||
if (token) { | |||||
config.headers.Authorization = token | |||||
return config | |||||
} | |||||
/** | |||||
* * 未登录或者token过期的情况下 | |||||
* * 跳转登录页重新登录,携带当前路由及参数,登录成功会回到原来的页面 | |||||
*/ | |||||
const { currentRoute } = router | |||||
router.replace({ | |||||
path: '/login', | |||||
query: { ...currentRoute.query, redirect: currentRoute.path } | |||||
}) | |||||
return Promise.reject({ code: '-1', message: '未登录' }) | |||||
}, | }, | ||||
(error) => Promise.reject(error) | (error) => Promise.reject(error) | ||||
) | ) | ||||
(response) => { | (response) => { | ||||
const { method } = response?.config | const { method } = response?.config | ||||
const { code } = response?.data | const { code } = response?.data | ||||
const { currentRoute } = router | |||||
switch (code) { | switch (code) { | ||||
case 0: | case 0: | ||||
if (method !== 'get') { | if (method !== 'get') { | ||||
break | break | ||||
case 401: | case 401: | ||||
// 未登录(可能是token过期或者无效了) | // 未登录(可能是token过期或者无效了) | ||||
removeToken() | |||||
router.replace({ | |||||
path: '/login', | |||||
query: { ...currentRoute.query, redirect: currentRoute.path } | |||||
}) | |||||
signoutRedirect() | |||||
break | break | ||||
default: | default: | ||||
break | break | ||||
return response?.data | return response?.data | ||||
}, | }, | ||||
(error) => { | (error) => { | ||||
return Promise.reject(error) | |||||
const { status } = error.response | |||||
if (status === 401) { | |||||
signoutRedirect() | |||||
} else if (status === 403) { | |||||
$message.error('暂无权限访问,请联系管理员') | |||||
return Promise.reject(error) | |||||
} else { | |||||
return Promise.reject(error) | |||||
} | |||||
} | } | ||||
) | ) | ||||
} | |||||
} |
import { UserManager } from 'oidc-client' | |||||
import { createLocalStorage } from '../cache' | |||||
let oidcManager = null | |||||
export const initServe = () => { | |||||
if (oidcManager) return oidcManager | |||||
const { VITE_AUTHORITY, VITE_CLIENT_ID, VITE_CLIENT_SECRET, VITE_REDIRECT_URI } = import.meta.env | |||||
oidcManager = new UserManager({ | |||||
/* 认证服务器 */ | |||||
authority: VITE_AUTHORITY, | |||||
/* 客户端id */ | |||||
client_id: VITE_CLIENT_ID, | |||||
client_secret: VITE_CLIENT_SECRET, | |||||
/* 回调客户端页面 */ | |||||
redirect_uri: VITE_REDIRECT_URI, | |||||
post_logout_redirect_uri: VITE_REDIRECT_URI, | |||||
response_type: 'code', | |||||
/* 授权范围 */ | |||||
scope: 'openid profile', | |||||
automaticSilentRenew: true, | |||||
revokeAccessTokenOnSignout: true, | |||||
metadata: { | |||||
issuer: `${VITE_AUTHORITY}`, | |||||
authorization_endpoint: `${VITE_AUTHORITY}/oauth2/authorize`, | |||||
token_endpoint: `${VITE_AUTHORITY}/oauth2/token`, | |||||
userinfo_endpoint: `${VITE_AUTHORITY}/userinfo`, | |||||
end_session_endpoint: `${VITE_AUTHORITY}/toLogout`, | |||||
revocation_endpoint: `${VITE_AUTHORITY}/oauth2/revoke` | |||||
} | |||||
}) | |||||
return oidcManager | |||||
} | |||||
export const getUserInfo = async() => { | |||||
oidcManager = initServe() | |||||
return await oidcManager.getUser() | |||||
} | |||||
/* 登录 */ | |||||
export const signinRedirect = async() => { | |||||
oidcManager = initServe() | |||||
oidcManager.signinRedirect({}) | |||||
.then(res => { | |||||
setPath(window.location.pathname) | |||||
console.log('signed in', res) | |||||
}).catch(err => { | |||||
console.err(err) | |||||
}) | |||||
} | |||||
/* 登录回调 */ | |||||
export const signinRedirectCallback = async() => { | |||||
const userInfo = await getUserInfo() | |||||
if (!userInfo) { | |||||
oidcManager = initServe() | |||||
return await oidcManager.signinRedirectCallback() | |||||
} | |||||
} | |||||
/* 退出 */ | |||||
export const signoutRedirect = () => { | |||||
oidcManager = initServe() | |||||
oidcManager.signoutRedirect({}) | |||||
.then(function(res) { | |||||
setPath(window.location.pathname) | |||||
console.log('signed out', res) | |||||
}).catch(function(err) { | |||||
console.log(err) | |||||
}) | |||||
} | |||||
/* 退出回调 */ | |||||
export const signoutRedirectCallback = async() => { | |||||
const userInfo = await getUserInfo() | |||||
if (!userInfo) { | |||||
oidcManager = initServe() | |||||
return await oidcManager.signoutRedirectCallback() | |||||
} | |||||
} | |||||
const PATH_CODE = 'redirect_path' | |||||
const DURATION = 24 * 60 * 60 | |||||
export const isPath = createLocalStorage() | |||||
/* 获取Path */ | |||||
export function getPath() { | |||||
return isPath.get(PATH_CODE) | |||||
} | |||||
/* 设置Path */ | |||||
export function setPath(Path, duration = DURATION) { | |||||
isPath.set(PATH_CODE, Path, duration) | |||||
} | |||||
/* 移出Path */ | |||||
export function removePath() { | |||||
isPath.remove(PATH_CODE) | |||||
} |
<template> | <template> | ||||
<div> | |||||
<!-- 1 --> | |||||
<div class="login"> | |||||
<n-spin> | |||||
<template #description> | |||||
正在进入系统中... | |||||
</template> | |||||
</n-spin> | |||||
</div> | </div> | ||||
</template> | </template> | ||||
<script> | <script> | ||||
import { useRoute, useRouter } from 'vue-router' | |||||
import { useUserStore } from '@/store/modules/user' | |||||
import { signinRedirectCallback, signoutRedirectCallback, signoutRedirect, getPath, removePath } from '@/utils/oidc/index.js' | |||||
export default { | export default { | ||||
name: 'LoginPage', | name: 'LoginPage', | ||||
setup() { | setup() { | ||||
const route = useRoute() | |||||
const router = useRouter() | |||||
const userStore = useUserStore() | |||||
if (route.query?.code && route.query?.state) { | |||||
signinRedirectCallback().then(res => { | |||||
// router.push({ path: getPath() }) | |||||
// removePath() | |||||
// userStore.getUserInfos() | |||||
const authority = res.profile.authority | |||||
const { VITE_PLATFORM } = import.meta.env | |||||
if (authority && (authority.includes(VITE_PLATFORM) || authority.includes('admin'))) { | |||||
router.push({ path: '/' }) | |||||
} else { | |||||
$message.error('暂无权限访问,请联系管理员') | |||||
setTimeout(() => { | |||||
signoutRedirect() | |||||
}, 2000) | |||||
} | |||||
}).catch(err => { | |||||
console.log(err) | |||||
signoutRedirect() | |||||
}) | |||||
// } else if (getPath()) { | |||||
// signoutRedirectCallback().then(res => { | |||||
// router.push({ path: getPath() }) | |||||
// userStore.setUserInfo() | |||||
// removePath() | |||||
// }) | |||||
} | |||||
} | } | ||||
} | } | ||||
</script> | </script> | ||||
<style scoped lang='scss'> | <style scoped lang='scss'> | ||||
.login{ | |||||
width: 100vw; | |||||
height: 100vh; | |||||
position: relative; | |||||
.n-spin-body{ | |||||
position: absolute; | |||||
left: 50%; | |||||
top: 50%; | |||||
transform: translate(-50%, -50%); | |||||
} | |||||
} | |||||
</style> | </style> |