diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..efe7735 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +unpackage \ No newline at end of file diff --git a/App.vue b/App.vue index 8c2b732..a98e355 100644 --- a/App.vue +++ b/App.vue @@ -1,15 +1,32 @@ - diff --git a/stores/token.js b/stores/token.js new file mode 100644 index 0000000..73e6125 --- /dev/null +++ b/stores/token.js @@ -0,0 +1,42 @@ +import { defineStore } from 'pinia'; +import { ref } from 'vue'; +import { STORAGE_KEYS } from '@/constants/storageKeys'; +import { getToken, removeToken } from '@/utils/storage'; + +/** 登录状态 */ +export const useTokenStore = defineStore(STORAGE_KEYS.TOKEN, () => { + + // 从本地存储获取token + const token = ref(getToken() || null); + const isLogin = ref(!!token.value); + + /** 设置token并保存到本地 */ + const setToken = (newToken) => { + token.value = newToken; + isLogin.value = true; + setToken(newToken); + }; + + /** 清除token */ + const clearToken = () => { + token.value = null; + isLogin.value = false; + removeToken() + }; + + /** 检查token是否有效(可扩展过期时间判断) */ + const checkToken = () => { + // 简单判断:token存在且不为空 + return !!token.value; + }; + + /** 验证token是否过期(实际项目中可添加过期时间判断) */ + const isTokenExpired = () => { + // 示例:如果token中包含过期时间,这里可以判断 + // 例如:const expireTime = parseInt(token.value.split('.')[1]); + // return Date.now() > expireTime * 1000; + return false; // 默认不过期 + }; + + return { token, isLogin, setToken, clearToken, checkToken, isTokenExpired }; +}) \ No newline at end of file diff --git a/utils/request.js b/utils/request.js new file mode 100644 index 0000000..f67862c --- /dev/null +++ b/utils/request.js @@ -0,0 +1,126 @@ +import { getToken, removeToken } from './storage'; + +const BASE_URL = 'xxxxx' + +/** + * 网络请求封装 + * @param {Object} options 请求参数 + * @returns {Promise} + */ +const request = (options) => { + // 默认配置 + const defaultOptions = { + url: '', + method: 'GET', + data: {}, + header: { + 'Content-Type': 'application/json' // 默认请求内容类型 + } + } + + // 合并配置 + const config = { ...defaultOptions, ...options } + + // 请求拦截:添加token等通用header + if (getToken()) { + config.header['Authorization'] = 'Bearer ' + getToken() + } + + // 显示加载状态(可选) + if (options.loading !== false) { + uni.showLoading({ + title: '加载中...', + mask: true + }) + } + + return new Promise((resolve, reject) => { + uni.request({ + url: BASE_URL + config.url, + method: config.method, + data: config.data, + timeout: 10000, // 请求超时时间 + header: config.header, + success: (response) => { + // 响应拦截:根据状态码处理 + if (response.statusCode === 200) { + // 这里可以根据后端数据格式调整 + // 例如:if (response.data.code === 0) {...} + resolve(response.data) + } else { + // 状态码错误处理 + handleError(response.statusCode, response.data) + reject(response) + } + }, + fail: (error) => { + // 网络错误处理 + uni.showToast({ + title: '网络异常,请检查网络连接', + icon: 'none', + duration: 2000 + }) + reject(error) + }, + complete: () => { + // 隐藏加载状态 + if (options.loading !== false) { + uni.hideLoading() + } + } + }) + }) +} + +/** + * 错误处理函数 + * @param {Number} statusCode HTTP状态码 + * @param {Object} data 响应数据 + */ +const handleError = (statusCode, data) => { + switch (statusCode) { + case 401: + uni.showModal({ + title: '提示', + content: '登录已过期,请重新登录', + showCancel: false, + success: () => { + // 清除本地存储的token并跳转到登录页 + removeToken() + uni.navigateTo({ + url: '/pages/login/index' + }) + } + }) + break + case 403: + uni.showToast({ + title: '没有权限访问', + icon: 'none', + duration: 2000 + }) + break + case 404: + uni.showToast({ + title: '请求资源不存在', + icon: 'none', + duration: 2000 + }) + break + case 500: + uni.showToast({ + title: '服务器内部错误', + icon: 'none', + duration: 2000 + }) + break + default: + uni.showToast({ + title: data.message || '请求失败,请重试', + icon: 'none', + duration: 2000 + }) + } +} + +export default request \ No newline at end of file diff --git a/utils/router.js b/utils/router.js new file mode 100644 index 0000000..38f1ced --- /dev/null +++ b/utils/router.js @@ -0,0 +1,66 @@ +// 统一页面跳转工具函数 + +/** + * 辅助函数:拼接 URL 参数 + * @param {string} url - 页面路径,如 '/pages/home/home' + * @param {object} params - 参数对象,如 { id: 123, name: 'test' } + * @returns {string} 拼接后的完整 url + */ +const appendParams = (url, params) => { + if (!params || Object.keys(params).length === 0) { + return url + } + + const query = Object.entries(params).map(([key, value]) => { + // 处理复杂类型(如对象、数组)需序列化 + if (typeof value === 'object') { + value = encodeURIComponent(JSON.stringify(value)) + } else { + value = encodeURIComponent(String(value)) + } + return `${key}=${value}` + }).join('&') + + return url.includes('?') ? `${url}&${query}` : `${url}?${query}` +} + +/** + * 普通跳转(保留返回) + */ +export const navigateTo = (url, params = {}) => { + const finalUrl = appendParams(url, params) + return uni.navigateTo({ url: finalUrl }) +} + +/** + * 关闭当前页,跳转到应用内某个页面(不可返回) + */ +export const redirectTo = (url, params = {}) => { + const finalUrl = appendParams(url, params) + return uni.redirectTo({ url: finalUrl }) +} + +/** + * 关闭所有页面,打开到应用内某个页面 + */ +export const reLaunch = (url, params = {}) => { + const finalUrl = appendParams(url, params) + return uni.reLaunch({ url: finalUrl }) +} + +/** + * 返回上一页(可指定 delta) + */ +export const navigateBack = (delta = 1) => { + return uni.navigateBack({ delta }) +} + +/** + * 跳转到 tabBar 页面(只能用 switchTab) + */ +export const switchTab = (url, params = {}) => { + if (Object.keys(params).length > 0) { + console.warn('switchTab 不支持携带参数,请使用全局状态或 storage 传递') + } + return uni.switchTab({ url }) +} \ No newline at end of file diff --git a/utils/storage.js b/utils/storage.js new file mode 100644 index 0000000..baeffa0 --- /dev/null +++ b/utils/storage.js @@ -0,0 +1,16 @@ +import { STORAGE_KEYS } from '@/constants/storageKeys' + +/** 保存 token */ +export const setToken = (v) => { + return uni.setStorageSync(STORAGE_KEYS.TOKEN, v) +} + +/** 获取 token */ +export const getToken = () => { + return uni.getStorageSync(STORAGE_KEYS.TOKEN) || '' +} + +/** 清楚 token */ +export const removeToken = () => { + return uni.removeStorageSync(STORAGE_KEYS.TOKEN) +} \ No newline at end of file