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