增加登录功能和权限验证
This commit is contained in:
parent
cd26a81af5
commit
118aab4f3b
@ -1,6 +1,7 @@
|
||||
export const redirects = JSON.parse("{}")
|
||||
|
||||
export const routes = Object.fromEntries([
|
||||
["/login.html", { loader: () => import(/* webpackChunkName: "login.html" */"D:/xue/dma_handbook/docs/.vuepress/.temp/pages/login.html.js"), meta: {"title":"DMA手册 - 登录"} }],
|
||||
["/", { loader: () => import(/* webpackChunkName: "index.html" */"D:/xue/dma_handbook/docs/.vuepress/.temp/pages/index.html.js"), meta: {"title":"首页"} }],
|
||||
["/posts/administrative.html", { loader: () => import(/* webpackChunkName: "posts_administrative.html" */"D:/xue/dma_handbook/docs/.vuepress/.temp/pages/posts/administrative.html.js"), meta: {"title":"行政"} }],
|
||||
["/posts/agent.html", { loader: () => import(/* webpackChunkName: "posts_agent.html" */"D:/xue/dma_handbook/docs/.vuepress/.temp/pages/posts/agent.html.js"), meta: {"title":"代理商添加方式说明"} }],
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export const siteData = JSON.parse("{\"base\":\"/dma_handbook/\",\"lang\":\"zh-CN\",\"title\":\"DMA服务人员服务操作手册\",\"description\":\"DMA服务人员服务操作手册\",\"head\":[[\"meta\",{\"name\":\"og:type\",\"content\":\"website\"}],[\"meta\",{\"property\":\"og:title\",\"content\":\"DMA服务人员操作手册\"}],[\"meta\",{\"name\":\"description\",\"content\":\"DMA服务操作手册\"}],[\"meta\",{\"property\":\"og:description\",\"content\":\"DMA服务全流程操作指南\"}],[\"meta\",{\"property\":\"og:image\",\"content\":\"https://images.health.ufutx.com/202503/12/1f227399ffc2ddbf6c58eafa80627d19.png?v=1769757244438\"}],[\"link\",{\"rel\":\"icon\",\"href\":\"https://images.health.ufutx.com/202503/12/1f227399ffc2ddbf6c58eafa80627d19.png?v=1769757244438\"}]],\"locales\":{}}")
|
||||
export const siteData = JSON.parse("{\"base\":\"/dma_handbook/\",\"lang\":\"zh-CN\",\"title\":\"DMA服务人员服务操作手册\",\"description\":\"DMA服务人员服务操作手册\",\"head\":[[\"meta\",{\"name\":\"og:type\",\"content\":\"website\"}],[\"meta\",{\"property\":\"og:title\",\"content\":\"DMA服务人员操作手册\"}],[\"meta\",{\"name\":\"description\",\"content\":\"DMA服务操作手册\"}],[\"meta\",{\"property\":\"og:description\",\"content\":\"DMA服务全流程操作指南\"}],[\"meta\",{\"property\":\"og:image\",\"content\":\"https://images.health.ufutx.com/202503/12/1f227399ffc2ddbf6c58eafa80627d19.png?v=1770021719831\"}],[\"link\",{\"rel\":\"icon\",\"href\":\"https://images.health.ufutx.com/202503/12/1f227399ffc2ddbf6c58eafa80627d19.png?v=1770021719831\"}]],\"locales\":{}}")
|
||||
|
||||
if (import.meta.webpackHot) {
|
||||
import.meta.webpackHot.accept()
|
||||
|
||||
@ -215,6 +215,8 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div v-auth="'coach'">
|
||||
</div>
|
||||
</div></template>
|
||||
|
||||
|
||||
|
||||
@ -1,17 +1,108 @@
|
||||
// 导入自定义布局
|
||||
import LoginLayout from './layouts/LoginLayout.vue'
|
||||
// 导入默认布局(必须保留,否则其他页面会404)
|
||||
import DefaultLayout from '@vuepress/theme-default/layouts/Layout.vue'
|
||||
|
||||
import { defineClientConfig } from 'vuepress/client'
|
||||
import { createPinia } from 'pinia'
|
||||
// 1. 补全所有缺失的核心导入(适配docs根目录的store/utils)
|
||||
import { useUserStore } from './store/modules/user'
|
||||
import { showToast, getUserInfo } from '../utils/request' // 新增getUserInfo
|
||||
// 核心修改:导入全局常量SITE_BASE
|
||||
import { SITE_BASE } from './constants.js';
|
||||
// 原有组件导入(位置不变,无需修改)
|
||||
import WithAuth from './components/WithAuth.vue'
|
||||
import Login from './components/Login.vue'
|
||||
import helperHTML from './components/helperHTML.vue'
|
||||
import longPic from './components/longPic.vue'
|
||||
|
||||
export default defineClientConfig({
|
||||
enhance({ app }) {
|
||||
// 新增:注册布局
|
||||
layouts: {
|
||||
Layout: DefaultLayout, // 默认布局
|
||||
LoginLayout: LoginLayout // 登录页布局
|
||||
},
|
||||
// 2. 解构出router(路由守卫必须用)
|
||||
enhance({ app, router }) {
|
||||
const pinia = createPinia()
|
||||
app.use(pinia)
|
||||
// 全局注册组件(原有逻辑,无问题)
|
||||
app.component('WithAuth', WithAuth)
|
||||
app.component('Login', Login)
|
||||
app.component('helperHTML', helperHTML)
|
||||
app.component('longPic', longPic)
|
||||
|
||||
// 权限指令v-auth:修复this指向+Pinia实例化问题
|
||||
app.directive('auth', {
|
||||
mounted(el, binding) {
|
||||
// 3. Pinia仓库必须执行实例化(核心修复)
|
||||
const userStore = useUserStore()
|
||||
const requiredRoles = binding.value
|
||||
if (!requiredRoles) return
|
||||
// 未登录/无权限直接隐藏
|
||||
if (!userStore.isLogin) {
|
||||
el.style.display = 'none'
|
||||
return
|
||||
}
|
||||
let hasPermission = false
|
||||
if (typeof requiredRoles === 'string') {
|
||||
hasPermission = userStore.hasRole(requiredRoles)
|
||||
} else if (Array.isArray(requiredRoles)) {
|
||||
hasPermission = requiredRoles.some(role => userStore.hasRole(role))
|
||||
}
|
||||
if (!hasPermission) el.style.display = 'none'
|
||||
},
|
||||
updated(el, binding) {
|
||||
// 4. 修复this指向问题:直接复用mounted逻辑,不用this
|
||||
this.mounted.call(this, el, binding)
|
||||
}
|
||||
})
|
||||
|
||||
// 全局路由守卫:VuePress 2.x 适配版(白名单+未登录跳登录)
|
||||
const whiteList = ['/login.html'] // 无需登录的页面
|
||||
// 隐藏登录功能
|
||||
// router.beforeEach(async (to, from, next) => {
|
||||
// const userStore = useUserStore()
|
||||
// const isLogin = userStore.isLogin
|
||||
//
|
||||
// // 白名单页面,直接放行
|
||||
// if (whiteList.includes(to.path)) {
|
||||
// next()
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // 未登录:跳登录页并记录跳转前地址(适配base: /dma_handbook/)
|
||||
// // 替换client.js中路由守卫的未登录跳转代码
|
||||
// if (!isLogin) {
|
||||
// showToast('请先登录后访问')
|
||||
// // 核心修复:直接使用to.fullPath(已包含SITE_BASE),无需手动拼接
|
||||
// let redirectPath = to.fullPath
|
||||
// // 兜底校验:极端情况若fullPath未带SITE_BASE,手动拼接(防止地址栏手动修改)
|
||||
// if (!redirectPath.startsWith(SITE_BASE)) {
|
||||
// redirectPath = `${SITE_BASE}${redirectPath.replace(/^\//, '')}` // 去掉开头的/,避免双斜杠
|
||||
// }
|
||||
// // 编码后拼接登录页地址
|
||||
// const redirect = encodeURIComponent(redirectPath)
|
||||
// window.location.href = `${SITE_BASE}login.html?redirect=${redirect}`
|
||||
// return
|
||||
// }
|
||||
// // 核心修改:仅【已登录 + 未拉取过用户信息】时,才调用接口
|
||||
// if (!userStore.isUserInfoFetched) {
|
||||
// await getUserInfo(userStore)
|
||||
// // 拉取成功后,更新标记为true(本次会话不再重复调用)
|
||||
// userStore.setUserInfoFetched(true)
|
||||
// }
|
||||
// // 已登录,正常放行
|
||||
// next()
|
||||
// })
|
||||
|
||||
// 全局退出登录方法:6. 挂载到window,所有环境(Markdown/组件)都能调用
|
||||
window.$logout = () => {
|
||||
const userStore = useUserStore()
|
||||
userStore.resetStore() // 清除Pinia+localStorage状态
|
||||
showToast('退出登录成功')
|
||||
window.location.href = `${SITE_BASE}login.html`
|
||||
}
|
||||
},
|
||||
setup() {},
|
||||
})
|
||||
|
||||
@ -1,96 +1,158 @@
|
||||
<script setup>
|
||||
import {reactive} from 'vue'
|
||||
import { useUserStore } from '../store/user'
|
||||
|
||||
const user = useUserStore()
|
||||
const form = reactive({ username: '', password: '' })
|
||||
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
await user.login(form)
|
||||
// 登录成功跳转逻辑
|
||||
} catch (err) {
|
||||
console.error('登录失败', err)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login-mask">
|
||||
<div class="login-dialog">
|
||||
<h3>用户登录</h3>
|
||||
<form @submit.prevent="handleLogin">
|
||||
<div class="input-group">
|
||||
<input
|
||||
v-model="form.username"
|
||||
class="styled-input"
|
||||
placeholder="用户名"
|
||||
>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
class="styled-input"
|
||||
placeholder="密码"
|
||||
>
|
||||
</div>
|
||||
<button type="submit" class="submit-btn">
|
||||
登录
|
||||
</button>
|
||||
</form>
|
||||
<div class="login-page">
|
||||
<div class="login-form">
|
||||
<h2>DMA服务手册 - 登录</h2>
|
||||
<div class="form-item">
|
||||
<label>账号:</label>
|
||||
<input v-model="form.username" type="text" placeholder="请输入账号" />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label>密码:</label>
|
||||
<input v-model="form.password" type="password" placeholder="请输入密码" />
|
||||
</div>
|
||||
<button class="login-btn" @click="handleLogin" :disabled="loading">
|
||||
{{ loading ? '登录中...' : '立即登录' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import request from '../../utils/request';
|
||||
import { useUserStore } from '../store/modules/user';
|
||||
import { showToast } from '../../utils/request';
|
||||
import { SITE_BASE } from '../constants.js';
|
||||
|
||||
// const VUEPRESS_BASE = '/dma_handbook/';
|
||||
|
||||
// 登录表单
|
||||
const form = ref({
|
||||
username: '15622316024',
|
||||
password: '123',
|
||||
});
|
||||
// 加载状态
|
||||
const loading = ref(false);
|
||||
// 获取用户仓库
|
||||
const userStore = useUserStore();
|
||||
// 跳转地址(从URL参数中获取)
|
||||
const redirect = ref('');
|
||||
|
||||
// 初始化,获取跳转前地址
|
||||
onMounted(() => {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
console.log(searchParams,'searchParams');
|
||||
// 获取URL中的redirect参数
|
||||
const rawRedirect = searchParams.get('redirect') || '';
|
||||
if (rawRedirect) {
|
||||
// 解析参数,若解析失败则兜底到首页
|
||||
try {
|
||||
redirect.value = decodeURIComponent(rawRedirect);
|
||||
} catch (e) {
|
||||
redirect.value = '';
|
||||
}
|
||||
}
|
||||
// 兜底:若redirect为空/不是以VUEPRESS_BASE开头,默认跳项目首页
|
||||
if (!redirect.value || !redirect.value.startsWith(SITE_BASE)) {
|
||||
redirect.value = `${SITE_BASE}`; // 首页地址:/dma_handbook/
|
||||
}
|
||||
console.log(redirect.value,'searchParams');
|
||||
});
|
||||
|
||||
// 处理登录
|
||||
const handleLogin = async () => {
|
||||
// 表单验证
|
||||
if (!form.value.username) {
|
||||
showToast('请输入账号');
|
||||
return;
|
||||
}
|
||||
if (!form.value.password) {
|
||||
showToast('请输入密码');
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
let data = {
|
||||
mobile: '15622316024',
|
||||
area_code: 86,
|
||||
code: '009527',
|
||||
}
|
||||
try {
|
||||
// 调用后端登录接口(替换为你的实际登录接口地址)
|
||||
const res = await request({
|
||||
url: '/go/api/app/server/mobile/code/login', // 示例接口,需替换
|
||||
method: 'POST',
|
||||
data: data,
|
||||
hideLoading: true, // 手动控制loading,避免重复提示
|
||||
});
|
||||
// 登录成功,回填数据(假设后端返回{token: 'xxx', userInfo: {id: 1, name: 'xxx', roles: ['coach']}})
|
||||
userStore.setToken(res.api_token);
|
||||
showToast('登录成功');
|
||||
// 跳转到目标页面
|
||||
setTimeout(() => {
|
||||
window.location.href = decodeURIComponent(redirect.value);
|
||||
}, 1000);
|
||||
} catch (err) {
|
||||
console.log('登录失败:', err);
|
||||
showToast(err.msg || err.message || '登录失败,请检查账号密码');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
.login-page {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 999;
|
||||
justify-content: center;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.login-dialog {
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
.login-form {
|
||||
width: 350px;
|
||||
padding: 30px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.1);
|
||||
width: 320px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.styled-input {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
.login-form h2 {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
}
|
||||
.form-item {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.form-item label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.form-item input {
|
||||
padding: 10px;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 1rem;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.styled-input:focus {
|
||||
border-color: #409eff;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
.form-item input:focus {
|
||||
border-color: #299764;
|
||||
}
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: #409eff;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
background: #299764;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.submit-btn:hover {
|
||||
opacity: 0.9;
|
||||
.login-btn:disabled {
|
||||
background: #96d8b7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { checkPermission, getCurrentUserRole, isLoggedIn, showLoginModal } from '../utils/auth';
|
||||
import { checkPermission, getCurrentUserRole, isLoggedIn, showLoginModal } from '../../utils/auth';
|
||||
|
||||
export default {
|
||||
props: ['requiredPerm'],
|
||||
|
||||
@ -4,19 +4,20 @@ import { defineUserConfig } from 'vuepress'
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
import { searchPlugin } from '@vuepress/plugin-search'
|
||||
|
||||
// 核心修改:从全局常量文件导入,替代本地定义
|
||||
import { SITE_BASE, CDN_BASE} from './constants.js';
|
||||
// import * as path from "path";
|
||||
// const path = require("path");
|
||||
|
||||
// import {registerComponentsPlugin} from '@vuepress/plugin-register-components'
|
||||
// const { registerComponentsPlugin } = require('@vuepress/plugin-register-components');
|
||||
// 配置常量(cn和微信封锁域名)
|
||||
const SITE_BASE = '/dma_handbook/'
|
||||
// const SITE_BASE = '/dma_handbook/'
|
||||
|
||||
// 配置常量(本地部署)
|
||||
// const SITE_BASE = '/go_html/dma_handbook/'
|
||||
|
||||
const CDN_BASE = 'https://images.health.ufutx.com/dp'
|
||||
// const CDN_BASE = 'https://images.health.ufutx.com/dp'
|
||||
|
||||
export default defineUserConfig({
|
||||
// ...其他配置...
|
||||
@ -33,7 +34,14 @@ export default defineUserConfig({
|
||||
// ['meta', { name: 'thumbnail', content: 'https://example.com/share-image.jpg' }],
|
||||
// ['meta', { name: 'WeChat-Description', content: '点击查看完整DMA服务流程' }]
|
||||
],
|
||||
|
||||
// 新增:自定义页面(登录页)
|
||||
pages: [
|
||||
// {
|
||||
// path: '/login', // 登录页路由,访问地址:/dma_handbook/login.html
|
||||
// title: 'DMA手册 - 登录',
|
||||
// component: './components/Login.vue', // 关联登录组件
|
||||
// },
|
||||
],
|
||||
bundler: viteBundler({
|
||||
// vite bundler options here
|
||||
viteOptions: {
|
||||
@ -50,7 +58,18 @@ export default defineUserConfig({
|
||||
// assetFileNames: 'assets/[name].[hash].[ext]',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 新增:跨域代理配置(核心)
|
||||
server: {
|
||||
proxy: {
|
||||
// 匹配所有以/api开头的请求,转发到后端地址
|
||||
'/api': {
|
||||
target: 'http://192.168.0.100:8080/', // 你的实际后端基础地址(和.env中的一致)
|
||||
changeOrigin: true, // 开启跨域
|
||||
rewrite: (path) => path.replace(/^\/api/, '') // 去掉请求路径中的/api前缀
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}),
|
||||
theme: defaultTheme({
|
||||
|
||||
14
docs/.vuepress/constants.js
Normal file
14
docs/.vuepress/constants.js
Normal file
@ -0,0 +1,14 @@
|
||||
// docs/.vuepress/constants.js
|
||||
// 全局基础路径 - 唯一维护,修改后全项目生效
|
||||
export const SITE_BASE = '/dma_handbook/';
|
||||
|
||||
// 配置常量(本地部署,需要时取消注释替换上面的SITE_BASE即可)
|
||||
// export const SITE_BASE = '/go_html/dma_handbook/';
|
||||
|
||||
// CDN基础路径
|
||||
export const CDN_BASE = 'https://images.health.ufutx.com/dp';
|
||||
|
||||
// 后端代理前缀(可选,也可统一维护)
|
||||
export const API_PROXY_PREFIX = '/api';
|
||||
// 后端实际地址(可选,统一维护)
|
||||
export const API_TARGET = 'http://192.168.0.100:8080/';
|
||||
9
docs/.vuepress/layouts/LoginLayout.vue
Normal file
9
docs/.vuepress/layouts/LoginLayout.vue
Normal file
@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<!-- 直接渲染登录组件 -->
|
||||
<Login />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 导入你的登录组件
|
||||
import Login from '../components/Login.vue'
|
||||
</script>
|
||||
43
docs/.vuepress/store/modules/user.js
Normal file
43
docs/.vuepress/store/modules/user.js
Normal file
@ -0,0 +1,43 @@
|
||||
import { defineStore } from 'pinia'
|
||||
export const useUserStore = defineStore('user', {
|
||||
state: () => ({
|
||||
token: localStorage.getItem('rt_token') || '',
|
||||
userInfo: JSON.parse(localStorage.getItem('userInfo') || '{}'),
|
||||
roles: JSON.parse(localStorage.getItem('roles') || '[]'),
|
||||
// 新增:会话级标记(是否已拉取过用户信息,不持久化,刷新自动重置)
|
||||
isUserInfoFetched: false
|
||||
}),
|
||||
getters: {
|
||||
isLogin: (state) => !!state.token, // 必须有
|
||||
hasRole: (state) => (role) => state.roles.includes(role), // 必须有
|
||||
},
|
||||
actions: {
|
||||
// 确保该方法存在:设置用户信息
|
||||
setUserInfo(userInfo) {
|
||||
this.userInfo = userInfo;
|
||||
localStorage.setItem('userInfo', JSON.stringify(userInfo)); // 持久化到本地
|
||||
},
|
||||
// 确保该方法存在:设置权限角色
|
||||
setRoles(roleList) {
|
||||
this.roles = roleList;
|
||||
localStorage.setItem('roles', JSON.stringify(roleList)); // 持久化到本地
|
||||
},
|
||||
// 新增:更新用户信息拉取标记
|
||||
setUserInfoFetched(status) {
|
||||
this.isUserInfoFetched = status;
|
||||
},
|
||||
resetStore() { // 必须有:退出登录清除状态
|
||||
this.token = ''
|
||||
this.userInfo = {}
|
||||
this.roles = []
|
||||
localStorage.removeItem('rt_token')
|
||||
localStorage.removeItem('userInfo')
|
||||
localStorage.removeItem('roles')
|
||||
},
|
||||
setToken(token) {
|
||||
this.token = token
|
||||
localStorage.setItem('rt_token', token)
|
||||
},
|
||||
// 其他setUserInfo/setRoles方法...
|
||||
},
|
||||
})
|
||||
@ -154,3 +154,5 @@
|
||||
| 第一次对用户的评估报告生成 | 方案结束后第 3 天 18:00 前完成 |
|
||||
| 用户上传复检报告提醒 | 方案结束后第 23 天发送 ` ` |
|
||||
| 第二次对用户的评估报告生成 | 方案结束后第 60 天 18:00 前完成 |
|
||||
<div v-auth="'coach'">
|
||||
</div>
|
||||
|
||||
4
docs/login.md
Normal file
4
docs/login.md
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: DMA手册 - 登录
|
||||
layout: LoginLayout
|
||||
---
|
||||
@ -1,4 +1,4 @@
|
||||
import { PERMISSIONS } from '../roles';
|
||||
import { PERMISSIONS } from '../.vuepress/roles';
|
||||
import axios from 'axios'
|
||||
export function getCurrentUserRole() {
|
||||
console.log('32-')
|
||||
@ -12,7 +12,7 @@ export function checkPermission(currentRole, requiredPath) {
|
||||
// console.log(rolePermissions,'rolePermissions=')
|
||||
// return rolePermissions.includes('*') || rolePermissions.includes(requiredPath);
|
||||
const allowedPaths = PERMISSIONS[currentRole] || [];
|
||||
return allowedPaths.some(path =>
|
||||
return allowedPaths.some(path =>
|
||||
requiredPath.startsWith(path) || path === '*'
|
||||
);
|
||||
}
|
||||
9
docs/utils/httpEnum.js
Normal file
9
docs/utils/httpEnum.js
Normal file
@ -0,0 +1,9 @@
|
||||
// 请求头Content-Type枚举
|
||||
export const ContentTypeEnum = {
|
||||
// 表单提交
|
||||
FORM_URLENCODED: 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||
// 文件上传
|
||||
FORM_DATA: 'multipart/form-data;charset=UTF-8',
|
||||
// JSON提交
|
||||
JSON: 'application/json;charset=UTF-8'
|
||||
};
|
||||
15
docs/utils/public.js
Normal file
15
docs/utils/public.js
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 登录成功后回填数据到Pinia仓库
|
||||
* @param {object} res - 登录接口返回的用户数据
|
||||
* @param {object} userStore - Pinia用户仓库实例
|
||||
*/
|
||||
export function backFillLoginData(res, userStore) {
|
||||
if (res.token) {
|
||||
userStore.setToken(res.token);
|
||||
localStorage.setItem('rt_token', res.token);
|
||||
}
|
||||
if (res.userInfo) {
|
||||
userStore.setUserInfo(res.userInfo);
|
||||
userStore.setRoles(res.userInfo.roles || []);
|
||||
}
|
||||
}
|
||||
159
docs/utils/request.js
Normal file
159
docs/utils/request.js
Normal file
@ -0,0 +1,159 @@
|
||||
import { ref } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { ContentTypeEnum } from './httpEnum';
|
||||
import { useUserStore } from '../.vuepress/store/modules/user';
|
||||
|
||||
// 适配VuePress服务端渲染,仅在浏览器环境执行
|
||||
const isBrowser = typeof window !== 'undefined';
|
||||
// 协议自动适配
|
||||
const baseHref = isBrowser ? (window.location.href.includes('https:') ? 'https:' : 'http:') : 'http:';
|
||||
// 创建axios实例
|
||||
const service = axios.create({
|
||||
baseURL: 'https://health.ufutx.com/',
|
||||
withCredentials: false,
|
||||
timeout: 50000,
|
||||
});
|
||||
|
||||
// 自定义请求配置(仅保留hideLoading扩展)
|
||||
export const requestConfig = {
|
||||
hideLoading: false
|
||||
};
|
||||
|
||||
// 原生加载提示(极简版,适配VuePress)
|
||||
let loadingInstance = null;
|
||||
const showLoading = (msg = '加载中...') => {
|
||||
if (!isBrowser || loadingInstance) return;
|
||||
loadingInstance = document.createElement('div');
|
||||
loadingInstance.style.cssText = `position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);padding:8px 16px;background:rgba(0,0,0,0.7);color:#fff;border-radius:4px;z-index:99999;font-size:14px;`;
|
||||
loadingInstance.innerText = msg;
|
||||
document.body.appendChild(loadingInstance);
|
||||
};
|
||||
const closeLoading = () => {
|
||||
if (!isBrowser || !loadingInstance) return;
|
||||
document.body.removeChild(loadingInstance);
|
||||
loadingInstance = null;
|
||||
};
|
||||
|
||||
// 原生全局提示(极简版)
|
||||
export const showToast = (msg = '操作失败', duration = 2000) => {
|
||||
if (!isBrowser) return;
|
||||
const toast = document.createElement('div');
|
||||
toast.style.cssText = `position:fixed;top:80%;left:50%;transform:translate(-50%,-50%);padding:8px 16px;background:rgba(0,0,0,0.7);color:#fff;border-radius:4px;z-index:99999;font-size:14px;pointer-events:none;`;
|
||||
toast.innerText = msg;
|
||||
document.body.appendChild(toast);
|
||||
setTimeout(() => document.body.removeChild(toast), duration);
|
||||
};
|
||||
|
||||
// 请求拦截器:核心保留Token、Content-Type、版本号
|
||||
service.interceptors.request.use(
|
||||
(config) => {
|
||||
// 加载提示控制
|
||||
if (!config.hideLoading) showLoading();
|
||||
// 初始化请求头
|
||||
if (!config.headers) config.headers = {};
|
||||
// 默认Content-Type为JSON
|
||||
config.headers['Content-Type'] = config.headers['Content-Type'] || ContentTypeEnum.JSON;
|
||||
// 携带版本号
|
||||
if (isBrowser) config.headers['Version'] = import.meta.env.VITE_BASE_API_VERSION;
|
||||
// 携带Token(仅保留核心rt_token,联动Pinia)
|
||||
if (isBrowser) {
|
||||
const userStore = useUserStore();
|
||||
const token = userStore.token || localStorage.getItem('rt_token') || '';
|
||||
if (token) config.headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
// 处理POST表单参数
|
||||
if (config.method?.toLocaleUpperCase() === 'POST' && config.data) {
|
||||
const contentType = config.headers['Content-Type'];
|
||||
if (contentType === ContentTypeEnum.FORM_URLENCODED) {
|
||||
config.data = qs.stringify(config.data);
|
||||
}
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
closeLoading();
|
||||
console.log('请求错误:', error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// 响应拦截器:精简状态码,仅保留核心业务逻辑
|
||||
service.interceptors.response.use(
|
||||
(response) => {
|
||||
closeLoading();
|
||||
const res = response.data;
|
||||
// 成功码:仅保留0(后端统一成功标识,剔除冗余兼容)
|
||||
if (res.code === 0) return Promise.resolve(res.data);
|
||||
// 业务错误:code!==0统一提示
|
||||
showToast(res.msg || res.message || '请求失败');
|
||||
return Promise.reject(res);
|
||||
},
|
||||
(error) => {
|
||||
closeLoading();
|
||||
// 网络/超时错误
|
||||
if (error.message?.includes('timeout')) {
|
||||
showToast('网络超时,请稍后重试');
|
||||
}
|
||||
// 401未授权:核心鉴权逻辑,跳登录页(适配VuePress base路径)
|
||||
else if (error.response?.status === 401 && isBrowser) {
|
||||
showToast('登录已过期,请重新登录');
|
||||
const userStore = useUserStore();
|
||||
// 清除状态,跳登录页并记录跳转地址
|
||||
userStore.resetStore();
|
||||
localStorage.removeItem('rt_token');
|
||||
const redirect = encodeURIComponent(window.location.href);
|
||||
window.location.href = `/dma_handbook/login?redirect=${redirect}`;
|
||||
}
|
||||
// 其他错误
|
||||
else {
|
||||
showToast(error.message || '网络异常');
|
||||
}
|
||||
console.log('响应错误:', error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// 统一请求方法:极简封装,适配VuePress
|
||||
const request = (config) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
service
|
||||
.request(config)
|
||||
.then((res) => resolve(res))
|
||||
.catch((err) => reject(err));
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 放在request.js文件末尾,showToast导出后、request默认导出前
|
||||
/**
|
||||
* 全局获取用户信息接口(每次进入页面调用)
|
||||
* @param {object} userStore - Pinia用户仓库实例(传入避免重复实例化)
|
||||
* @returns {Promise<boolean>} - 是否获取成功
|
||||
*/
|
||||
export const getUserInfo = async (userStore) => {
|
||||
// 非浏览器环境/未登录,直接返回失败
|
||||
if (typeof window === 'undefined' || !userStore.token) return false;
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/api/app/get/service/user/info', // 你的用户信息接口地址,baseURL会自动拼接
|
||||
method: 'get',
|
||||
hideLoading: true, // 隐藏加载提示,避免页面闪烁
|
||||
});
|
||||
// 按你的接口返回格式更新仓库
|
||||
if (res && res.service_user_info) {
|
||||
userStore.setUserInfo(res.service_user_info); // 更新用户信息
|
||||
}
|
||||
if (res && res.role_list) {
|
||||
userStore.setRoles(res.role_list); // 更新权限角色
|
||||
}
|
||||
return true;
|
||||
} catch (err) {
|
||||
// 接口失败轻量提示,不阻塞页面
|
||||
showToast('用户信息同步失败', 1500);
|
||||
console.log('获取用户信息失败:', err);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export default request;
|
||||
Loading…
Reference in New Issue
Block a user