From 118aab4f3bb5c4790ce2ecfc3c5897cfd7146161 Mon Sep 17 00:00:00 2001
From: lanzhihui <503792708@qq.com>
Date: Thu, 5 Feb 2026 10:24:40 +0800
Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=99=BB=E5=BD=95=E5=8A=9F?=
=?UTF-8?q?=E8=83=BD=E5=92=8C=E6=9D=83=E9=99=90=E9=AA=8C=E8=AF=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/.vuepress/.temp/internal/routes.js | 1 +
docs/.vuepress/.temp/internal/siteData.js | 2 +-
docs/.vuepress/.temp/pages/index.html.vue | 2 +
docs/.vuepress/client.js | 93 +++++++++-
docs/.vuepress/components/Login.vue | 212 ++++++++++++++--------
docs/.vuepress/components/WithAuth.vue | 2 +-
docs/.vuepress/config.js | 29 ++-
docs/.vuepress/constants.js | 14 ++
docs/.vuepress/layouts/LoginLayout.vue | 9 +
docs/.vuepress/store/modules/user.js | 43 +++++
docs/README.md | 2 +
docs/login.md | 4 +
docs/{.vuepress => }/utils/auth.js | 4 +-
docs/utils/httpEnum.js | 9 +
docs/utils/public.js | 15 ++
docs/utils/request.js | 159 ++++++++++++++++
16 files changed, 515 insertions(+), 85 deletions(-)
create mode 100644 docs/.vuepress/constants.js
create mode 100644 docs/.vuepress/layouts/LoginLayout.vue
create mode 100644 docs/.vuepress/store/modules/user.js
create mode 100644 docs/login.md
rename docs/{.vuepress => }/utils/auth.js (92%)
create mode 100644 docs/utils/httpEnum.js
create mode 100644 docs/utils/public.js
create mode 100644 docs/utils/request.js
diff --git a/docs/.vuepress/.temp/internal/routes.js b/docs/.vuepress/.temp/internal/routes.js
index 4c15038..4273b03 100644
--- a/docs/.vuepress/.temp/internal/routes.js
+++ b/docs/.vuepress/.temp/internal/routes.js
@@ -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":"代理商添加方式说明"} }],
diff --git a/docs/.vuepress/.temp/internal/siteData.js b/docs/.vuepress/.temp/internal/siteData.js
index dc8aa6c..ba416ff 100644
--- a/docs/.vuepress/.temp/internal/siteData.js
+++ b/docs/.vuepress/.temp/internal/siteData.js
@@ -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()
diff --git a/docs/.vuepress/.temp/pages/index.html.vue b/docs/.vuepress/.temp/pages/index.html.vue
index 9c1e926..54aaa2e 100644
--- a/docs/.vuepress/.temp/pages/index.html.vue
+++ b/docs/.vuepress/.temp/pages/index.html.vue
@@ -215,6 +215,8 @@
+
+
diff --git a/docs/.vuepress/client.js b/docs/.vuepress/client.js
index e6ff649..9583ab5 100644
--- a/docs/.vuepress/client.js
+++ b/docs/.vuepress/client.js
@@ -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() {},
})
diff --git a/docs/.vuepress/components/Login.vue b/docs/.vuepress/components/Login.vue
index 0b53c35..b51554e 100644
--- a/docs/.vuepress/components/Login.vue
+++ b/docs/.vuepress/components/Login.vue
@@ -1,96 +1,158 @@
-
-
-
-
-
用户登录
-
+
+
+
diff --git a/docs/.vuepress/components/WithAuth.vue b/docs/.vuepress/components/WithAuth.vue
index a6e7d04..4b680ed 100644
--- a/docs/.vuepress/components/WithAuth.vue
+++ b/docs/.vuepress/components/WithAuth.vue
@@ -11,7 +11,7 @@
diff --git a/docs/.vuepress/store/modules/user.js b/docs/.vuepress/store/modules/user.js
new file mode 100644
index 0000000..392ca7d
--- /dev/null
+++ b/docs/.vuepress/store/modules/user.js
@@ -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方法...
+ },
+})
diff --git a/docs/README.md b/docs/README.md
index 3da93e9..b78ab8c 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -154,3 +154,5 @@
| 第一次对用户的评估报告生成 | 方案结束后第 3 天 18:00 前完成 |
| 用户上传复检报告提醒 | 方案结束后第 23 天发送 ` ` |
| 第二次对用户的评估报告生成 | 方案结束后第 60 天 18:00 前完成 |
+
+
diff --git a/docs/login.md b/docs/login.md
new file mode 100644
index 0000000..3fb9fed
--- /dev/null
+++ b/docs/login.md
@@ -0,0 +1,4 @@
+---
+title: DMA手册 - 登录
+layout: LoginLayout
+---
diff --git a/docs/.vuepress/utils/auth.js b/docs/utils/auth.js
similarity index 92%
rename from docs/.vuepress/utils/auth.js
rename to docs/utils/auth.js
index c500954..3f290f2 100644
--- a/docs/.vuepress/utils/auth.js
+++ b/docs/utils/auth.js
@@ -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 === '*'
);
}
diff --git a/docs/utils/httpEnum.js b/docs/utils/httpEnum.js
new file mode 100644
index 0000000..3a71606
--- /dev/null
+++ b/docs/utils/httpEnum.js
@@ -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'
+};
diff --git a/docs/utils/public.js b/docs/utils/public.js
new file mode 100644
index 0000000..0b05f91
--- /dev/null
+++ b/docs/utils/public.js
@@ -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 || []);
+ }
+}
diff --git a/docs/utils/request.js b/docs/utils/request.js
new file mode 100644
index 0000000..04b86b2
--- /dev/null
+++ b/docs/utils/request.js
@@ -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
} - 是否获取成功
+ */
+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;