-
用户登录
-
+
+
+
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;