From 5dc52ce6df67d6189b5e24f16b535484b6e0cd7f Mon Sep 17 00:00:00 2001 From: liyongde <1419499670@qq.com> Date: Mon, 12 Jan 2026 19:13:10 +0800 Subject: [PATCH] init project --- .gitignore | 29 ++++ App.vue | 21 +++ README.md | 136 ++++++++++++++++++ env.example | 11 ++ index.html | 17 +++ main.js | 18 +++ manifest.json | 116 +++++++++++++++ package.json | 66 +++++++++ pages.json | 37 +++++ pages/index/index.vue | 291 ++++++++++++++++++++++++++++++++++++++ src/api/index.js | 77 ++++++++++ src/components/README.md | 25 ++++ src/stores/user.js | 55 +++++++ src/styles/common.scss | 122 ++++++++++++++++ src/styles/variables.scss | 34 +++++ src/utils/index.js | 164 +++++++++++++++++++++ src/utils/request.js | 205 +++++++++++++++++++++++++++ static/.gitkeep | 3 + tsconfig.json | 26 ++++ tsconfig.node.json | 11 ++ vite.config.js | 36 +++++ 21 files changed, 1500 insertions(+) create mode 100644 .gitignore create mode 100644 App.vue create mode 100644 README.md create mode 100644 env.example create mode 100644 index.html create mode 100644 main.js create mode 100644 manifest.json create mode 100644 package.json create mode 100644 pages.json create mode 100644 pages/index/index.vue create mode 100644 src/api/index.js create mode 100644 src/components/README.md create mode 100644 src/stores/user.js create mode 100644 src/styles/common.scss create mode 100644 src/styles/variables.scss create mode 100644 src/utils/index.js create mode 100644 src/utils/request.js create mode 100644 static/.gitkeep create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b8356fc --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# uni-app +unpackage +.hbuilderx + diff --git a/App.vue b/App.vue new file mode 100644 index 0000000..decbe2a --- /dev/null +++ b/App.vue @@ -0,0 +1,21 @@ + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..7625f73 --- /dev/null +++ b/README.md @@ -0,0 +1,136 @@ +# nl-uniapp + +基于 Vue3 + Vite + Ant Design Vue 的 uni-app 项目模板 + +## 技术栈 + +- **框架**: Vue 3 +- **构建工具**: Vite +- **UI组件库**: Ant Design Vue 4.x +- **状态管理**: Pinia +- **HTTP请求**: Axios (已封装) +- **样式预处理**: Sass/SCSS + +## 项目结构 + +``` +nl-uniapp/ +├── pages/ # 页面目录 +│ └── index/ # 首页 +├── src/ +│ ├── api/ # API接口 +│ │ └── index.js # 接口定义 +│ ├── stores/ # Pinia状态管理 +│ │ └── user.js # 用户状态 +│ ├── utils/ # 工具函数 +│ │ └── request.js # axios封装 +│ └── styles/ # 样式文件 +│ ├── common.scss # 公共样式 +│ └── variables.scss # 样式变量 +├── static/ # 静态资源 +├── App.vue # 应用入口 +├── main.js # 主入口文件 +├── pages.json # 页面配置 +├── manifest.json # 应用配置 +├── vite.config.js # Vite配置 +└── package.json # 项目配置 +``` + +## 快速开始 + +### 安装依赖 + +```bash +npm install +# 或 +yarn install +# 或 +pnpm install +``` + +### 开发运行 + +```bash +# H5 +npm run dev:h5 + +# 微信小程序 +npm run dev:mp-weixin + +# 支付宝小程序 +npm run dev:mp-alipay + +# 其他平台... +``` + +### 构建打包 + +```bash +# H5 +npm run build:h5 + +# 微信小程序 +npm run build:mp-weixin + +# 其他平台... +``` + +## 功能特性 + +### 1. Axios封装 + +已封装好的axios请求工具,位于 `src/utils/request.js`,包含: + +- 请求/响应拦截器 +- 自动添加token +- 统一错误处理 +- Loading状态管理 +- 支持GET、POST、PUT、DELETE、文件上传等方法 + +使用示例: + +```javascript +import { api } from '@/api' + +// GET请求 +const res = await api.getList({ page: 1 }) + +// POST请求 +const res = await api.create({ name: '测试' }) +``` + +### 2. 状态管理 + +使用Pinia进行状态管理,示例store位于 `src/stores/user.js` + +### 3. API接口管理 + +所有API接口统一管理在 `src/api/index.js`,方便维护和调用 + +### 4. 样式系统 + +- 支持SCSS预处理器 +- 统一的样式变量定义 +- 公共样式类 + +## 注意事项 + +1. **Ant Design Vue兼容性**: Ant Design Vue主要针对H5端,在小程序端可能不完全兼容,建议: + - H5端使用Ant Design Vue组件 + - 小程序端使用uni-app原生组件或uni-ui组件库 + +2. **API配置**: 请在 `src/utils/request.js` 中修改 `baseURL` 为你的实际API地址 + +3. **Token管理**: Token会自动从store中获取并添加到请求头,请确保正确设置token + +## 开发建议 + +1. 页面开发时,优先使用uni-app原生组件以保证跨平台兼容性 +2. 样式使用rpx单位以适配不同屏幕尺寸 +3. API请求统一使用封装好的request工具 +4. 状态管理使用Pinia,避免直接使用localStorage + +## License + +MIT + diff --git a/env.example b/env.example new file mode 100644 index 0000000..ed80a8e --- /dev/null +++ b/env.example @@ -0,0 +1,11 @@ +# 环境变量配置示例 +# 复制此文件为 .env.development 和 .env.production 并修改相应配置 + +# 开发环境 +VITE_APP_BASE_API=/api +VITE_APP_TITLE=nl-uniapp + +# 生产环境 +# VITE_APP_BASE_API=https://your-api-domain.com +# VITE_APP_TITLE=nl-uniapp + diff --git a/index.html b/index.html new file mode 100644 index 0000000..16ea8d8 --- /dev/null +++ b/index.html @@ -0,0 +1,17 @@ + + + + + + nl-uniapp + + + +
+ + + + diff --git a/main.js b/main.js new file mode 100644 index 0000000..128d1bb --- /dev/null +++ b/main.js @@ -0,0 +1,18 @@ +import { createSSRApp } from 'vue' +import App from './App.vue' +import Antd from 'ant-design-vue' +import 'ant-design-vue/dist/reset.css' +import { createPinia } from 'pinia' + +export function createApp() { + const app = createSSRApp(App) + const pinia = createPinia() + + app.use(pinia) + app.use(Antd) + + return { + app + } +} + diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..eaf9610 --- /dev/null +++ b/manifest.json @@ -0,0 +1,116 @@ +{ + "name": "nl-uniapp", + "appid": "__UNI__NLUNAPP", + "description": "uniapp项目", + "versionName": "1.0.0", + "versionCode": "100", + "transformPx": false, + "app-plus": { + "usingComponents": true, + "nvueStyleCompiler": "uni-app", + "compilerVersion": 3, + "splashscreen": { + "alwaysShowBeforeRender": true, + "waiting": true, + "autoclose": true, + "delay": 0 + }, + "modules": {}, + "distribute": { + "android": { + "permissions": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "ios": {}, + "sdkConfigs": {} + } + }, + "quickapp": {}, + "mp-weixin": { + "appid": "", + "setting": { + "urlCheck": false, + "es6": true, + "enhance": true, + "postcss": true, + "preloadBackgroundData": false, + "minified": true, + "newFeature": false, + "coverView": true, + "nodeModules": false, + "autoAudits": false, + "showShadowRootInWxmlPanel": true, + "scopeDataCheck": false, + "uglifyFileName": false, + "checkInvalidKey": true, + "checkSiteMap": true, + "uploadWithSourceMap": true, + "compileHotReLoad": false, + "lazyloadPlaceholderEnable": false, + "useMultiFrameRuntime": true, + "useApiHook": true, + "useApiHostProcess": true, + "babelSetting": { + "ignore": [], + "disablePlugins": [], + "outputPath": "" + }, + "enableEngineNative": false, + "useIsolateContext": true, + "userConfirmedBundleSwitch": false, + "packNpmManually": false, + "packNpmRelationList": [], + "minifyWXSS": true, + "showES6CompileOption": false, + "minifyWXML": true, + "useStaticServer": true + }, + "usingComponents": true, + "permission": { + "scope.userLocation": { + "desc": "你的位置信息将用于小程序位置接口的效果展示" + } + }, + "optimization": { + "subPackages": true + } + }, + "mp-alipay": { + "usingComponents": true + }, + "mp-baidu": { + "usingComponents": true + }, + "mp-toutiao": { + "usingComponents": true + }, + "uniStatistics": { + "enable": false + }, + "vueVersion": "3", + "h5": { + "devServer": { + "port": 3000, + "disableHostCheck": true + }, + "router": { + "mode": "hash" + } + } +} + diff --git a/package.json b/package.json new file mode 100644 index 0000000..fb993f9 --- /dev/null +++ b/package.json @@ -0,0 +1,66 @@ +{ + "name": "nl-uniapp", + "version": "1.0.0", + "description": "uniapp项目 - Vue3 + Vite + Ant Design Vue", + "main": "main.js", + "scripts": { + "install": "npm install", + "dev:h5": "uni", + "dev:mp-weixin": "uni -p mp-weixin", + "dev:mp-alipay": "uni -p mp-alipay", + "dev:mp-baidu": "uni -p mp-baidu", + "dev:mp-toutiao": "uni -p mp-toutiao", + "dev:mp-qq": "uni -p mp-qq", + "dev:mp-kuaishou": "uni -p mp-kuaishou", + "dev:mp-lark": "uni -p mp-lark", + "dev:quickapp-webview": "uni -p quickapp-webview", + "dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei", + "dev:quickapp-webview-union": "uni -p quickapp-webview-union", + "build:h5": "uni build", + "build:mp-weixin": "uni build -p mp-weixin", + "build:mp-alipay": "uni build -p mp-alipay", + "build:mp-baidu": "uni build -p mp-baidu", + "build:mp-toutiao": "uni build -p mp-toutiao", + "build:mp-qq": "uni build -p mp-qq", + "build:mp-kuaishou": "uni build -p mp-kuaishou", + "build:mp-lark": "uni build -p mp-lark", + "build:quickapp-webview": "uni build -p quickapp-webview", + "build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei", + "build:quickapp-webview-union": "uni build -p quickapp-webview-union" + }, + "dependencies": { + "@dcloudio/uni-app": "^3.0.0", + "@dcloudio/uni-app-plus": "^3.0.0", + "@dcloudio/uni-components": "^3.0.0", + "@dcloudio/uni-h5": "^3.0.0", + "@dcloudio/uni-mp-alipay": "^3.0.0", + "@dcloudio/uni-mp-baidu": "^3.0.0", + "@dcloudio/uni-mp-kuaishou": "^3.0.0", + "@dcloudio/uni-mp-lark": "^3.0.0", + "@dcloudio/uni-mp-qq": "^3.0.0", + "@dcloudio/uni-mp-toutiao": "^3.0.0", + "@dcloudio/uni-mp-weixin": "^3.0.0", + "@dcloudio/uni-quickapp-webview": "^3.0.0", + "ant-design-vue": "^4.0.0", + "axios": "^1.6.0", + "pinia": "^2.1.0", + "vue": "^3.3.0" + }, + "devDependencies": { + "@dcloudio/types": "^3.4.0", + "@dcloudio/uni-automator": "^3.0.0", + "@dcloudio/uni-cli-shared": "^3.0.0", + "@dcloudio/vite-plugin-uni": "^3.0.0", + "@vitejs/plugin-vue": "^4.5.0", + "sass": "^1.69.0", + "vite": "^5.0.0" + }, + "browserslist": [ + "Android >= 4.4", + "ios >= 9" + ], + "uni-app": { + "scripts": {} + } +} + diff --git a/pages.json b/pages.json new file mode 100644 index 0000000..67a7aae --- /dev/null +++ b/pages.json @@ -0,0 +1,37 @@ +{ + "easycom": { + "autoscan": true, + "custom": { + "^u-(.*)": "uview-ui/components/u-$1/u-$1.vue" + } + }, + "pages": [ + { + "path": "pages/index/index", + "style": { + "navigationBarTitleText": "首页" + } + } + ], + "globalStyle": { + "navigationBarTextStyle": "black", + "navigationBarTitleText": "nl-uniapp", + "navigationBarBackgroundColor": "#F8F8F8", + "backgroundColor": "#F8F8F8" + }, + "tabBar": { + "color": "#7A7E83", + "selectedColor": "#3cc51f", + "borderStyle": "black", + "backgroundColor": "#ffffff", + "list": [ + { + "pagePath": "pages/index/index", + "iconPath": "static/tab-home.png", + "selectedIconPath": "static/tab-home-current.png", + "text": "首页" + } + ] + } +} + diff --git a/pages/index/index.vue b/pages/index/index.vue new file mode 100644 index 0000000..be2bc7e --- /dev/null +++ b/pages/index/index.vue @@ -0,0 +1,291 @@ + + + + + + diff --git a/src/api/index.js b/src/api/index.js new file mode 100644 index 0000000..60df025 --- /dev/null +++ b/src/api/index.js @@ -0,0 +1,77 @@ +import request from '@/utils/request' +import { useUserStore } from '@/stores/user' + +// 获取token的辅助函数 +function getToken() { + try { + const userStore = useUserStore() + return userStore.getToken() || '' + } catch (e) { + return '' + } +} + +// 示例API接口 +export const api = { + // 获取用户信息 + getUserInfo() { + return request.get('/user/info') + }, + + // 登录 + login(data) { + return request.post('/user/login', data) + }, + + // 退出登录 + logout() { + return request.post('/user/logout') + }, + + // 获取列表数据 + getList(params) { + return request.get('/list', params) + }, + + // 创建数据 + create(data) { + return request.post('/create', data) + }, + + // 更新数据 + update(id, data) { + return request.put(`/update/${id}`, data) + }, + + // 删除数据 + delete(id) { + return request.delete(`/delete/${id}`) + }, + + // 上传文件 + uploadFile(filePath) { + return new Promise((resolve, reject) => { + const baseURL = import.meta.env.VITE_APP_BASE_API || (process.env.NODE_ENV === 'development' ? '/api' : 'https://your-api-domain.com') + uni.uploadFile({ + url: baseURL + '/upload', + filePath: filePath, + name: 'file', + header: { + Authorization: `Bearer ${getToken()}` + }, + success: (res) => { + try { + const data = JSON.parse(res.data) + resolve(data) + } catch (e) { + resolve(res.data) + } + }, + fail: (err) => { + reject(err) + } + }) + }) + } +} + diff --git a/src/components/README.md b/src/components/README.md new file mode 100644 index 0000000..3e89268 --- /dev/null +++ b/src/components/README.md @@ -0,0 +1,25 @@ +# 组件目录 + +此目录用于存放自定义组件。 + +## 使用方式 + +在页面中引入组件: + +```vue + + + +``` + +## 组件命名规范 + +- 组件文件名使用 PascalCase(大驼峰命名) +- 组件名与文件名保持一致 + diff --git a/src/stores/user.js b/src/stores/user.js new file mode 100644 index 0000000..d51e1d3 --- /dev/null +++ b/src/stores/user.js @@ -0,0 +1,55 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' + +export const useUserStore = defineStore('user', () => { + const token = ref('') + const userInfo = ref(null) + + // 设置token + function setToken(val) { + token.value = val + // 可以同时存储到本地 + uni.setStorageSync('token', val) + } + + // 获取token + function getToken() { + if (!token.value) { + token.value = uni.getStorageSync('token') || '' + } + return token.value + } + + // 设置用户信息 + function setUserInfo(info) { + userInfo.value = info + uni.setStorageSync('userInfo', info) + } + + // 获取用户信息 + function getUserInfo() { + if (!userInfo.value) { + userInfo.value = uni.getStorageSync('userInfo') || null + } + return userInfo.value + } + + // 退出登录 + function logout() { + token.value = '' + userInfo.value = null + uni.removeStorageSync('token') + uni.removeStorageSync('userInfo') + } + + return { + token, + userInfo, + setToken, + getToken, + setUserInfo, + getUserInfo, + logout + } +}) + diff --git a/src/styles/common.scss b/src/styles/common.scss new file mode 100644 index 0000000..3cda76a --- /dev/null +++ b/src/styles/common.scss @@ -0,0 +1,122 @@ +/* 全局样式 */ +page { + background-color: #f5f5f5; + font-size: 14px; + color: #333; +} + +/* 通用容器 */ +.container { + padding: 16px; +} + +/* 通用卡片 */ +.card { + background: #fff; + border-radius: 8px; + padding: 16px; + margin-bottom: 16px; +} + +/* 通用按钮 */ +.btn { + padding: 12px 24px; + border-radius: 4px; + border: none; + font-size: 14px; + cursor: pointer; + + &.btn-primary { + background-color: #1890ff; + color: #fff; + } + + &.btn-success { + background-color: #52c41a; + color: #fff; + } + + &.btn-warning { + background-color: #faad14; + color: #fff; + } + + &.btn-error { + background-color: #f5222d; + color: #fff; + } +} + +/* 通用文本 */ +.text-primary { + color: #1890ff; +} + +.text-success { + color: #52c41a; +} + +.text-warning { + color: #faad14; +} + +.text-error { + color: #f5222d; +} + +.text-center { + text-align: center; +} + +.text-right { + text-align: right; +} + +/* 通用布局 */ +.flex { + display: flex; +} + +.flex-center { + display: flex; + align-items: center; + justify-content: center; +} + +.flex-between { + display: flex; + align-items: center; + justify-content: space-between; +} + +.flex-column { + display: flex; + flex-direction: column; +} + +/* 通用间距 */ +.mt-xs { margin-top: 4px; } +.mt-sm { margin-top: 8px; } +.mt-md { margin-top: 16px; } +.mt-lg { margin-top: 24px; } + +.mb-xs { margin-bottom: 4px; } +.mb-sm { margin-bottom: 8px; } +.mb-md { margin-bottom: 16px; } +.mb-lg { margin-bottom: 24px; } + +.ml-xs { margin-left: 4px; } +.ml-sm { margin-left: 8px; } +.ml-md { margin-left: 16px; } +.ml-lg { margin-left: 24px; } + +.mr-xs { margin-right: 4px; } +.mr-sm { margin-right: 8px; } +.mr-md { margin-right: 16px; } +.mr-lg { margin-right: 24px; } + +.p-xs { padding: 4px; } +.p-sm { padding: 8px; } +.p-md { padding: 16px; } +.p-lg { padding: 24px; } + diff --git a/src/styles/variables.scss b/src/styles/variables.scss new file mode 100644 index 0000000..d18ab5a --- /dev/null +++ b/src/styles/variables.scss @@ -0,0 +1,34 @@ +// 主题颜色 +$primary-color: #1890ff; +$success-color: #52c41a; +$warning-color: #faad14; +$error-color: #f5222d; +$info-color: #1890ff; + +// 文字颜色 +$text-color: #333333; +$text-color-secondary: #666666; +$text-color-disabled: #999999; + +// 背景颜色 +$bg-color: #ffffff; +$bg-color-secondary: #f5f5f5; + +// 边框颜色 +$border-color: #e8e8e8; +$border-radius: 4px; + +// 间距 +$spacing-xs: 4px; +$spacing-sm: 8px; +$spacing-md: 16px; +$spacing-lg: 24px; +$spacing-xl: 32px; + +// 字体大小 +$font-size-xs: 12px; +$font-size-sm: 14px; +$font-size-md: 16px; +$font-size-lg: 18px; +$font-size-xl: 20px; + diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 0000000..259a400 --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1,164 @@ +/** + * 工具函数集合 + */ + +/** + * 格式化日期 + * @param {Date|string|number} date 日期 + * @param {string} format 格式,默认 'YYYY-MM-DD HH:mm:ss' + * @returns {string} 格式化后的日期字符串 + */ +export function formatDate(date, format = 'YYYY-MM-DD HH:mm:ss') { + const d = new Date(date) + const year = d.getFullYear() + const month = String(d.getMonth() + 1).padStart(2, '0') + const day = String(d.getDate()).padStart(2, '0') + const hour = String(d.getHours()).padStart(2, '0') + const minute = String(d.getMinutes()).padStart(2, '0') + const second = String(d.getSeconds()).padStart(2, '0') + + return format + .replace('YYYY', year) + .replace('MM', month) + .replace('DD', day) + .replace('HH', hour) + .replace('mm', minute) + .replace('ss', second) +} + +/** + * 防抖函数 + * @param {Function} func 要防抖的函数 + * @param {number} wait 等待时间(毫秒) + * @returns {Function} 防抖后的函数 + */ +export function debounce(func, wait = 300) { + let timeout + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout) + func(...args) + } + clearTimeout(timeout) + timeout = setTimeout(later, wait) + } +} + +/** + * 节流函数 + * @param {Function} func 要节流的函数 + * @param {number} limit 时间限制(毫秒) + * @returns {Function} 节流后的函数 + */ +export function throttle(func, limit = 300) { + let inThrottle + return function executedFunction(...args) { + if (!inThrottle) { + func(...args) + inThrottle = true + setTimeout(() => (inThrottle = false), limit) + } + } +} + +/** + * 深拷贝 + * @param {any} obj 要拷贝的对象 + * @returns {any} 拷贝后的对象 + */ +export function deepClone(obj) { + if (obj === null || typeof obj !== 'object') return obj + if (obj instanceof Date) return new Date(obj.getTime()) + if (obj instanceof Array) return obj.map(item => deepClone(item)) + if (typeof obj === 'object') { + const clonedObj = {} + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + clonedObj[key] = deepClone(obj[key]) + } + } + return clonedObj + } +} + +/** + * 生成唯一ID + * @returns {string} 唯一ID + */ +export function generateId() { + return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}` +} + +/** + * 存储数据到本地 + * @param {string} key 键名 + * @param {any} value 值 + */ +export function setStorage(key, value) { + try { + uni.setStorageSync(key, value) + } catch (e) { + console.error('存储数据失败:', e) + } +} + +/** + * 从本地获取数据 + * @param {string} key 键名 + * @param {any} defaultValue 默认值 + * @returns {any} 存储的值 + */ +export function getStorage(key, defaultValue = null) { + try { + const value = uni.getStorageSync(key) + return value !== '' ? value : defaultValue + } catch (e) { + console.error('获取数据失败:', e) + return defaultValue + } +} + +/** + * 删除本地存储 + * @param {string} key 键名 + */ +export function removeStorage(key) { + try { + uni.removeStorageSync(key) + } catch (e) { + console.error('删除数据失败:', e) + } +} + +/** + * 显示提示消息 + * @param {string} title 提示内容 + * @param {string} icon 图标类型 + * @param {number} duration 显示时长 + */ +export function showToast(title, icon = 'none', duration = 2000) { + uni.showToast({ + title, + icon, + duration + }) +} + +/** + * 显示加载提示 + * @param {string} title 提示内容 + */ +export function showLoading(title = '加载中...') { + uni.showLoading({ + title, + mask: true + }) +} + +/** + * 隐藏加载提示 + */ +export function hideLoading() { + uni.hideLoading() +} + diff --git a/src/utils/request.js b/src/utils/request.js new file mode 100644 index 0000000..7aa8dee --- /dev/null +++ b/src/utils/request.js @@ -0,0 +1,205 @@ +import axios from 'axios' +import { useUserStore } from '@/stores/user' + +// 创建axios实例 +const service = axios.create({ + baseURL: import.meta.env.VITE_APP_BASE_API || (process.env.NODE_ENV === 'development' ? '/api' : 'https://your-api-domain.com'), + timeout: 10000, + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + } +}) + +// 请求拦截器 +service.interceptors.request.use( + config => { + // 在发送请求之前做些什么 + const userStore = useUserStore() + + // 添加token + if (userStore.token) { + config.headers.Authorization = `Bearer ${userStore.token}` + } + + // 显示loading(可根据需要自定义) + if (config.showLoading !== false) { + uni.showLoading({ + title: '加载中...', + mask: true + }) + } + + return config + }, + error => { + // 对请求错误做些什么 + uni.hideLoading() + console.error('请求错误:', error) + return Promise.reject(error) + } +) + +// 响应拦截器 +service.interceptors.response.use( + response => { + // 隐藏loading + uni.hideLoading() + + const res = response.data + + // 如果返回的状态码为200,说明接口请求成功,可以正常拿到数据 + // 否则的话抛出错误 + if (res.code !== undefined && res.code !== 200) { + // 根据不同的错误码,做相应的处理 + handleError(res.code, res.message || '请求失败') + + // 返回一个reject的Promise,让错误信息能够被catch到 + return Promise.reject(new Error(res.message || '请求失败')) + } else { + // 返回成功的数据 + return res + } + }, + error => { + // 隐藏loading + uni.hideLoading() + + // 对响应错误做点什么 + console.error('响应错误:', error) + + if (error.response) { + // 服务器返回了错误状态码 + const { status, data } = error.response + + switch (status) { + case 401: + // 未授权,清除token并跳转到登录页 + handleError(401, '未授权,请重新登录') + try { + const userStore = useUserStore() + userStore.logout() + // 注意:如果登录页面不存在,请注释掉下面的跳转 + // uni.reLaunch({ + // url: '/pages/login/login' + // }) + } catch (e) { + console.error('退出登录失败:', e) + } + break + case 403: + handleError(403, '拒绝访问') + break + case 404: + handleError(404, '请求错误,未找到该资源') + break + case 500: + handleError(500, '服务器错误') + break + default: + handleError(status, data?.message || `连接错误${status}`) + } + } else if (error.request) { + // 请求已经发出,但没有收到响应 + handleError(-1, '网络连接失败,请检查网络') + } else { + // 发送请求时出了点问题 + handleError(-1, error.message || '请求失败') + } + + return Promise.reject(error) + } +) + +// 错误处理函数 +function handleError(code, message) { + // 可以根据不同的错误码做不同的处理 + uni.showToast({ + title: message, + icon: 'none', + duration: 2000 + }) +} + +// 封装请求方法 +const request = { + /** + * GET请求 + * @param {string} url 请求地址 + * @param {object} params 请求参数 + * @param {object} config 请求配置 + */ + get(url, params = {}, config = {}) { + return service({ + method: 'get', + url, + params, + ...config + }) + }, + + /** + * POST请求 + * @param {string} url 请求地址 + * @param {object} data 请求数据 + * @param {object} config 请求配置 + */ + post(url, data = {}, config = {}) { + return service({ + method: 'post', + url, + data, + ...config + }) + }, + + /** + * PUT请求 + * @param {string} url 请求地址 + * @param {object} data 请求数据 + * @param {object} config 请求配置 + */ + put(url, data = {}, config = {}) { + return service({ + method: 'put', + url, + data, + ...config + }) + }, + + /** + * DELETE请求 + * @param {string} url 请求地址 + * @param {object} params 请求参数 + * @param {object} config 请求配置 + */ + delete(url, params = {}, config = {}) { + return service({ + method: 'delete', + url, + params, + ...config + }) + }, + + /** + * 上传文件 + * @param {string} url 请求地址 + * @param {FormData} formData 表单数据 + * @param {object} config 请求配置 + */ + upload(url, formData, config = {}) { + return service({ + method: 'post', + url, + data: formData, + headers: { + 'Content-Type': 'multipart/form-data' + }, + ...config + }) + } +} + +export default request + diff --git a/static/.gitkeep b/static/.gitkeep new file mode 100644 index 0000000..cff4153 --- /dev/null +++ b/static/.gitkeep @@ -0,0 +1,3 @@ +# 静态资源目录 +# 请在此目录放置图片、字体等静态资源 + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..b9477e5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} + diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..e8c30d3 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.js"] +} + diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..4542d7e --- /dev/null +++ b/vite.config.js @@ -0,0 +1,36 @@ +import { defineConfig } from 'vite' +import uni from '@dcloudio/vite-plugin-uni' +import vue from '@vitejs/plugin-vue' +import path from 'path' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + uni() + ], + resolve: { + alias: { + '@': path.resolve(__dirname, 'src') + } + }, + css: { + preprocessorOptions: { + scss: { + additionalData: `@import "@/styles/variables.scss";` + } + } + }, + server: { + port: 3000, + open: true, + proxy: { + '/api': { + target: 'http://localhost:8080', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, '') + } + } + } +}) +