init project
This commit is contained in:
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
@@ -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
|
||||||
|
|
||||||
21
App.vue
Normal file
21
App.vue
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<script setup>
|
||||||
|
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
|
||||||
|
|
||||||
|
onLaunch(() => {
|
||||||
|
console.log('App Launch')
|
||||||
|
})
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
console.log('App Show')
|
||||||
|
})
|
||||||
|
|
||||||
|
onHide(() => {
|
||||||
|
console.log('App Hide')
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
/*每个页面公共css */
|
||||||
|
@import '@/styles/common.scss';
|
||||||
|
</style>
|
||||||
|
|
||||||
136
README.md
Normal file
136
README.md
Normal file
@@ -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
|
||||||
|
|
||||||
11
env.example
Normal file
11
env.example
Normal file
@@ -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
|
||||||
|
|
||||||
17
index.html
Normal file
17
index.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||||
|
<title>nl-uniapp</title>
|
||||||
|
<script>
|
||||||
|
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
|
||||||
|
document.write('<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0' + (coverSupport ? ',viewport-fit=cover' : '') + '" />')
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
18
main.js
Normal file
18
main.js
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
116
manifest.json
Normal file
116
manifest.json
Normal file
@@ -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": [
|
||||||
|
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||||
|
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||||
|
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
66
package.json
Normal file
66
package.json
Normal file
@@ -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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
37
pages.json
Normal file
37
pages.json
Normal file
@@ -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": "首页"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
291
pages/index/index.vue
Normal file
291
pages/index/index.vue
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
<template>
|
||||||
|
<view class="container">
|
||||||
|
<!-- H5端使用Ant Design Vue组件 -->
|
||||||
|
<!-- #ifdef H5 -->
|
||||||
|
<a-card title="欢迎使用 nl-uniapp" :bordered="false">
|
||||||
|
<a-space direction="vertical" :size="16" style="width: 100%">
|
||||||
|
<a-typography-title :level="3">项目信息</a-typography-title>
|
||||||
|
|
||||||
|
<a-descriptions :column="1" bordered>
|
||||||
|
<a-descriptions-item label="框架">Vue 3</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="构建工具">Vite</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="UI组件库">Ant Design Vue</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="状态管理">Pinia</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="HTTP请求">Axios</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
|
||||||
|
<a-divider />
|
||||||
|
|
||||||
|
<a-space direction="vertical" :size="12" style="width: 100%">
|
||||||
|
<a-typography-title :level="4">功能演示</a-typography-title>
|
||||||
|
|
||||||
|
<a-button type="primary" block @click="handleGetRequest" :loading="loading">
|
||||||
|
测试GET请求
|
||||||
|
</a-button>
|
||||||
|
|
||||||
|
<a-button type="primary" block @click="handlePostRequest" :loading="loading">
|
||||||
|
测试POST请求
|
||||||
|
</a-button>
|
||||||
|
|
||||||
|
<a-button type="default" block @click="handleShowMessage">
|
||||||
|
显示消息提示
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
|
||||||
|
<a-divider />
|
||||||
|
|
||||||
|
<a-alert
|
||||||
|
message="提示"
|
||||||
|
description="这是一个基于 Vue3 + Vite + Ant Design Vue 的 uniapp 项目模板"
|
||||||
|
type="info"
|
||||||
|
show-icon
|
||||||
|
/>
|
||||||
|
</a-space>
|
||||||
|
</a-card>
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
|
<!-- 小程序端使用uni-app原生组件 -->
|
||||||
|
<!-- #ifndef H5 -->
|
||||||
|
<view class="card">
|
||||||
|
<view class="title">欢迎使用 nl-uniapp</view>
|
||||||
|
|
||||||
|
<view class="info-section">
|
||||||
|
<view class="info-title">项目信息</view>
|
||||||
|
<view class="info-item">
|
||||||
|
<text class="label">框架:</text>
|
||||||
|
<text class="value">Vue 3</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-item">
|
||||||
|
<text class="label">构建工具:</text>
|
||||||
|
<text class="value">Vite</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-item">
|
||||||
|
<text class="label">UI组件库:</text>
|
||||||
|
<text class="value">Ant Design Vue</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-item">
|
||||||
|
<text class="label">状态管理:</text>
|
||||||
|
<text class="value">Pinia</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-item">
|
||||||
|
<text class="label">HTTP请求:</text>
|
||||||
|
<text class="value">Axios</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="divider"></view>
|
||||||
|
|
||||||
|
<view class="demo-section">
|
||||||
|
<view class="demo-title">功能演示</view>
|
||||||
|
<button class="btn btn-primary" @click="handleGetRequest" :loading="loading">
|
||||||
|
测试GET请求
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-primary" @click="handlePostRequest" :loading="loading">
|
||||||
|
测试POST请求
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-default" @click="handleShowMessage">
|
||||||
|
显示消息提示
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="divider"></view>
|
||||||
|
|
||||||
|
<view class="alert">
|
||||||
|
<text class="alert-title">提示</text>
|
||||||
|
<text class="alert-desc">这是一个基于 Vue3 + Vite + Ant Design Vue 的 uniapp 项目模板</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { api } from '@/api'
|
||||||
|
// #ifdef H5
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// GET请求示例
|
||||||
|
const handleGetRequest = async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
const res = await api.getList({ page: 1, pageSize: 10 })
|
||||||
|
console.log('GET请求成功:', res)
|
||||||
|
// #ifdef H5
|
||||||
|
message.success('GET请求成功,请查看控制台')
|
||||||
|
// #endif
|
||||||
|
// #ifndef H5
|
||||||
|
uni.showToast({
|
||||||
|
title: 'GET请求成功',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
} catch (error) {
|
||||||
|
console.error('GET请求失败:', error)
|
||||||
|
// #ifdef H5
|
||||||
|
message.error('GET请求失败')
|
||||||
|
// #endif
|
||||||
|
// #ifndef H5
|
||||||
|
uni.showToast({
|
||||||
|
title: 'GET请求失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST请求示例
|
||||||
|
const handlePostRequest = async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
const res = await api.create({ name: '测试数据', value: '123' })
|
||||||
|
console.log('POST请求成功:', res)
|
||||||
|
// #ifdef H5
|
||||||
|
message.success('POST请求成功,请查看控制台')
|
||||||
|
// #endif
|
||||||
|
// #ifndef H5
|
||||||
|
uni.showToast({
|
||||||
|
title: 'POST请求成功',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
} catch (error) {
|
||||||
|
console.error('POST请求失败:', error)
|
||||||
|
// #ifdef H5
|
||||||
|
message.error('POST请求失败')
|
||||||
|
// #endif
|
||||||
|
// #ifndef H5
|
||||||
|
uni.showToast({
|
||||||
|
title: 'POST请求失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 消息提示示例
|
||||||
|
const handleShowMessage = () => {
|
||||||
|
// #ifdef H5
|
||||||
|
message.info('这是一个消息提示')
|
||||||
|
// #endif
|
||||||
|
// #ifndef H5
|
||||||
|
uni.showToast({
|
||||||
|
title: '这是一个消息提示',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
padding: 16px;
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 小程序端样式
|
||||||
|
.card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-section {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: #666;
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
color: #333;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
height: 1px;
|
||||||
|
background-color: #e8e8e8;
|
||||||
|
margin: 24px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-section {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
&.btn-primary {
|
||||||
|
background-color: #1890ff;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.btn-default {
|
||||||
|
background-color: #fff;
|
||||||
|
color: #333;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
padding: 12px;
|
||||||
|
background-color: #e6f7ff;
|
||||||
|
border: 1px solid #91d5ff;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
.alert-title {
|
||||||
|
display: block;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-desc {
|
||||||
|
display: block;
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
77
src/api/index.js
Normal file
77
src/api/index.js
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
25
src/components/README.md
Normal file
25
src/components/README.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# 组件目录
|
||||||
|
|
||||||
|
此目录用于存放自定义组件。
|
||||||
|
|
||||||
|
## 使用方式
|
||||||
|
|
||||||
|
在页面中引入组件:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<custom-component />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import CustomComponent from '@/components/CustomComponent.vue'
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 组件命名规范
|
||||||
|
|
||||||
|
- 组件文件名使用 PascalCase(大驼峰命名)
|
||||||
|
- 组件名与文件名保持一致
|
||||||
|
|
||||||
55
src/stores/user.js
Normal file
55
src/stores/user.js
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
122
src/styles/common.scss
Normal file
122
src/styles/common.scss
Normal file
@@ -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; }
|
||||||
|
|
||||||
34
src/styles/variables.scss
Normal file
34
src/styles/variables.scss
Normal file
@@ -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;
|
||||||
|
|
||||||
164
src/utils/index.js
Normal file
164
src/utils/index.js
Normal file
@@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
205
src/utils/request.js
Normal file
205
src/utils/request.js
Normal file
@@ -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
|
||||||
|
|
||||||
3
static/.gitkeep
Normal file
3
static/.gitkeep
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# 静态资源目录
|
||||||
|
# 请在此目录放置图片、字体等静态资源
|
||||||
|
|
||||||
26
tsconfig.json
Normal file
26
tsconfig.json
Normal file
@@ -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" }]
|
||||||
|
}
|
||||||
|
|
||||||
11
tsconfig.node.json
Normal file
11
tsconfig.node.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.js"]
|
||||||
|
}
|
||||||
|
|
||||||
36
vite.config.js
Normal file
36
vite.config.js
Normal file
@@ -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/, '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
Reference in New Issue
Block a user