chore: 初始化项目 — uni-app + Vue3 + TypeScript 脚手架
This commit is contained in:
+22
@@ -0,0 +1,22 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
*.local
|
||||
.superpowers/
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -0,0 +1,19 @@
|
||||
// app.js
|
||||
App({
|
||||
onLaunch() {
|
||||
// 展示本地存储能力
|
||||
const logs = wx.getStorageSync('logs') || []
|
||||
logs.unshift(Date.now())
|
||||
wx.setStorageSync('logs', logs)
|
||||
|
||||
// 登录
|
||||
wx.login({
|
||||
success: res => {
|
||||
// 发送 res.code 到后台换取 openId, sessionKey, unionId
|
||||
}
|
||||
})
|
||||
},
|
||||
globalData: {
|
||||
userInfo: null
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"pages": [
|
||||
"pages/index/index",
|
||||
"pages/logs/logs"
|
||||
],
|
||||
"window": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "Weixin",
|
||||
"navigationBarBackgroundColor": "#ffffff"
|
||||
},
|
||||
"style": "v2",
|
||||
"componentFramework": "glass-easel",
|
||||
"sitemapLocation": "sitemap.json",
|
||||
"lazyCodeLoading": "requiredComponents"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/**app.wxss**/
|
||||
.container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 200rpx 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,337 @@
|
||||
# 湖南12301文旅综合平台 — 第一迭代设计文档
|
||||
|
||||
**文档版本**:v1.0
|
||||
**创建日期**:2026-06-13
|
||||
**实现方案**:方案 A — 由底向上
|
||||
|
||||
---
|
||||
|
||||
## 一、迭代范围
|
||||
|
||||
第一迭代聚焦 **基础层搭建 + 用户端核心流程**,具体包含:
|
||||
|
||||
| 阶段 | 内容 |
|
||||
|------|------|
|
||||
| **Phase 1:基础层** | 分包目录、路由配置、API 层改造、Store 设计、环境配置、共享组件 |
|
||||
| **Phase 2:用户端核心页面** | 首页、年卡列表/详情、确认购买、激活年卡、入园码 |
|
||||
|
||||
**不包含在第一迭代**:
|
||||
- 管理端页面(卡管理、景区管理)→ 第二迭代
|
||||
- H5 版本登录(OAuth / 手机号)→ 后续扩展
|
||||
- 权益查看、景区预约、预约记录、使用记录 → 后续迭代
|
||||
- 真实微信支付接入 → 第一版用模拟支付
|
||||
|
||||
---
|
||||
|
||||
## 二、项目架构
|
||||
|
||||
### 2.1 分包目录结构
|
||||
|
||||
```
|
||||
zhy-test/
|
||||
├── src/
|
||||
│ ├── pages/ # 主包(共享入口)
|
||||
│ │ ├── index/index.vue # 入口页(改造:默认跳转用户端首页)
|
||||
│ │ └── login/login.vue # 登录页(改造为微信授权登录)
|
||||
│ │
|
||||
│ ├── packages/
|
||||
│ │ ├── user/ # 用户端分包
|
||||
│ │ │ └── pages/
|
||||
│ │ │ ├── index/index.vue # 用户首页(宫格式)
|
||||
│ │ │ ├── card/list.vue # 年卡列表
|
||||
│ │ │ ├── card/detail.vue # 年卡详情
|
||||
│ │ │ ├── card/buy.vue # 确认购买(模拟支付)
|
||||
│ │ │ ├── card/activate.vue # 激活年卡
|
||||
│ │ │ ├── card/qrcode.vue # 入园码(卡片式)
|
||||
│ │ │ ├── card/bind.vue # 绑定年卡
|
||||
│ │ │ └── profile/index.vue # 个人中心
|
||||
│ │ │
|
||||
│ │ └── admin/ # 管理端分包(第二迭代,暂不实现)
|
||||
│ │
|
||||
│ ├── api/ # 接口层
|
||||
│ │ ├── request.ts # HTTP 封装(改造现有)
|
||||
│ │ ├── user.ts # 用户/登录接口(改造)
|
||||
│ │ ├── card.ts # 年卡接口(新增)
|
||||
│ │ └── order.ts # 订单/支付接口(新增)
|
||||
│ │
|
||||
│ ├── store/ # Pinia Store
|
||||
│ │ ├── user.ts # 用户状态(改造现有)
|
||||
│ │ └── card.ts # 年卡状态(新增)
|
||||
│ │
|
||||
│ ├── components/ # 共享组件(新增)
|
||||
│ │ ├── card-item.vue # 年卡卡片组件
|
||||
│ │ └── empty-state.vue # 空状态组件
|
||||
│ │
|
||||
│ ├── config/ # 环境配置(新增)
|
||||
│ │ └── index.ts # 根据环境变量切换 baseURL
|
||||
│ │
|
||||
│ └── utils/ # 工具函数(扩展现有)
|
||||
│ └── index.ts
|
||||
```
|
||||
|
||||
### 2.2 pages.json 路由配置
|
||||
|
||||
主包保留 `pages/index`(入口跳转)和 `pages/login`,用户端页面全部放入 `packages/user` 分包。
|
||||
|
||||
```json
|
||||
{
|
||||
"pages": [
|
||||
{ "path": "pages/index/index", "style": { "navigationBarTitleText": "湖南12301" } },
|
||||
{ "path": "pages/login/login", "style": { "navigationBarTitleText": "登录" } }
|
||||
],
|
||||
"subPackages": [
|
||||
{
|
||||
"root": "packages/user",
|
||||
"pages": [
|
||||
{ "path": "pages/index/index", "style": { "navigationBarTitleText": "文旅专区" } },
|
||||
{ "path": "pages/card/list", "style": { "navigationBarTitleText": "年卡列表" } },
|
||||
{ "path": "pages/card/detail", "style": { "navigationBarTitleText": "年卡详情" } },
|
||||
{ "path": "pages/card/buy", "style": { "navigationBarTitleText": "确认购买" } },
|
||||
{ "path": "pages/card/activate", "style": { "navigationBarTitleText": "激活年卡" } },
|
||||
{ "path": "pages/card/qrcode", "style": { "navigationBarTitleText": "入园码" } },
|
||||
{ "path": "pages/card/bind", "style": { "navigationBarTitleText": "绑定年卡" } },
|
||||
{ "path": "pages/profile/index", "style": { "navigationBarTitleText": "个人中心" } }
|
||||
]
|
||||
}
|
||||
],
|
||||
"tabBar": {
|
||||
"color": "#7A7E83",
|
||||
"selectedColor": "#4A90D9",
|
||||
"backgroundColor": "#ffffff",
|
||||
"list": [
|
||||
{ "pagePath": "packages/user/pages/index/index", "text": "首页" },
|
||||
{ "pagePath": "packages/user/pages/profile/index", "text": "我的" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 多环境配置
|
||||
|
||||
| 环境 | baseURL | 触发方式 | 配置文件 |
|
||||
|------|---------|----------|----------|
|
||||
| 本地开发 | `http://localhost:80` | `pnpm dev:h5` | `.env.local` |
|
||||
| 开发环境 | `https://dscp-dev.hn12301.net` | `--mode dev` | `.env.dev` |
|
||||
| 预发布 | `https://dscp-uat.hn12301.net` | `--mode uat` | `.env.uat` |
|
||||
|
||||
通过 `config/index.ts` 统一读取 `import.meta.env.VITE_API_BASE_URL`。
|
||||
|
||||
---
|
||||
|
||||
## 三、API 层设计
|
||||
|
||||
### 3.1 request.ts 改造
|
||||
|
||||
**改造要点**:
|
||||
- `baseURL` 从 `config/index.ts` 读取(替代空字符串)
|
||||
- Token 管理保持现有 `uni.getStorageSync` / `uni.setStorageSync` 模式
|
||||
- 请求拦截器自动注入 `Authorization: Bearer <token>`
|
||||
- 响应拦截器:`code === 200` 成功,`401` 清除 token 并跳转登录页
|
||||
- 保留 `get()` / `post()` / `put()` / `del()` 便捷方法
|
||||
|
||||
### 3.2 接口模块定义
|
||||
|
||||
```typescript
|
||||
// api/user.ts(改造)
|
||||
wxLogin(code: string) // POST /manage-api/auth/wx-login → { accessToken, refreshToken, userId }
|
||||
getUserInfo() // GET /manage-api/user/info → UserInfo
|
||||
|
||||
// api/card.ts(新增)
|
||||
getCardList(params?) // GET /manage-api/card/list → CardInfo[]
|
||||
getCardDetail(id: string) // GET /manage-api/card/detail?id=xxx → CardDetail
|
||||
activateCard(cardId, data) // POST /manage-api/card/activate → boolean
|
||||
bindCard(cardNo: string) // POST /manage-api/card/bind → boolean
|
||||
getMyCards() // GET /manage-api/card/my-list → MyCard[]
|
||||
getEntryQrCode(cardId: string) // GET /manage-api/card/qrcode?cardId=xxx → { qrCode, expireTime }
|
||||
|
||||
// api/order.ts(新增)
|
||||
createOrder(data) // POST /manage-api/order/create → OrderInfo
|
||||
mockPay(orderId: string) // POST /manage-api/order/mock-pay → { success: true }(模拟支付)
|
||||
getOrderList(params?) // GET /manage-api/order/list → OrderInfo[]
|
||||
getOrderDetail(id: string) // GET /manage-api/order/detail?id=xxx → OrderDetail
|
||||
```
|
||||
|
||||
> **注意**:接口路径和参数格式需在实现时与后端实际接口对齐。以上为预定义,实现阶段根据后端实际接口调整。
|
||||
|
||||
---
|
||||
|
||||
## 四、Store 设计
|
||||
|
||||
### 4.1 user store(改造现有)
|
||||
|
||||
```
|
||||
State:
|
||||
token: string // 访问令牌
|
||||
refreshToken: string // 刷新令牌
|
||||
userInfo: UserInfo | null // 用户信息
|
||||
|
||||
Computed:
|
||||
isLogin: boolean // !!token
|
||||
|
||||
Actions:
|
||||
wxLogin(): Promise // 调用 wx.login() 获取 code,发送后端换取 token
|
||||
logout(): void // 清除状态和 storage
|
||||
fetchUserInfo(): Promise // 获取当前用户信息
|
||||
```
|
||||
|
||||
### 4.2 card store(新增)
|
||||
|
||||
```
|
||||
State:
|
||||
cardList: CardInfo[] // 年卡列表
|
||||
currentCard: CardDetail // 当前查看的年卡详情
|
||||
myCards: MyCard[] // 我的年卡列表
|
||||
|
||||
Actions:
|
||||
fetchCardList(params?): Promise // 获取年卡列表
|
||||
fetchCardDetail(id): Promise // 获取年卡详情
|
||||
fetchMyCards(): Promise // 获取我的年卡
|
||||
activateCard(cardId, data): Promise // 激活年卡
|
||||
bindCard(cardNo): Promise // 绑定年卡
|
||||
getEntryQrCode(cardId): Promise // 获取入园码
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、微信登录设计
|
||||
|
||||
### 5.1 登录流程
|
||||
|
||||
```
|
||||
用户打开小程序
|
||||
→ 检查本地是否有 token
|
||||
→ 有 token → 直接进入用户端首页
|
||||
→ 无 token → 跳转登录页
|
||||
→ 用户点击"微信登录"按钮
|
||||
→ uni.login() 获取 code
|
||||
→ POST /manage-api/auth/wx-login { code }
|
||||
→ 后端返回 { accessToken, refreshToken, userId }
|
||||
→ 存入 Store + Storage
|
||||
→ 跳转用户端首页
|
||||
```
|
||||
|
||||
### 5.2 平台策略
|
||||
|
||||
| 平台 | 第一迭代 | 后续扩展 |
|
||||
|------|----------|----------|
|
||||
| 微信小程序 | `wx.login()` 授权登录 | — |
|
||||
| H5 | 不支持(提示请在微信小程序中使用) | 微信 OAuth / 手机号登录 |
|
||||
|
||||
### 5.3 降级方案
|
||||
|
||||
如果后端微信登录接口尚未就绪,可临时使用现有账号密码登录接口,在 `user store` 的 `wxLogin()` 方法中预留切换点。
|
||||
|
||||
---
|
||||
|
||||
## 六、用户端页面设计
|
||||
|
||||
### 6.1 首页(宫格式)
|
||||
|
||||
**路由**:`/packages/user/pages/index/index`
|
||||
|
||||
**布局**:
|
||||
- 顶部:标题栏 "湖南12301文旅专区"
|
||||
- 中部:2×2 宫格功能入口(购买年卡 / 激活绑定 / 景区预约 / 我的记录)。其中"景区预约"和"我的记录"在第一迭代中点击后弹出"即将上线"提示
|
||||
- 下部:推荐年卡列表(卡片式展示)
|
||||
- 底部:TabBar(首页 / 我的)— 权益 Tab 在后续迭代中补充
|
||||
|
||||
### 6.2 年卡列表
|
||||
|
||||
**路由**:`/packages/user/pages/card/list`
|
||||
|
||||
**布局**:
|
||||
- 顶部搜索栏
|
||||
- 分类筛选标签
|
||||
- 年卡卡片列表(使用 card-item 组件)
|
||||
- 每个卡片:封面图 + 名称 + 价格 + 简要权益
|
||||
|
||||
### 6.3 年卡详情
|
||||
|
||||
**路由**:`/packages/user/pages/card/detail`
|
||||
|
||||
**布局**:
|
||||
- 顶部封面大图
|
||||
- 年卡名称 + 价格(原价划线)
|
||||
- 权益说明列表
|
||||
- 权益景区横滑卡片
|
||||
- 底部固定按钮:立即购买
|
||||
|
||||
### 6.4 确认购买(模拟支付)
|
||||
|
||||
**路由**:`/packages/user/pages/card/buy`
|
||||
|
||||
**布局**:
|
||||
- 订单信息卡片(卡种名称 / 价格 / 有效期)
|
||||
- 支付方式选择(第一版仅"模拟支付")
|
||||
- 底部按钮:确认支付
|
||||
- 支付成功后跳转到激活页面
|
||||
|
||||
### 6.5 激活年卡
|
||||
|
||||
**路由**:`/packages/user/pages/card/activate`
|
||||
|
||||
**布局**:
|
||||
- 年卡信息展示
|
||||
- 实名信息表单(姓名 / 身份证号 / 手机号)
|
||||
- 底部按钮:立即激活
|
||||
- 激活成功后跳转到入园码页面
|
||||
|
||||
### 6.6 入园码(卡片式)
|
||||
|
||||
**路由**:`/packages/user/pages/card/qrcode`
|
||||
|
||||
**布局**:
|
||||
- 年卡信息卡片(渐变色背景,显示姓名和有效期)
|
||||
- 二维码区域(白色卡片,居中显示)
|
||||
- 提示文字:"扫码入园 · 每60秒自动刷新"
|
||||
- 底部操作按钮:预约景区 / 刷新二维码
|
||||
|
||||
### 6.7 个人中心
|
||||
|
||||
**路由**:`/packages/user/pages/profile/index`(TabBar 页面)
|
||||
|
||||
**布局**:
|
||||
- 用户头像 + 昵称卡片
|
||||
- 我的年卡入口
|
||||
- 订单管理入口
|
||||
- 使用记录入口
|
||||
- 退出登录按钮
|
||||
|
||||
---
|
||||
|
||||
## 七、页面导航流程
|
||||
|
||||
```
|
||||
小程序启动
|
||||
→ pages/index/index(入口页,自动跳转)
|
||||
→ packages/user/pages/index/index(用户首页)
|
||||
|
||||
核心购买流程:
|
||||
用户首页 → 年卡列表 → 年卡详情 → 确认购买 → 模拟支付 → 激活年卡 → 入园码
|
||||
|
||||
TabBar 常驻页面:
|
||||
首页(packages/user/pages/index/index)
|
||||
我的(packages/user/pages/profile/index)
|
||||
|
||||
非 Tab 页面(navigateTo):
|
||||
年卡列表、年卡详情、确认购买、激活年卡、入园码、绑定年卡
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、共享组件
|
||||
|
||||
| 组件 | 路径 | 用途 |
|
||||
|------|------|------|
|
||||
| `card-item.vue` | `components/card-item.vue` | 年卡卡片展示(封面/名称/价格/权益摘要) |
|
||||
| `empty-state.vue` | `components/empty-state.vue` | 空状态提示(图标 + 文案 + 操作按钮) |
|
||||
|
||||
---
|
||||
|
||||
## 九、关键约束
|
||||
|
||||
1. **接口对齐**:API 路径和参数为预定义,实现时需与后端实际接口对齐
|
||||
2. **模拟支付**:第一版不接入真实微信支付,使用模拟支付接口跑通流程
|
||||
3. **UI 优先级**:功能先行,使用 uni-ui 默认样式,后续迭代美化
|
||||
4. **仅小程序**:第一迭代仅支持微信小程序平台,H5 登录后缀扩展
|
||||
5. **管理端预留**:`packages/admin/` 目录结构预留,不在第一迭代实现
|
||||
@@ -0,0 +1,150 @@
|
||||
# 湖南12301文旅综合平台 - 规格说明书
|
||||
|
||||
**文档版本**:v1.0
|
||||
**最后更新**:2026-06-13
|
||||
|
||||
---
|
||||
|
||||
## 一、项目基本信息
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| **项目名称** | 湖南12301文旅综合平台 |
|
||||
| **技术栈** | uni-app + Vue 3 + Composition API + TypeScript + Pinia + uni-ui |
|
||||
| **构建工具** | Vite |
|
||||
| **环境配置** | local / dev / uat 三套 |
|
||||
| **接口架构** | 后端统一 API 层封装所有第三方 |
|
||||
| **目标平台** | 微信小程序 + H5 + App(Android/iOS) |
|
||||
|
||||
---
|
||||
|
||||
## 二、双端架构
|
||||
|
||||
### 2.1 目录结构
|
||||
|
||||
```
|
||||
湖南12301/
|
||||
├── src/
|
||||
│ ├── pages/ # 主包(共享页面)
|
||||
│ │ ├── index/ # 首页(双端入口选择)
|
||||
│ │ └── login/ # 登录页
|
||||
│ ├── packages/
|
||||
│ │ ├── admin/ # 【综合管理端】商家/管理员
|
||||
│ │ │ └── pages/
|
||||
│ │ │ ├── index/ # 管理端首页
|
||||
│ │ │ ├── card/ # 卡管理
|
||||
│ │ │ └── scenic/ # 景区管理
|
||||
│ │ └── user/ # 【文旅专区端】C端用户
|
||||
│ │ └── pages/
|
||||
│ │ ├── index/ # 用户端首页
|
||||
│ │ ├── card/ # 年卡购买/激活/绑定/入园码
|
||||
│ │ ├── rights/ # 权益查看(免票景区/特惠商品)
|
||||
│ │ ├── scenic/ # 景区预约
|
||||
│ │ ├── booking/ # 预约记录
|
||||
│ │ ├── usage/ # 使用记录
|
||||
│ │ └── profile/ # 个人中心(我的年卡/订单)
|
||||
│ ├── components/ # 共享组件
|
||||
│ ├── api/ # 共享接口层
|
||||
│ ├── store/ # Pinia 共享状态
|
||||
│ └── utils/ # 工具函数
|
||||
```
|
||||
|
||||
### 2.2 发布模式
|
||||
|
||||
| 模式 | 编译命令 | 说明 |
|
||||
|---|---|---|
|
||||
| 聚合版 | `npm run build:all` | 双端合一,首页选择入口 |
|
||||
| 管理端独立版 | `npm run build:admin` | 商家/管理员独立小程序 |
|
||||
| 用户端独立版 | `npm run build:user` | C端用户独立小程序 |
|
||||
|
||||
---
|
||||
|
||||
## 三、综合管理端功能(商家/管理员)
|
||||
|
||||
### 3.1 TabBar 设计
|
||||
|
||||
| Tab | 路由 | 说明 |
|
||||
|---|---|---|
|
||||
| 🏠 | 首页 | 数据概览 |
|
||||
| 🎫 | 卡管理 | 年卡列表/编辑 |
|
||||
| 📍 | 景区管理 | 景区列表/编辑 |
|
||||
| 👤 | 我的 | 个人设置 |
|
||||
|
||||
### 3.2 功能模块
|
||||
|
||||
| 模块 | 路由 | 功能 |
|
||||
|---|---|---|
|
||||
| **首页** | `/packages/admin/pages/index/index` | 数据概览(待扩展) |
|
||||
| **卡管理** | `/packages/admin/pages/card/list` | 年卡列表、创建/编辑年卡 |
|
||||
| **景区管理** | `/packages/admin/pages/scenic/list` | 景区列表、创建/编辑景区 |
|
||||
|
||||
---
|
||||
|
||||
## 四、文旅专区端功能(C端用户)
|
||||
|
||||
### 4.1 TabBar 设计
|
||||
|
||||
| Tab | 路由 | 说明 |
|
||||
|---|---|---|
|
||||
| 🏠 | 首页 | 年卡展示、推荐景区、Banner |
|
||||
| 🎫 | 权益 | 免票景区、特惠商品 |
|
||||
| 👤 | 我的 | 我的年卡、订单、预约、积分等 |
|
||||
|
||||
### 4.2 功能模块
|
||||
|
||||
| 序号 | 模块 | 路由 | 功能 |
|
||||
|---|---|---|---|
|
||||
| 1 | **年卡购买** | `/packages/user/pages/card/buy` | 卡种列表 → 选择卡种 → 确认订单 → 微信支付 |
|
||||
| 2 | **年卡激活** | `/packages/user/pages/card/activate` | 激活已购买的年卡 |
|
||||
| 3 | **年卡绑定** | `/packages/user/pages/card/bind` | 输入卡号绑定已有年卡 |
|
||||
| 4 | **入园码** | `/packages/user/pages/card/qrcode` | 生成/展示入园二维码 |
|
||||
| 5 | **权益查看** | `/packages/user/pages/rights/index` | 免票景区列表、特惠商品列表(含搜索/筛选/排序) |
|
||||
| 6 | **景区预约** | `/packages/user/pages/scenic/book` | 选择景区 → 选择时间 → 确认预约 |
|
||||
| 7 | **预约记录** | `/packages/user/pages/booking/list` | 查看/取消已预约记录 |
|
||||
| 8 | **使用记录** | `/packages/user/pages/usage/list` | 已入园/已使用记录 |
|
||||
| 9 | **订单管理** | `/packages/user/pages/profile/order` | 全部/待支付/已完成订单 |
|
||||
| 10 | **我的年卡** | `/packages/user/pages/profile/my-card` | 已购年卡列表、到期时间 |
|
||||
|
||||
### 4.3 权益查看子功能
|
||||
|
||||
| 功能 | 说明 |
|
||||
|---|---|
|
||||
| **搜索** | 关键词搜索景区/商品 |
|
||||
| **分类筛选** | 按分类筛选(景区/影院/温泉/亲子乐园等) |
|
||||
| **排序** | 按距离/热度/时间排序 |
|
||||
|
||||
### 4.4 核心业务流程
|
||||
|
||||
```
|
||||
购买年卡:
|
||||
年卡展示 → 选择卡种 → 微信支付 → 支付成功 → 年卡激活 → 我的年卡
|
||||
|
||||
权益使用:
|
||||
查看权益 → 选择景区 → 景区预约 → 生成入园码 → 入园核销 → 使用记录
|
||||
|
||||
年卡绑定:
|
||||
已有年卡 → 输入卡号 → 验证 → 绑定成功 → 查看权益
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、API 接口模块
|
||||
|
||||
| 模块 | 接口描述 |
|
||||
|---|---|
|
||||
| **用户模块** | 登录、获取用户信息 |
|
||||
| **年卡模块** | 卡种列表、卡种详情、年卡激活、年卡绑定、我的年卡列表 |
|
||||
| **权益模块** | 免票景区列表、特惠商品列表、景区/商品详情 |
|
||||
| **预约模块** | 创建预约、取消预约、预约列表 |
|
||||
| **核销模块** | 生成入园码、验证入园码 |
|
||||
| **订单模块** | 创建订单、微信支付、订单列表、订单详情 |
|
||||
|
||||
---
|
||||
|
||||
## 六、环境配置
|
||||
|
||||
| 环境 | 标识 | 用途 |
|
||||
|---|---|---|
|
||||
| 本地开发 | `local` | 本机开发调试 |
|
||||
| 测试环境 | `dev` | 团队联调、测试验证 |
|
||||
| 预发布环境 | `uat` | 生产环境验证、灰度测试 |
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<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>
|
||||
<title></title>
|
||||
<!--preload-links-->
|
||||
<!--app-context-->
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"><!--app-html--></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+8810
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"name": "uni-preset-vue",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev:custom": "uni -p",
|
||||
"dev:h5": "uni",
|
||||
"dev:h5:ssr": "uni --ssr",
|
||||
"dev:mp-alipay": "uni -p mp-alipay",
|
||||
"dev:mp-baidu": "uni -p mp-baidu",
|
||||
"dev:mp-jd": "uni -p mp-jd",
|
||||
"dev:mp-kuaishou": "uni -p mp-kuaishou",
|
||||
"dev:mp-lark": "uni -p mp-lark",
|
||||
"dev:mp-qq": "uni -p mp-qq",
|
||||
"dev:mp-toutiao": "uni -p mp-toutiao",
|
||||
"dev:mp-harmony": "uni -p mp-harmony",
|
||||
"dev:mp-weixin": "uni -p mp-weixin",
|
||||
"dev:mp-xhs": "uni -p mp-xhs",
|
||||
"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:custom": "uni build -p",
|
||||
"build:h5": "uni build",
|
||||
"build:h5:ssr": "uni build --ssr",
|
||||
"build:mp-alipay": "uni build -p mp-alipay",
|
||||
"build:mp-baidu": "uni build -p mp-baidu",
|
||||
"build:mp-jd": "uni build -p mp-jd",
|
||||
"build:mp-kuaishou": "uni build -p mp-kuaishou",
|
||||
"build:mp-lark": "uni build -p mp-lark",
|
||||
"build:mp-qq": "uni build -p mp-qq",
|
||||
"build:mp-toutiao": "uni build -p mp-toutiao",
|
||||
"build:mp-harmony": "uni build -p mp-harmony",
|
||||
"build:mp-weixin": "uni build -p mp-weixin",
|
||||
"build:mp-xhs": "uni build -p mp-xhs",
|
||||
"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",
|
||||
"type-check": "vue-tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dcloudio/uni-app": "3.0.0-4080420251103001",
|
||||
"@dcloudio/uni-app-harmony": "3.0.0-4080420251103001",
|
||||
"@dcloudio/uni-app-plus": "3.0.0-4080420251103001",
|
||||
"@dcloudio/uni-components": "3.0.0-4080420251103001",
|
||||
"@dcloudio/uni-h5": "3.0.0-4080420251103001",
|
||||
"@dcloudio/uni-mp-alipay": "3.0.0-4080420251103001",
|
||||
"@dcloudio/uni-mp-baidu": "3.0.0-4080420251103001",
|
||||
"@dcloudio/uni-mp-harmony": "3.0.0-4080420251103001",
|
||||
"@dcloudio/uni-mp-jd": "3.0.0-4080420251103001",
|
||||
"@dcloudio/uni-mp-kuaishou": "3.0.0-4080420251103001",
|
||||
"@dcloudio/uni-mp-lark": "3.0.0-4080420251103001",
|
||||
"@dcloudio/uni-mp-qq": "3.0.0-4080420251103001",
|
||||
"@dcloudio/uni-mp-toutiao": "3.0.0-4080420251103001",
|
||||
"@dcloudio/uni-mp-weixin": "3.0.0-4080420251103001",
|
||||
"@dcloudio/uni-mp-xhs": "3.0.0-4080420251103001",
|
||||
"@dcloudio/uni-quickapp-webview": "3.0.0-4080420251103001",
|
||||
"@dcloudio/uni-ui": "^1.5.12",
|
||||
"pinia": "^3.0.4",
|
||||
"vue": "^3.5.38",
|
||||
"vue-i18n": "^9.1.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dcloudio/types": "^3.4.8",
|
||||
"@dcloudio/uni-automator": "3.0.0-4080420251103001",
|
||||
"@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
|
||||
"@dcloudio/uni-stacktracey": "3.0.0-4080420251103001",
|
||||
"@dcloudio/vite-plugin-uni": "3.0.0-4080420251103001",
|
||||
"@vue/runtime-core": "^3.5.38",
|
||||
"@vue/tsconfig": "^0.1.3",
|
||||
"typescript": "^4.9.4",
|
||||
"vite": "5.2.8",
|
||||
"vue-tsc": "^1.0.24"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// index.js
|
||||
const defaultAvatarUrl = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0'
|
||||
|
||||
Page({
|
||||
data: {
|
||||
motto: 'Hello World',
|
||||
userInfo: {
|
||||
avatarUrl: defaultAvatarUrl,
|
||||
nickName: '',
|
||||
},
|
||||
hasUserInfo: false,
|
||||
canIUseGetUserProfile: wx.canIUse('getUserProfile'),
|
||||
canIUseNicknameComp: wx.canIUse('input.type.nickname'),
|
||||
},
|
||||
bindViewTap() {
|
||||
wx.navigateTo({
|
||||
url: '../logs/logs'
|
||||
})
|
||||
},
|
||||
onChooseAvatar(e) {
|
||||
const { avatarUrl } = e.detail
|
||||
const { nickName } = this.data.userInfo
|
||||
this.setData({
|
||||
"userInfo.avatarUrl": avatarUrl,
|
||||
hasUserInfo: nickName && avatarUrl && avatarUrl !== defaultAvatarUrl,
|
||||
})
|
||||
},
|
||||
onInputChange(e) {
|
||||
const nickName = e.detail.value
|
||||
const { avatarUrl } = this.data.userInfo
|
||||
this.setData({
|
||||
"userInfo.nickName": nickName,
|
||||
hasUserInfo: nickName && avatarUrl && avatarUrl !== defaultAvatarUrl,
|
||||
})
|
||||
},
|
||||
getUserProfile(e) {
|
||||
// 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认,开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
|
||||
wx.getUserProfile({
|
||||
desc: '展示用户信息', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
|
||||
success: (res) => {
|
||||
console.log(res)
|
||||
this.setData({
|
||||
userInfo: res.userInfo,
|
||||
hasUserInfo: true
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<!--index.wxml-->
|
||||
<scroll-view class="scrollarea" scroll-y type="list">
|
||||
<view class="container">
|
||||
<view class="userinfo">
|
||||
<block wx:if="{{canIUseNicknameComp && !hasUserInfo}}">
|
||||
<button class="avatar-wrapper" open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar">
|
||||
<image class="avatar" src="{{userInfo.avatarUrl}}"></image>
|
||||
</button>
|
||||
<view class="nickname-wrapper">
|
||||
<text class="nickname-label">昵称</text>
|
||||
<input type="nickname" class="nickname-input" placeholder="请输入昵称" bind:change="onInputChange" />
|
||||
</view>
|
||||
</block>
|
||||
<block wx:elif="{{!hasUserInfo}}">
|
||||
<button wx:if="{{canIUseGetUserProfile}}" bindtap="getUserProfile"> 获取头像昵称 </button>
|
||||
<view wx:else> 请使用2.10.4及以上版本基础库 </view>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>
|
||||
<text class="userinfo-nickname">{{userInfo.nickName}}</text>
|
||||
</block>
|
||||
</view>
|
||||
<view class="usermotto">
|
||||
<text class="user-motto">{{motto}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
@@ -0,0 +1,62 @@
|
||||
/**index.wxss**/
|
||||
page {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.scrollarea {
|
||||
flex: 1;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.userinfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
color: #aaa;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.userinfo-avatar {
|
||||
overflow: hidden;
|
||||
width: 128rpx;
|
||||
height: 128rpx;
|
||||
margin: 20rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.usermotto {
|
||||
margin-top: 200px;
|
||||
}
|
||||
|
||||
.avatar-wrapper {
|
||||
padding: 0;
|
||||
width: 56px !important;
|
||||
border-radius: 8px;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
display: block;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
.nickname-wrapper {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
border-top: .5px solid rgba(0, 0, 0, 0.1);
|
||||
border-bottom: .5px solid rgba(0, 0, 0, 0.1);
|
||||
color: black;
|
||||
}
|
||||
|
||||
.nickname-label {
|
||||
width: 105px;
|
||||
}
|
||||
|
||||
.nickname-input {
|
||||
flex: 1;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"compileType": "miniprogram",
|
||||
"libVersion": "trial",
|
||||
"packOptions": {
|
||||
"ignore": [],
|
||||
"include": []
|
||||
},
|
||||
"setting": {
|
||||
"coverView": true,
|
||||
"es6": true,
|
||||
"postcss": true,
|
||||
"minified": true,
|
||||
"enhance": true,
|
||||
"showShadowRootInWxmlPanel": true,
|
||||
"packNpmRelationList": [],
|
||||
"babelSetting": {
|
||||
"ignore": [],
|
||||
"disablePlugins": [],
|
||||
"outputPath": ""
|
||||
},
|
||||
"uglifyFileName": false,
|
||||
"uploadWithSourceMap": true,
|
||||
"packNpmManually": false,
|
||||
"minifyWXSS": true,
|
||||
"localPlugins": false,
|
||||
"condition": false,
|
||||
"swc": false,
|
||||
"disableSWC": true,
|
||||
"compileWorklet": false,
|
||||
"disableUseStrict": false,
|
||||
"minifyWXML": true,
|
||||
"useCompilerPlugins": false
|
||||
},
|
||||
"condition": {},
|
||||
"editorSetting": {
|
||||
"tabIndent": "auto",
|
||||
"tabSize": 2
|
||||
},
|
||||
"appid": "wx636bc46a6570e9fd",
|
||||
"simulatorPluginLibVersion": {}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
|
||||
"projectname": "%E8%B4%A8%E6%83%A0%E6%B8%B8%E6%B5%8B%E8%AF%95",
|
||||
"setting": {
|
||||
"compileHotReLoad": true,
|
||||
"urlCheck": true,
|
||||
"coverView": true,
|
||||
"lazyloadPlaceholderEnable": false,
|
||||
"skylineRenderEnable": false,
|
||||
"preloadBackgroundData": false,
|
||||
"autoAudits": false,
|
||||
"showShadowRootInWxmlPanel": true,
|
||||
"showES6CompileOption": false,
|
||||
"bigPackageSizeSupport": false,
|
||||
"useApiHook": true,
|
||||
"checkInvalidKey": true,
|
||||
"useStaticServer": false,
|
||||
"useLanDebug": false,
|
||||
"ignoreDevUnusedFiles": true
|
||||
},
|
||||
"libVersion": "3.16.1"
|
||||
}
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
/// <reference types='@dcloudio/types' />
|
||||
import 'vue'
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
type Hooks = App.AppInstance & Page.PageInstance;
|
||||
|
||||
interface ComponentCustomOptions extends Hooks {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
|
||||
"rules": [{
|
||||
"action": "allow",
|
||||
"page": "*"
|
||||
}]
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { onLaunch, onShow, onHide } from "@dcloudio/uni-app"
|
||||
|
||||
onLaunch(() => {
|
||||
const logs = uni.getStorageSync('logs') || []
|
||||
logs.unshift(Date.now())
|
||||
uni.setStorageSync('logs', logs)
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
})
|
||||
|
||||
onHide(() => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 30rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './user'
|
||||
export * from './request'
|
||||
@@ -0,0 +1,103 @@
|
||||
interface RequestOptions {
|
||||
url: string
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
|
||||
data?: any
|
||||
header?: any
|
||||
needToken?: boolean
|
||||
}
|
||||
|
||||
interface ResponseData<T = any> {
|
||||
code: number
|
||||
message: string
|
||||
data: T
|
||||
}
|
||||
|
||||
const BASE_URL = ''
|
||||
const TOKEN_KEY = 'app_token'
|
||||
|
||||
function getToken(): string {
|
||||
return uni.getStorageSync(TOKEN_KEY) || ''
|
||||
}
|
||||
|
||||
function setToken(token: string): void {
|
||||
uni.setStorageSync(TOKEN_KEY, token)
|
||||
}
|
||||
|
||||
function removeToken(): void {
|
||||
uni.removeStorageSync(TOKEN_KEY)
|
||||
}
|
||||
|
||||
function request<T = any>(options: RequestOptions): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { url, method = 'GET', data, needToken = true } = options
|
||||
|
||||
const header: any = {
|
||||
'Content-Type': 'application/json',
|
||||
...options.header
|
||||
}
|
||||
|
||||
if (needToken) {
|
||||
const token = getToken()
|
||||
if (token) {
|
||||
header['Authorization'] = `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
|
||||
uni.request({
|
||||
url: BASE_URL + url,
|
||||
method,
|
||||
data,
|
||||
header,
|
||||
success: (res: any) => {
|
||||
const result = res.data as ResponseData<T>
|
||||
if (res.statusCode === 200) {
|
||||
if (result.code === 0 || result.code === 200) {
|
||||
resolve(result.data as T)
|
||||
} else if (result.code === 401) {
|
||||
removeToken()
|
||||
uni.showToast({ title: '请先登录', icon: 'none' })
|
||||
reject(new Error(result.message || '未授权'))
|
||||
} else {
|
||||
uni.showToast({ title: result.message || '请求失败', icon: 'none' })
|
||||
reject(new Error(result.message || '请求失败'))
|
||||
}
|
||||
} else {
|
||||
uni.showToast({ title: '网络错误', icon: 'none' })
|
||||
reject(new Error('网络错误'))
|
||||
}
|
||||
},
|
||||
fail: (err: any) => {
|
||||
uni.showToast({ title: '网络连接失败', icon: 'none' })
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function get<T = any>(url: string, data?: any, needToken: boolean = true): Promise<T> {
|
||||
return request<T>({ url, method: 'GET', data, needToken })
|
||||
}
|
||||
|
||||
function post<T = any>(url: string, data?: any, needToken: boolean = true): Promise<T> {
|
||||
return request<T>({ url, method: 'POST', data, needToken })
|
||||
}
|
||||
|
||||
function put<T = any>(url: string, data?: any, needToken: boolean = true): Promise<T> {
|
||||
return request<T>({ url, method: 'PUT', data, needToken })
|
||||
}
|
||||
|
||||
function del<T = any>(url: string, data?: any, needToken: boolean = true): Promise<T> {
|
||||
return request<T>({ url, method: 'DELETE', data, needToken })
|
||||
}
|
||||
|
||||
export {
|
||||
request,
|
||||
get,
|
||||
post,
|
||||
put,
|
||||
del,
|
||||
getToken,
|
||||
setToken,
|
||||
removeToken,
|
||||
BASE_URL
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { get, post } from './request'
|
||||
|
||||
export interface UserInfo {
|
||||
id: number
|
||||
nickname: string
|
||||
avatarUrl: string
|
||||
}
|
||||
|
||||
export interface LoginParams {
|
||||
username: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export interface LoginResult {
|
||||
token: string
|
||||
userInfo: UserInfo
|
||||
}
|
||||
|
||||
export function login(params: LoginParams): Promise<LoginResult> {
|
||||
return post<LoginResult>('/api/login', params, false)
|
||||
}
|
||||
|
||||
export function getUserInfo(): Promise<UserInfo> {
|
||||
return get<UserInfo>('/api/user/info')
|
||||
}
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import { DefineComponent } from 'vue'
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { createSSRApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import App from './App.vue'
|
||||
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
const pinia = createPinia()
|
||||
app.use(pinia)
|
||||
return {
|
||||
app,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"easycom": {
|
||||
"autoscan": true,
|
||||
"custom": {
|
||||
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue"
|
||||
}
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "首页"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/login",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/profile/profile",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人中心"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/logs/logs",
|
||||
"style": {
|
||||
"navigationBarTitleText": "日志"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "zhy-test",
|
||||
"navigationBarBackgroundColor": "#F8F8F8",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
},
|
||||
"tabBar": {
|
||||
"color": "#7A7E83",
|
||||
"selectedColor": "#007AFF",
|
||||
"borderStyle": "black",
|
||||
"backgroundColor": "#ffffff",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/index/index",
|
||||
"text": "首页"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/profile/profile",
|
||||
"text": "我的"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="hero">
|
||||
<image class="logo" src="/static/logo.png" mode="aspectFit" />
|
||||
<text class="title">zhy-test</text>
|
||||
<text class="subtitle">uni-app + Vue3 + TypeScript</text>
|
||||
</view>
|
||||
|
||||
<view class="card-list">
|
||||
<view class="card" @click="goToProfile">
|
||||
<view class="card-icon">👤</view>
|
||||
<view class="card-content">
|
||||
<text class="card-title">个人中心</text>
|
||||
<text class="card-desc">查看个人信息</text>
|
||||
</view>
|
||||
<view class="card-arrow">›</view>
|
||||
</view>
|
||||
|
||||
<view class="card" @click="goToLogs">
|
||||
<view class="card-icon">📝</view>
|
||||
<view class="card-content">
|
||||
<text class="card-title">日志记录</text>
|
||||
<text class="card-desc">查看应用启动日志</text>
|
||||
</view>
|
||||
<view class="card-arrow">›</view>
|
||||
</view>
|
||||
|
||||
<view class="card" @click="goToLogin">
|
||||
<view class="card-icon">🔑</view>
|
||||
<view class="card-content">
|
||||
<text class="card-title">登录</text>
|
||||
<text class="card-desc">用户登录页面</text>
|
||||
</view>
|
||||
<view class="card-arrow">›</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info">
|
||||
<text class="info-title">支持多端发布</text>
|
||||
<view class="tag-list">
|
||||
<text class="tag">微信小程序</text>
|
||||
<text class="tag">H5</text>
|
||||
<text class="tag">App</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
function goToProfile() {
|
||||
uni.switchTab({ url: '/pages/profile/profile' })
|
||||
}
|
||||
|
||||
function goToLogs() {
|
||||
uni.navigateTo({ url: '/pages/logs/logs' })
|
||||
}
|
||||
|
||||
function goToLogin() {
|
||||
uni.navigateTo({ url: '/pages/login/login' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
padding: 30rpx;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.hero {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 60rpx 0;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 26rpx;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.card-list {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.card:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
font-size: 40rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.card-desc {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.card-arrow {
|
||||
font-size: 40rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.info {
|
||||
padding: 30rpx;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.info-title {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
display: block;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.tag-list {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
display: inline-block;
|
||||
padding: 10rpx 30rpx;
|
||||
background: #e8f4ff;
|
||||
color: #007aff;
|
||||
border-radius: 30rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="header">
|
||||
<text class="welcome">欢迎使用</text>
|
||||
<text class="app-name">zhy-test</text>
|
||||
</view>
|
||||
|
||||
<view class="form">
|
||||
<view class="form-item">
|
||||
<text class="label">账号</text>
|
||||
<input
|
||||
class="input"
|
||||
v-model="form.username"
|
||||
placeholder="请输入账号"
|
||||
type="text"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="label">密码</text>
|
||||
<input
|
||||
class="input"
|
||||
v-model="form.password"
|
||||
placeholder="请输入密码"
|
||||
password
|
||||
/>
|
||||
</view>
|
||||
|
||||
<button class="btn-primary" @click="handleLogin" :disabled="loading">
|
||||
{{ loading ? '登录中...' : '登 录' }}
|
||||
</button>
|
||||
|
||||
<view class="tips">
|
||||
<text>当前为演示版本,后端接口未配置时无法真实登录</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useUserStore } from '../../store/user'
|
||||
import { showToast } from '../../utils'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const loading = ref(false)
|
||||
|
||||
const form = reactive({
|
||||
username: '',
|
||||
password: ''
|
||||
})
|
||||
|
||||
async function handleLogin() {
|
||||
if (!form.username || !form.password) {
|
||||
showToast('请输入账号和密码')
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
await userStore.login({
|
||||
username: form.username,
|
||||
password: form.password
|
||||
})
|
||||
showToast('登录成功', 'success')
|
||||
setTimeout(() => {
|
||||
uni.switchTab({ url: '/pages/profile/profile' })
|
||||
}, 1000)
|
||||
} catch (err: any) {
|
||||
console.error('登录失败:', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
padding: 60rpx 40rpx;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 80rpx;
|
||||
padding-top: 40rpx;
|
||||
}
|
||||
|
||||
.welcome {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
color: #666;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.app-name {
|
||||
display: block;
|
||||
font-size: 56rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
padding: 0 20rpx;
|
||||
background: #f8f8f8;
|
||||
border-radius: 12rpx;
|
||||
font-size: 28rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
background: #007aff;
|
||||
color: #fff;
|
||||
border-radius: 12rpx;
|
||||
font-size: 32rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.btn-primary[disabled] {
|
||||
background: #99c9ff;
|
||||
}
|
||||
|
||||
.tips {
|
||||
margin-top: 30rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tips text {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,239 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="user-card">
|
||||
<image
|
||||
class="avatar"
|
||||
:src="userStore.userInfo?.avatarUrl || defaultAvatar"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="user-info">
|
||||
<text class="nickname">{{ userStore.userInfo?.nickname || '未登录' }}</text>
|
||||
<text class="user-id">ID: {{ userStore.userInfo?.id || '---' }}</text>
|
||||
</view>
|
||||
<button v-if="!userStore.isLogin" class="login-btn" size="mini" @click="goLogin">
|
||||
登录
|
||||
</button>
|
||||
<button v-else class="login-btn" size="mini" @click="handleLogout">
|
||||
退出
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">功能菜单</view>
|
||||
|
||||
<view class="menu-list">
|
||||
<view class="menu-item" @click="goToLogs">
|
||||
<view class="menu-icon">📝</view>
|
||||
<text class="menu-text">查看日志</text>
|
||||
<view class="menu-arrow">›</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @click="showAbout">
|
||||
<view class="menu-icon">ℹ️</view>
|
||||
<text class="menu-text">关于应用</text>
|
||||
<view class="menu-arrow">›</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @click="clearCache">
|
||||
<view class="menu-icon">🗑️</view>
|
||||
<text class="menu-text">清除缓存</text>
|
||||
<view class="menu-arrow">›</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">技术栈</view>
|
||||
<view class="tech-list">
|
||||
<view class="tech-item">
|
||||
<text class="tech-label">框架</text>
|
||||
<text class="tech-value">uni-app</text>
|
||||
</view>
|
||||
<view class="tech-item">
|
||||
<text class="tech-label">Vue</text>
|
||||
<text class="tech-value">3.x</text>
|
||||
</view>
|
||||
<view class="tech-item">
|
||||
<text class="tech-label">构建</text>
|
||||
<text class="tech-value">Vite</text>
|
||||
</view>
|
||||
<view class="tech-item">
|
||||
<text class="tech-label">语言</text>
|
||||
<text class="tech-value">TypeScript</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useUserStore } from '../../store/user'
|
||||
import { showToast } from '../../utils'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const defaultAvatar = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0'
|
||||
|
||||
function goLogin() {
|
||||
uni.navigateTo({ url: '/pages/login/login' })
|
||||
}
|
||||
|
||||
function handleLogout() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要退出登录吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
userStore.logout()
|
||||
showToast('已退出登录', 'success')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function goToLogs() {
|
||||
uni.navigateTo({ url: '/pages/logs/logs' })
|
||||
}
|
||||
|
||||
function showAbout() {
|
||||
uni.showModal({
|
||||
title: '关于',
|
||||
content: 'zhy-test uni-app 项目\n\n支持多端发布:\n- 微信小程序\n- H5\n- Android/iOS App',
|
||||
showCancel: false
|
||||
})
|
||||
}
|
||||
|
||||
function clearCache() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要清除缓存吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.clearStorageSync()
|
||||
showToast('缓存已清除', 'success')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
padding: 30rpx;
|
||||
box-sizing: border-box;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.user-card {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 50%;
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
flex: 1;
|
||||
margin-left: 30rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.user-id {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.section {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 20rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.menu-list {
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.menu-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
font-size: 36rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.menu-text {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.menu-arrow {
|
||||
font-size: 36rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.tech-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.tech-item {
|
||||
width: calc(50% - 10rpx);
|
||||
background: #f8f8f8;
|
||||
padding: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tech-label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.tech-value {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
export {}
|
||||
|
||||
declare module "vue" {
|
||||
type Hooks = App.AppInstance & Page.PageInstance;
|
||||
interface ComponentCustomOptions extends Hooks {}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,41 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { getToken, setToken, removeToken } from '../api/request'
|
||||
import { getUserInfo, login as apiLogin, type UserInfo, type LoginParams } from '../api/user'
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
const token = ref<string>(getToken())
|
||||
const userInfo = ref<UserInfo | null>(null)
|
||||
|
||||
const isLogin = computed(() => !!token.value)
|
||||
|
||||
async function login(params: LoginParams) {
|
||||
const result = await apiLogin(params)
|
||||
token.value = result.token
|
||||
userInfo.value = result.userInfo
|
||||
setToken(result.token)
|
||||
return result
|
||||
}
|
||||
|
||||
async function fetchUserInfo() {
|
||||
if (!token.value) return
|
||||
const info = await getUserInfo()
|
||||
userInfo.value = info
|
||||
return info
|
||||
}
|
||||
|
||||
function logout() {
|
||||
token.value = ''
|
||||
userInfo.value = null
|
||||
removeToken()
|
||||
}
|
||||
|
||||
return {
|
||||
token,
|
||||
userInfo,
|
||||
isLogin,
|
||||
login,
|
||||
fetchUserInfo,
|
||||
logout
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 这里是uni-app内置的常用样式变量
|
||||
*
|
||||
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
|
||||
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
|
||||
*
|
||||
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
|
||||
*/
|
||||
|
||||
/* 颜色变量 */
|
||||
|
||||
/* 行为相关颜色 */
|
||||
$uni-color-primary: #007aff;
|
||||
$uni-color-success: #4cd964;
|
||||
$uni-color-warning: #f0ad4e;
|
||||
$uni-color-error: #dd524d;
|
||||
|
||||
/* 文字基本颜色 */
|
||||
$uni-text-color: #333; // 基本色
|
||||
$uni-text-color-inverse: #fff; // 反色
|
||||
$uni-text-color-grey: #999; // 辅助灰色,如加载更多的提示信息
|
||||
$uni-text-color-placeholder: #808080;
|
||||
$uni-text-color-disable: #c0c0c0;
|
||||
|
||||
/* 背景颜色 */
|
||||
$uni-bg-color: #fff;
|
||||
$uni-bg-color-grey: #f8f8f8;
|
||||
$uni-bg-color-hover: #f1f1f1; // 点击状态颜色
|
||||
$uni-bg-color-mask: rgba(0, 0, 0, 0.4); // 遮罩颜色
|
||||
|
||||
/* 边框颜色 */
|
||||
$uni-border-color: #c8c7cc;
|
||||
|
||||
/* 尺寸变量 */
|
||||
|
||||
/* 文字尺寸 */
|
||||
$uni-font-size-sm: 12px;
|
||||
$uni-font-size-base: 14px;
|
||||
$uni-font-size-lg: 16;
|
||||
|
||||
/* 图片尺寸 */
|
||||
$uni-img-size-sm: 20px;
|
||||
$uni-img-size-base: 26px;
|
||||
$uni-img-size-lg: 40px;
|
||||
|
||||
/* Border Radius */
|
||||
$uni-border-radius-sm: 2px;
|
||||
$uni-border-radius-base: 3px;
|
||||
$uni-border-radius-lg: 6px;
|
||||
$uni-border-radius-circle: 50%;
|
||||
|
||||
/* 水平间距 */
|
||||
$uni-spacing-row-sm: 5px;
|
||||
$uni-spacing-row-base: 10px;
|
||||
$uni-spacing-row-lg: 15px;
|
||||
|
||||
/* 垂直间距 */
|
||||
$uni-spacing-col-sm: 4px;
|
||||
$uni-spacing-col-base: 8px;
|
||||
$uni-spacing-col-lg: 12px;
|
||||
|
||||
/* 透明度 */
|
||||
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
|
||||
|
||||
/* 文章场景相关 */
|
||||
$uni-color-title: #2c405a; // 文章标题颜色
|
||||
$uni-font-size-title: 20px;
|
||||
$uni-color-subtitle: #555; // 二级标题颜色
|
||||
$uni-font-size-subtitle: 18px;
|
||||
$uni-color-paragraph: #3f536e; // 文章段落颜色
|
||||
$uni-font-size-paragraph: 15px;
|
||||
@@ -0,0 +1,44 @@
|
||||
const formatNumber = (n: number): string => {
|
||||
const str = n.toString()
|
||||
return str[1] ? str : '0' + str
|
||||
}
|
||||
|
||||
function formatTime(date: Date | number | string): string {
|
||||
const d = new Date(date)
|
||||
const year = d.getFullYear()
|
||||
const month = d.getMonth() + 1
|
||||
const day = d.getDate()
|
||||
const hour = d.getHours()
|
||||
const minute = d.getMinutes()
|
||||
const second = d.getSeconds()
|
||||
return `${year}/${month}/${day} ${[hour, minute, second].map(formatNumber).join(':')}`
|
||||
}
|
||||
|
||||
function showToast(title: string, icon: 'success' | 'loading' | 'error' | 'none' = 'none'): void {
|
||||
uni.showToast({
|
||||
title,
|
||||
icon,
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
|
||||
function navigateTo(url: string): void {
|
||||
uni.navigateTo({ url })
|
||||
}
|
||||
|
||||
function redirectTo(url: string): void {
|
||||
uni.redirectTo({ url })
|
||||
}
|
||||
|
||||
function switchTab(url: string): void {
|
||||
uni.switchTab({ url })
|
||||
}
|
||||
|
||||
export {
|
||||
formatTime,
|
||||
formatNumber,
|
||||
showToast,
|
||||
navigateTo,
|
||||
redirectTo,
|
||||
switchTab
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"lib": ["esnext", "dom"],
|
||||
"types": ["@dcloudio/types"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
const formatTime = date => {
|
||||
const year = date.getFullYear()
|
||||
const month = date.getMonth() + 1
|
||||
const day = date.getDate()
|
||||
const hour = date.getHours()
|
||||
const minute = date.getMinutes()
|
||||
const second = date.getSeconds()
|
||||
|
||||
return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}`
|
||||
}
|
||||
|
||||
const formatNumber = n => {
|
||||
n = n.toString()
|
||||
return n[1] ? n : `0${n}`
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
formatTime
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from "vite";
|
||||
import uni from "@dcloudio/vite-plugin-uni";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [uni()],
|
||||
});
|
||||
Reference in New Issue
Block a user