wip: 临时提交
This commit is contained in:
2064
.husky/uniapp从0到1.md
2064
.husky/uniapp从0到1.md
File diff suppressed because it is too large
Load Diff
49
components.d.ts
vendored
Normal file
49
components.d.ts
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// Generated by vite-plugin-uni-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
export {}
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
CuDateQuery: typeof import('./src/components/cu-date-query/index.vue')['default']
|
||||
CuDict: typeof import('./src/components/cu-dict/index.vue')['default']
|
||||
CuDictLabel: typeof import('./src/components/cu-dict-label/index.vue')['default']
|
||||
CuPicker: typeof import('./src/components/cu-picker/index.vue')['default']
|
||||
DaTree: typeof import('./src/components/da-tree/index.vue')['default']
|
||||
Loading1: typeof import('./src/components/qiun-loading/loading1.vue')['default']
|
||||
Loading2: typeof import('./src/components/qiun-loading/loading2.vue')['default']
|
||||
Loading3: typeof import('./src/components/qiun-loading/loading3.vue')['default']
|
||||
Loading4: typeof import('./src/components/qiun-loading/loading4.vue')['default']
|
||||
Loading5: typeof import('./src/components/qiun-loading/loading5.vue')['default']
|
||||
QiunDataCharts: typeof import('./src/components/qiun-data-charts/qiun-data-charts.vue')['default']
|
||||
QiunError: typeof import('./src/components/qiun-error/qiun-error.vue')['default']
|
||||
QiunLoading: typeof import('./src/components/qiun-loading/qiun-loading.vue')['default']
|
||||
TodoItem: typeof import('./src/components/todo/TodoItem.vue')['default']
|
||||
TodoList: typeof import('./src/components/todo/TodoList.vue')['default']
|
||||
WdButton: typeof import('wot-design-uni/components/wd-button/wd-button.vue')['default']
|
||||
WdCard: typeof import('wot-design-uni/components/wd-card/wd-card.vue')['default']
|
||||
WdCell: typeof import('wot-design-uni/components/wd-cell/wd-cell.vue')['default']
|
||||
WdCellGroup: typeof import('wot-design-uni/components/wd-cell-group/wd-cell-group.vue')['default']
|
||||
WdConfigProvider: typeof import('wot-design-uni/components/wd-config-provider/wd-config-provider.vue')['default']
|
||||
WdForm: typeof import('wot-design-uni/components/wd-form/wd-form.vue')['default']
|
||||
WdGrid: typeof import('wot-design-uni/components/wd-grid/wd-grid.vue')['default']
|
||||
WdGridItem: typeof import('wot-design-uni/components/wd-grid-item/wd-grid-item.vue')['default']
|
||||
WdIcon: typeof import('wot-design-uni/components/wd-icon/wd-icon.vue')['default']
|
||||
WdMessageBox: typeof import('wot-design-uni/components/wd-message-box/wd-message-box.vue')['default']
|
||||
WdNavbar: typeof import('wot-design-uni/components/wd-navbar/wd-navbar.vue')['default']
|
||||
WdNoticeBar: typeof import('wot-design-uni/components/wd-notice-bar/wd-notice-bar.vue')['default']
|
||||
WdNotify: typeof import('wot-design-uni/components/wd-notify/wd-notify.vue')['default']
|
||||
WdPopup: typeof import('wot-design-uni/components/wd-popup/wd-popup.vue')['default']
|
||||
WdRadio: typeof import('wot-design-uni/components/wd-radio/wd-radio.vue')['default']
|
||||
WdRadioGroup: typeof import('wot-design-uni/components/wd-radio-group/wd-radio-group.vue')['default']
|
||||
WdSwiper: typeof import('wot-design-uni/components/wd-swiper/wd-swiper.vue')['default']
|
||||
WdSwitch: typeof import('wot-design-uni/components/wd-switch/wd-switch.vue')['default']
|
||||
WdTabbar: typeof import('wot-design-uni/components/wd-tabbar/wd-tabbar.vue')['default']
|
||||
WdTabbarItem: typeof import('wot-design-uni/components/wd-tabbar-item/wd-tabbar-item.vue')['default']
|
||||
WdTag: typeof import('wot-design-uni/components/wd-tag/wd-tag.vue')['default']
|
||||
WdToast: typeof import('wot-design-uni/components/wd-toast/wd-toast.vue')['default']
|
||||
WechatProfile: typeof import('./src/components/WechatProfile.vue')['default']
|
||||
}
|
||||
}
|
||||
@@ -1,361 +0,0 @@
|
||||
# 主题设置功能指南
|
||||
|
||||
## 功能概述
|
||||
|
||||
本项目提供了完整的主题设置功能,支持暗黑模式切换和主题色自定义,让用户可以个性化应用的外观。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 1. 暗黑模式
|
||||
|
||||
- 支持浅色/暗黑模式切换
|
||||
- 自动保存用户偏好设置
|
||||
- 平滑的过渡动画效果
|
||||
- 导航栏颜色自动适配
|
||||
|
||||
### 2. 主题色设置
|
||||
|
||||
- 12种预设主题色可选
|
||||
- 支持自定义十六进制颜色值
|
||||
- 实时预览效果
|
||||
- 主题色持久化存储
|
||||
|
||||
### 3. 效果预览
|
||||
|
||||
- 按钮样式预览
|
||||
- 文本颜色预览
|
||||
- 标签组件预览
|
||||
- 实时更新显示
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
src/
|
||||
├── composables/
|
||||
│ └── theme/
|
||||
│ ├── theme.ts # 主题管理核心逻辑
|
||||
│ └── rootTheme.ts # 主题配置和默认值
|
||||
├── store/
|
||||
│ └── modules/
|
||||
│ └── theme.ts # 主题状态管理
|
||||
├── pages/
|
||||
│ └── setting.vue # 主题设置页面
|
||||
├── utils/
|
||||
│ ├── theme.ts # 主题工具函数
|
||||
│ └── colorUtils.ts # 颜色处理工具
|
||||
└── styles/
|
||||
└── global.scss # 全局主题样式
|
||||
```
|
||||
|
||||
## 核心组件说明
|
||||
|
||||
### 1. useTheme Composable
|
||||
|
||||
位置:`src/composables/theme/theme.ts`
|
||||
|
||||
主要功能:
|
||||
|
||||
- 主题模式切换(浅色/暗黑)
|
||||
- 主题色设置和保存
|
||||
- 导航栏颜色适配
|
||||
- 本地存储管理
|
||||
|
||||
```typescript
|
||||
const {
|
||||
theme, // 当前主题模式
|
||||
themeVars, // 主题变量
|
||||
toggleTheme, // 切换主题模式
|
||||
setThemeColor, // 设置主题色
|
||||
initTheme, // 初始化主题
|
||||
} = useTheme();
|
||||
```
|
||||
|
||||
### 2. useThemeStore
|
||||
|
||||
位置:`src/store/modules/theme.ts`
|
||||
|
||||
主要功能:
|
||||
|
||||
- 主题色状态管理
|
||||
- CSS变量动态设置
|
||||
- 多平台兼容处理
|
||||
|
||||
```typescript
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
// 设置主题色
|
||||
themeStore.setPrimaryColor("#165DFF");
|
||||
|
||||
// 初始化主题
|
||||
themeStore.initTheme();
|
||||
```
|
||||
|
||||
### 3. 主题设置页面
|
||||
|
||||
位置:`src/pages/setting.vue`
|
||||
|
||||
功能模块:
|
||||
|
||||
- 暗黑模式开关
|
||||
- 预设主题色选择
|
||||
- 自定义颜色输入
|
||||
- 效果实时预览
|
||||
- 重置默认主题
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 基础使用
|
||||
|
||||
在页面中引入主题功能:
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { useTheme } from "@/composables/theme/theme";
|
||||
import { useThemeStore } from "@/store/modules/theme";
|
||||
|
||||
const { theme, themeVars, toggleTheme } = useTheme();
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
// 切换暗黑模式
|
||||
function switchTheme() {
|
||||
toggleTheme();
|
||||
}
|
||||
|
||||
// 设置主题色
|
||||
function changeThemeColor(color) {
|
||||
themeStore.setPrimaryColor(color);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<wd-config-provider :theme-vars="themeVars" :theme="theme">
|
||||
<!-- 你的页面内容 -->
|
||||
</wd-config-provider>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 2. 在布局中使用
|
||||
|
||||
在 `src/layouts/tabbar.vue` 中已经集成了主题功能:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<wd-config-provider :theme-vars="themeVars" :theme="theme">
|
||||
<!-- 布局内容 -->
|
||||
</wd-config-provider>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 3. 自定义主题色
|
||||
|
||||
支持的颜色格式:
|
||||
|
||||
- 6位十六进制:`#165DFF`
|
||||
- 3位十六进制:`#16F`(会自动转换为6位)
|
||||
|
||||
```javascript
|
||||
// 设置自定义主题色
|
||||
const customColor = "#FF6B6B";
|
||||
themeStore.setPrimaryColor(customColor);
|
||||
```
|
||||
|
||||
## 预设主题色
|
||||
|
||||
项目提供了12种预设主题色:
|
||||
|
||||
| 颜色名称 | 颜色值 | 说明 |
|
||||
| -------- | ------- | ---------- |
|
||||
| 蓝色 | #0055FE | 默认主题色 |
|
||||
| 红色 | #CD5C5C | 经典红色 |
|
||||
| 绿色 | #228B22 | 自然绿色 |
|
||||
| 紫色 | #722ED1 | 优雅紫色 |
|
||||
| 橙色 | #FA8C16 | 活力橙色 |
|
||||
| 黄色 | #FADB14 | 明亮黄色 |
|
||||
| 青色 | #13C2C2 | 清新青色 |
|
||||
| 粉色 | #EB2F96 | 温馨粉色 |
|
||||
| 天蓝色 | #1890FF | 科技蓝色 |
|
||||
| 深红色 | #F5222D | 警示红色 |
|
||||
|
||||
## 样式变量
|
||||
|
||||
主题系统使用CSS变量来实现动态主题切换:
|
||||
|
||||
```scss
|
||||
:root {
|
||||
/* 主色调 */
|
||||
--primary-color: #165dff;
|
||||
--primary-color-light: #94bfff;
|
||||
--primary-color-dark: #0e3c9b;
|
||||
|
||||
/* 功能色 */
|
||||
--success-color: #0fc6c2;
|
||||
--warning-color: #ff7d00;
|
||||
--danger-color: #f5222d;
|
||||
--info-color: #86909c;
|
||||
}
|
||||
```
|
||||
|
||||
在组件中使用:
|
||||
|
||||
```scss
|
||||
.my-button {
|
||||
background-color: var(--primary-color);
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--primary-color-dark);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 平台兼容性
|
||||
|
||||
### H5平台
|
||||
|
||||
- 使用CSS变量动态设置主题
|
||||
- 支持所有现代浏览器
|
||||
- 平滑的过渡动画
|
||||
|
||||
### 小程序平台
|
||||
|
||||
- 使用原生API设置TabBar样式
|
||||
- 通过工具函数处理样式应用
|
||||
- 兼容微信、支付宝等小程序
|
||||
|
||||
### APP平台
|
||||
|
||||
- 支持原生导航栏颜色设置
|
||||
- 状态栏颜色自动适配
|
||||
- 性能优化处理
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 主题初始化
|
||||
|
||||
在应用启动时初始化主题:
|
||||
|
||||
```typescript
|
||||
// src/App.vue
|
||||
import { useThemeStore } from "@/store/modules/theme";
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
onLaunch(() => {
|
||||
// 初始化主题
|
||||
themeStore.initTheme();
|
||||
});
|
||||
```
|
||||
|
||||
### 2. 组件中使用主题
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view class="my-component">
|
||||
<text class="title">标题</text>
|
||||
<button class="primary-btn">按钮</button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.my-component {
|
||||
.title {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.primary-btn {
|
||||
background: var(--primary-color);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8rpx;
|
||||
|
||||
&:active {
|
||||
background: var(--primary-color-dark);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
### 3. 暗黑模式适配
|
||||
|
||||
```scss
|
||||
// 暗黑模式样式
|
||||
:deep(.wd-config-provider[data-theme="dark"]) {
|
||||
.my-component {
|
||||
background: #2a2a2a;
|
||||
color: #fff;
|
||||
|
||||
.title {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **性能优化**:主题切换时避免频繁的DOM操作
|
||||
2. **兼容性**:小程序环境下某些CSS特性可能不支持
|
||||
3. **用户体验**:提供平滑的过渡动画效果
|
||||
4. **持久化**:确保用户设置能够正确保存和恢复
|
||||
5. **响应式**:在不同屏幕尺寸下保持良好的显示效果
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 1. 主题色不生效
|
||||
|
||||
- 检查CSS变量是否正确设置
|
||||
- 确认组件是否在 `wd-config-provider` 包裹内
|
||||
- 验证颜色值格式是否正确
|
||||
|
||||
### 2. 暗黑模式切换异常
|
||||
|
||||
- 检查 `theme` 状态是否正确更新
|
||||
- 确认暗黑模式样式是否正确编写
|
||||
- 验证导航栏颜色设置是否生效
|
||||
|
||||
### 3. 设置不持久化
|
||||
|
||||
- 检查本地存储权限
|
||||
- 确认存储key是否正确
|
||||
- 验证初始化逻辑是否执行
|
||||
|
||||
## 扩展开发
|
||||
|
||||
### 1. 添加新的预设主题色
|
||||
|
||||
在 `src/composables/theme/rootTheme.ts` 中添加:
|
||||
|
||||
```typescript
|
||||
export const colorColumns = [
|
||||
// 现有颜色...
|
||||
{
|
||||
value: "#YOUR_COLOR",
|
||||
label: "你的颜色名称",
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
### 2. 自定义主题变量
|
||||
|
||||
在 `src/composables/theme/rootTheme.ts` 中扩展:
|
||||
|
||||
```typescript
|
||||
export const initThemeVars: ConfigProviderThemeVars = {
|
||||
colorTheme: colorColumns[0].value,
|
||||
// 添加更多主题变量
|
||||
colorSuccess: "#52c41a",
|
||||
colorWarning: "#faad14",
|
||||
colorDanger: "#ff4d4f",
|
||||
};
|
||||
```
|
||||
|
||||
### 3. 添加主题切换动画
|
||||
|
||||
```scss
|
||||
.theme-transition {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
```
|
||||
|
||||
通过以上指南,你可以充分利用项目的主题设置功能,为用户提供个性化的应用体验。
|
||||
@@ -1,342 +0,0 @@
|
||||
# 微信小程序手机授权登录功能指南
|
||||
|
||||
## 功能概述
|
||||
|
||||
本项目实现了完整的微信小程序手机授权登录功能,包括:
|
||||
|
||||
- 微信登录授权
|
||||
- 手机号获取授权
|
||||
- 头像昵称填写(使用微信小程序新能力)
|
||||
- 用户信息完善流程
|
||||
- 登录状态管理
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 1. 微信登录流程
|
||||
|
||||
- **基础微信登录**:使用 `uni.login()` 获取 code,调用后端接口完成登录
|
||||
- **增强微信登录**:支持更多用户信息和手机号一次性授权
|
||||
- **登录状态检查**:自动检查用户信息完整性,引导用户完善信息
|
||||
|
||||
### 2. 手机号授权
|
||||
|
||||
- **一键授权**:使用微信小程序 `getPhoneNumber` 能力
|
||||
- **安全获取**:通过后端接口解密获取真实手机号
|
||||
- **状态显示**:显示脱敏手机号,支持重新授权
|
||||
|
||||
### 3. 头像昵称填写
|
||||
|
||||
- **微信新能力**:使用 `chooseAvatar` 和 `type="nickname"` 输入框
|
||||
- **自动上传**:头像选择后自动上传到服务器
|
||||
- **实时预览**:支持头像实时预览和昵称输入
|
||||
|
||||
### 4. 用户信息完善
|
||||
|
||||
- **智能引导**:登录后自动检查信息完整性
|
||||
- **分步填写**:头像、昵称、性别、手机号分步骤完善
|
||||
- **跳过机制**:允许用户暂时跳过,但会提示影响功能使用
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
src/
|
||||
├── pages/
|
||||
│ └── login/
|
||||
│ ├── index.vue # 登录页面
|
||||
│ └── complete-profile.vue # 完善信息页面
|
||||
├── components/
|
||||
│ └── WechatProfile.vue # 微信头像昵称组件
|
||||
├── api/
|
||||
│ ├── auth.ts # 认证API
|
||||
│ ├── user.ts # 用户API
|
||||
│ └── file.ts # 文件上传API
|
||||
├── store/
|
||||
│ └── modules/
|
||||
│ └── user.ts # 用户状态管理
|
||||
└── utils/
|
||||
├── auth.ts # 认证工具函数
|
||||
└── storage.ts # 存储工具函数
|
||||
```
|
||||
|
||||
## 核心组件说明
|
||||
|
||||
### 1. 登录页面 (`pages/login/index.vue`)
|
||||
|
||||
**主要功能:**
|
||||
|
||||
- 用户名密码登录
|
||||
- 微信一键登录
|
||||
- 登录状态检查和跳转
|
||||
|
||||
**关键代码:**
|
||||
|
||||
```typescript
|
||||
// 微信登录处理
|
||||
const handleWechatLogin = async () => {
|
||||
const { code } = await uni.login({ provider: "weixin" });
|
||||
|
||||
// 尝试增强登录
|
||||
try {
|
||||
const result = await userStore.loginByWechatMini({ code });
|
||||
// 检查信息完整性
|
||||
if (result.isNewUser || !result.isProfileComplete) {
|
||||
// 跳转到完善信息页面
|
||||
uni.navigateTo({
|
||||
url: `/pages/login/complete-profile?redirect=${redirect}`,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// 回退到基础登录
|
||||
await userStore.loginByWechat(code);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 2. 完善信息页面 (`pages/login/complete-profile.vue`)
|
||||
|
||||
**主要功能:**
|
||||
|
||||
- 头像上传(支持微信新能力)
|
||||
- 昵称输入
|
||||
- 性别选择
|
||||
- 手机号授权
|
||||
- 信息提交和验证
|
||||
|
||||
**关键代码:**
|
||||
|
||||
```typescript
|
||||
// 手机号授权
|
||||
const onGetPhoneNumber = async (e: any) => {
|
||||
if (e.detail.errMsg === "getPhoneNumber:ok") {
|
||||
const phoneData = await UserAPI.getPhoneNumber({
|
||||
code: e.detail.code,
|
||||
encryptedData: e.detail.encryptedData,
|
||||
iv: e.detail.iv,
|
||||
});
|
||||
profileForm.mobile = phoneData.phoneNumber;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 3. 微信头像昵称组件 (`components/WechatProfile.vue`)
|
||||
|
||||
**主要功能:**
|
||||
|
||||
- 使用微信小程序头像选择能力
|
||||
- 昵称输入框(type="nickname")
|
||||
- 性别选择
|
||||
- 数据双向绑定
|
||||
|
||||
**关键代码:**
|
||||
|
||||
```vue
|
||||
<!-- 头像选择 -->
|
||||
<button open-type="chooseAvatar" @chooseavatar="onChooseAvatar">
|
||||
<image v-if="avatar" :src="avatar" />
|
||||
</button>
|
||||
|
||||
<!-- 昵称输入 -->
|
||||
<input v-model="nickname" type="nickname" placeholder="请输入昵称" />
|
||||
```
|
||||
|
||||
## API 接口说明
|
||||
|
||||
### 1. 认证相关接口
|
||||
|
||||
```typescript
|
||||
// 基础微信登录
|
||||
AuthAPI.wechatLogin(code: string): Promise<LoginResult>
|
||||
|
||||
// 增强微信登录
|
||||
AuthAPI.wechatMiniLogin(data: WechatMiniLoginData): Promise<WechatLoginResult>
|
||||
```
|
||||
|
||||
### 2. 用户相关接口
|
||||
|
||||
```typescript
|
||||
// 获取微信手机号
|
||||
UserAPI.getPhoneNumber(data: WechatPhoneData): Promise<PhoneNumberResult>
|
||||
|
||||
// 绑定手机号
|
||||
UserAPI.bindMobile(data: MobileBindingForm): Promise<void>
|
||||
|
||||
// 更新用户信息
|
||||
UserAPI.updateProfile(data: UserProfileForm): Promise<void>
|
||||
```
|
||||
|
||||
### 3. 文件上传接口
|
||||
|
||||
```typescript
|
||||
// 上传文件
|
||||
FileAPI.upload(filePath: string): Promise<FileInfo>
|
||||
```
|
||||
|
||||
## 类型定义
|
||||
|
||||
### 微信登录相关
|
||||
|
||||
```typescript
|
||||
interface WechatMiniLoginData {
|
||||
code: string;
|
||||
userInfo?: {
|
||||
nickName?: string;
|
||||
avatarUrl?: string;
|
||||
gender?: number;
|
||||
};
|
||||
phoneData?: {
|
||||
code: string;
|
||||
encryptedData?: string;
|
||||
iv?: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface WechatLoginResult extends LoginResult {
|
||||
isNewUser?: boolean;
|
||||
isProfileComplete?: boolean;
|
||||
userInfo?: {
|
||||
userId?: number;
|
||||
username?: string;
|
||||
nickname?: string;
|
||||
avatar?: string;
|
||||
mobile?: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 手机号授权相关
|
||||
|
||||
```typescript
|
||||
interface WechatPhoneData {
|
||||
code: string;
|
||||
encryptedData?: string;
|
||||
iv?: string;
|
||||
}
|
||||
|
||||
interface PhoneNumberResult {
|
||||
phoneNumber: string;
|
||||
purePhoneNumber?: string;
|
||||
countryCode?: string;
|
||||
}
|
||||
```
|
||||
|
||||
## 使用流程
|
||||
|
||||
### 1. 用户首次登录
|
||||
|
||||
1. 用户点击微信登录按钮
|
||||
2. 调用 `uni.login()` 获取微信 code
|
||||
3. 调用后端登录接口,获取 token
|
||||
4. 检查用户信息完整性
|
||||
5. 如果信息不完整,跳转到完善信息页面
|
||||
|
||||
### 2. 完善用户信息
|
||||
|
||||
1. 用户进入完善信息页面
|
||||
2. 选择头像(使用微信新能力或传统上传)
|
||||
3. 输入昵称(使用 type="nickname" 输入框)
|
||||
4. 选择性别
|
||||
5. 授权获取手机号
|
||||
6. 提交信息,更新用户资料
|
||||
|
||||
### 3. 后续登录
|
||||
|
||||
1. 用户再次登录时,检查信息完整性
|
||||
2. 如果信息完整,直接跳转到主页
|
||||
3. 如果信息不完整,引导用户完善
|
||||
|
||||
## 配置要求
|
||||
|
||||
### 1. 微信小程序配置
|
||||
|
||||
在 `manifest.json` 中配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"mp-weixin": {
|
||||
"appid": "your-appid",
|
||||
"setting": {
|
||||
"urlCheck": false
|
||||
},
|
||||
"permission": {
|
||||
"scope.userInfo": {
|
||||
"desc": "用于完善用户资料"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 后端接口要求
|
||||
|
||||
- 支持微信登录 code 解析
|
||||
- 支持微信手机号解密
|
||||
- 支持文件上传
|
||||
- 支持用户信息更新
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 1. 微信小程序新能力
|
||||
|
||||
- `chooseAvatar` 和 `type="nickname"` 需要微信基础库 2.21.2+
|
||||
- 需要在微信开发者工具中测试
|
||||
- 真机调试时需要注意兼容性
|
||||
|
||||
### 2. 手机号授权
|
||||
|
||||
- 需要微信小程序认证
|
||||
- 需要在微信公众平台配置服务器域名
|
||||
- 手机号解密需要在后端完成
|
||||
|
||||
### 3. 用户体验
|
||||
|
||||
- 提供跳过机制,避免强制完善信息
|
||||
- 显示脱敏手机号,保护用户隐私
|
||||
- 支持重新授权和修改信息
|
||||
|
||||
## 扩展功能
|
||||
|
||||
### 1. 社交登录
|
||||
|
||||
可以扩展支持其他社交平台登录:
|
||||
|
||||
- QQ 登录
|
||||
- 支付宝登录
|
||||
- 苹果登录
|
||||
|
||||
### 2. 实名认证
|
||||
|
||||
可以添加实名认证功能:
|
||||
|
||||
- 身份证验证
|
||||
- 人脸识别
|
||||
- 银行卡验证
|
||||
|
||||
### 3. 多端同步
|
||||
|
||||
可以实现多端登录状态同步:
|
||||
|
||||
- H5 端登录
|
||||
- APP 端登录
|
||||
- 小程序端登录
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 1. 微信登录失败
|
||||
|
||||
- 检查 appid 配置
|
||||
- 检查服务器域名配置
|
||||
- 检查网络连接
|
||||
|
||||
### 2. 手机号授权失败
|
||||
|
||||
- 检查小程序是否已认证
|
||||
- 检查后端解密接口
|
||||
- 检查用户授权状态
|
||||
|
||||
### 3. 头像上传失败
|
||||
|
||||
- 检查文件上传接口
|
||||
- 检查文件大小限制
|
||||
- 检查网络状态
|
||||
|
||||
## 总结
|
||||
@@ -90,11 +90,12 @@
|
||||
"@dcloudio/uni-mp-weixin": "3.0.0-4020420240722002",
|
||||
"@dcloudio/uni-mp-xhs": "3.0.0-4020420240722002",
|
||||
"@dcloudio/uni-quickapp-webview": "3.0.0-4020420240722002",
|
||||
"@stomp/stompjs": "^7.1.1",
|
||||
"@uni-helper/uni-use": "^0.19.14",
|
||||
"@vueuse/core": "10.11.1",
|
||||
"@vueuse/core": "9.13.0",
|
||||
"pinia": "^2.2.2",
|
||||
"vue": "^3.5.13",
|
||||
"wot-design-uni": "^1.4.0"
|
||||
"wot-design-uni": "^1.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^19.5.0",
|
||||
@@ -105,6 +106,7 @@
|
||||
"@dcloudio/uni-stacktracey": "3.0.0-4020420240722002",
|
||||
"@dcloudio/vite-plugin-uni": "3.0.0-4020420240722002",
|
||||
"@eslint/js": "^9.10.0",
|
||||
"@uni-helper/vite-plugin-uni-components": "^0.2.0",
|
||||
"@uni-helper/vite-plugin-uni-layouts": "^0.1.10",
|
||||
"@uni-helper/vite-plugin-uni-pages": "^0.2.28",
|
||||
"@vue/runtime-core": "^3.4.21",
|
||||
@@ -135,7 +137,7 @@
|
||||
"unocss": "^0.62.4",
|
||||
"unocss-preset-weapp": "^0.62.2",
|
||||
"unplugin-auto-import": "^0.18.3",
|
||||
"vite": "5.2.8",
|
||||
"vite": "6.3.2",
|
||||
"vue-eslint-parser": "^9.4.3",
|
||||
"vue-tsc": "^1.0.24"
|
||||
}
|
||||
|
||||
@@ -3,9 +3,36 @@ import { defineUniPages } from "@uni-helper/vite-plugin-uni-pages";
|
||||
|
||||
export default defineUniPages({
|
||||
// 你也可以定义 pages 字段,它具有最高的优先级。
|
||||
pages: [],
|
||||
pages: [
|
||||
{
|
||||
path: "pages/index/index",
|
||||
layout: "tabbar",
|
||||
},
|
||||
{
|
||||
path: "pages/mine/index",
|
||||
layout: "tabbar",
|
||||
},
|
||||
],
|
||||
globalStyle: {
|
||||
navigationBarTextStyle: "black",
|
||||
navigationBarTitleText: "@uni-helper",
|
||||
navigationBarTitleText: "vue-uniapp-template",
|
||||
},
|
||||
|
||||
tabBar: {
|
||||
custom: true,
|
||||
height: "0px",
|
||||
color: "#00000000",
|
||||
selectedColor: "#00000000",
|
||||
backgroundColor: "#00000000",
|
||||
list: [
|
||||
{
|
||||
text: "首页",
|
||||
pagePath: "pages/index/index",
|
||||
},
|
||||
{
|
||||
text: "我的",
|
||||
pagePath: "pages/mine/index",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getToken } from "@/utils/storage";
|
||||
import { ResultCodeEnum } from "@/enums/ResultCodeEnum";
|
||||
import { ApiCode } from "@/enums/api-code.enum";
|
||||
|
||||
// H5 使用 VITE_APP_BASE_API 作为代理路径,其他平台使用 VITE_APP_API_URL 作为请求路径
|
||||
let baseApi = import.meta.env.VITE_APP_API_URL;
|
||||
@@ -31,7 +31,7 @@ const FileAPI = {
|
||||
success: (response) => {
|
||||
const resData = JSON.parse(response.data) as ResponseData<FileInfo>;
|
||||
// 业务状态码 00000 表示成功
|
||||
if (resData.code === ResultCodeEnum.SUCCESS) {
|
||||
if (resData.code === ApiCode.SUCCESS) {
|
||||
resolve(resData.data);
|
||||
} else {
|
||||
// 其他业务处理失败
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import type { ConfigProviderThemeVars } from "wot-design-uni";
|
||||
/* 默认的主题list */
|
||||
export const colorColumns = [
|
||||
{
|
||||
value: "#0055FE",
|
||||
label: "蓝色",
|
||||
},
|
||||
{
|
||||
value: "#CD5C5C",
|
||||
label: "红色",
|
||||
},
|
||||
{
|
||||
value: "#228B22",
|
||||
label: "绿色",
|
||||
},
|
||||
];
|
||||
/* 默认的主题 */
|
||||
export const initThemState = "light";
|
||||
/* 默认的主题 */
|
||||
export const initThemeVars: ConfigProviderThemeVars = {
|
||||
colorTheme: colorColumns[0].value,
|
||||
};
|
||||
@@ -1,74 +0,0 @@
|
||||
import { colorColumns, initThemState, initThemeVars } from "./rootTheme";
|
||||
/* 暗黑模式切换 */
|
||||
const theme = ref<"light" | "dark">(initThemState);
|
||||
/* 组件库的主题色 */
|
||||
const themeVars = ref({ ...initThemeVars });
|
||||
|
||||
// 从本地存储恢复主题设置
|
||||
function loadThemeFromStorage() {
|
||||
try {
|
||||
const savedTheme = uni.getStorageSync("app_theme_mode");
|
||||
const savedThemeColor = uni.getStorageSync("app_theme_color");
|
||||
|
||||
if (savedTheme && (savedTheme === "light" || savedTheme === "dark")) {
|
||||
theme.value = savedTheme;
|
||||
}
|
||||
|
||||
if (savedThemeColor) {
|
||||
themeVars.value.colorTheme = savedThemeColor;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载主题设置失败:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存主题设置到本地存储
|
||||
function saveThemeToStorage() {
|
||||
try {
|
||||
uni.setStorageSync("app_theme_mode", theme.value);
|
||||
uni.setStorageSync("app_theme_color", themeVars.value.colorTheme);
|
||||
} catch (error) {
|
||||
console.error("保存主题设置失败:", error);
|
||||
}
|
||||
}
|
||||
|
||||
export function useTheme() {
|
||||
/* 切换暗黑模式 */
|
||||
function toggleTheme(mode?: "light" | "dark") {
|
||||
theme.value = mode || (theme.value === "light" ? "dark" : "light");
|
||||
setNavigationBarColor();
|
||||
saveThemeToStorage();
|
||||
}
|
||||
|
||||
/* 设置主题色 */
|
||||
function setThemeColor(color: string) {
|
||||
themeVars.value.colorTheme = color;
|
||||
saveThemeToStorage();
|
||||
}
|
||||
|
||||
/* 初始化theme */
|
||||
function initTheme() {
|
||||
loadThemeFromStorage();
|
||||
setNavigationBarColor();
|
||||
}
|
||||
|
||||
function setNavigationBarColor() {
|
||||
uni.setNavigationBarColor({
|
||||
frontColor: theme.value === "light" ? "#000000" : "#ffffff",
|
||||
backgroundColor: theme.value === "light" ? "#ffffff" : "#000000",
|
||||
animation: {
|
||||
duration: 400,
|
||||
timingFunc: "easeIn",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
theme,
|
||||
themeVars,
|
||||
initTheme,
|
||||
colorColumns,
|
||||
toggleTheme,
|
||||
setThemeColor,
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Client, type IMessage, type StompSubscription } from "@stomp/stompjs";
|
||||
import { Auth } from "@/utils/auth";
|
||||
import { getAccessToken } from "@/utils/auth";
|
||||
|
||||
export interface UseStompOptions {
|
||||
/** WebSocket 地址,不传时使用 VITE_APP_WS_ENDPOINT 环境变量 */
|
||||
@@ -70,7 +70,7 @@ export function useStomp(options: UseStompOptions = {}) {
|
||||
}
|
||||
|
||||
// 每次连接前重新获取最新令牌,不依赖之前的token值
|
||||
const currentToken = Auth.getAccessToken();
|
||||
const currentToken = getAccessToken();
|
||||
|
||||
// 检查令牌是否为空,如果为空则不进行连接
|
||||
if (!currentToken) {
|
||||
@@ -123,7 +123,7 @@ export function useStomp(options: UseStompOptions = {}) {
|
||||
};
|
||||
|
||||
// 设置 Web Socket 关闭监听器
|
||||
client.value.onWebSocketClose = (event) => {
|
||||
client.value.onWebSocketClose = (event: CloseEvent) => {
|
||||
isConnected.value = false;
|
||||
isConnecting = false;
|
||||
console.log(`WebSocket已关闭: ${event?.code} ${event?.reason}`);
|
||||
@@ -147,7 +147,7 @@ export function useStomp(options: UseStompOptions = {}) {
|
||||
};
|
||||
|
||||
// 设置错误监听器
|
||||
client.value.onStompError = (frame) => {
|
||||
client.value.onStompError = (frame: any) => {
|
||||
console.error("STOMP错误:", frame.headers, frame.body);
|
||||
isConnecting = false;
|
||||
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* @Author: weisheng
|
||||
* @Date: 2024-10-29 22:12:54
|
||||
* @LastEditTime: 2025-01-13 16:45:28
|
||||
* @LastEditors: 810505339
|
||||
* @Description:
|
||||
* @FilePath: \wot-demo\src\composables\useTabbar.ts
|
||||
* 记得注释
|
||||
*/
|
||||
export interface TabbarItem {
|
||||
name: string;
|
||||
value: number | null;
|
||||
active: boolean;
|
||||
title: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
const tabbarItems = ref<TabbarItem[]>([
|
||||
{ name: "home", value: null, active: true, title: "home", icon: "home" },
|
||||
{ name: "hi", value: null, active: false, title: "hi", icon: "app" },
|
||||
{ name: "setting", value: null, active: false, title: "setting", icon: "setting" },
|
||||
]);
|
||||
|
||||
export function useTabbar() {
|
||||
const tabbarList = computed(() => tabbarItems.value);
|
||||
|
||||
const activeTabbar = computed(() => {
|
||||
const item = tabbarItems.value.find((item) => item.active);
|
||||
return item || tabbarItems.value[0];
|
||||
});
|
||||
|
||||
const getTabbarItemValue = (name: string) => {
|
||||
const item = tabbarItems.value.find((item) => item.name === name);
|
||||
return item && item.value ? item.value : null;
|
||||
};
|
||||
|
||||
const setTabbarItem = (name: string, value: number) => {
|
||||
const tabbarItem = tabbarItems.value.find((item) => item.name === name);
|
||||
if (tabbarItem) {
|
||||
tabbarItem.value = value;
|
||||
}
|
||||
};
|
||||
|
||||
const setTabbarItemActive = (name: string) => {
|
||||
tabbarItems.value.forEach((item) => {
|
||||
if (item.name === name) {
|
||||
item.active = true;
|
||||
} else {
|
||||
item.active = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
tabbarList,
|
||||
activeTabbar,
|
||||
getTabbarItemValue,
|
||||
setTabbarItem,
|
||||
setTabbarItemActive,
|
||||
};
|
||||
}
|
||||
76
src/composables/useTheme.ts
Normal file
76
src/composables/useTheme.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { ref, computed } from "vue";
|
||||
import type { ConfigProviderThemeVars } from "wot-design-uni";
|
||||
|
||||
/* 默认的主题list */
|
||||
export const colorColumns = [
|
||||
{
|
||||
value: "#0055FE",
|
||||
label: "蓝色",
|
||||
},
|
||||
{
|
||||
value: "#CD5C5C",
|
||||
label: "红色",
|
||||
},
|
||||
{
|
||||
value: "#228B22",
|
||||
label: "绿色",
|
||||
},
|
||||
];
|
||||
|
||||
/* 默认的主题 */
|
||||
export const initThemState = "light";
|
||||
|
||||
/* 默认的主题变量 - 只使用wot-design-uni支持的变量 */
|
||||
export const initThemeVars: ConfigProviderThemeVars = {
|
||||
colorTheme: colorColumns[0].value,
|
||||
};
|
||||
|
||||
/* 暗黑模式主题变量 */
|
||||
export const darkThemeVars: ConfigProviderThemeVars = {
|
||||
colorTheme: colorColumns[0].value,
|
||||
};
|
||||
|
||||
/* 主题状态 */
|
||||
export const themeState = ref(initThemState);
|
||||
|
||||
/* 主题变量 */
|
||||
export const themeVars = ref<ConfigProviderThemeVars>(initThemeVars);
|
||||
|
||||
/* 计算属性:根据主题状态返回对应的主题变量 */
|
||||
export const computedThemeVars = computed(() => {
|
||||
const baseVars = themeState.value === "dark" ? darkThemeVars : initThemeVars;
|
||||
return {
|
||||
...baseVars,
|
||||
...themeVars.value,
|
||||
};
|
||||
});
|
||||
|
||||
/* 切换主题 */
|
||||
export const toggleTheme = () => {
|
||||
themeState.value = themeState.value === "light" ? "dark" : "light";
|
||||
// 更新主题变量
|
||||
const newBaseVars = themeState.value === "dark" ? darkThemeVars : initThemeVars;
|
||||
themeVars.value = {
|
||||
...newBaseVars,
|
||||
colorTheme: themeVars.value.colorTheme, // 保持用户选择的主题色
|
||||
};
|
||||
};
|
||||
|
||||
/* 设置主题色 */
|
||||
export const setThemeColor = (color: string) => {
|
||||
themeVars.value = {
|
||||
...themeVars.value,
|
||||
colorTheme: color,
|
||||
};
|
||||
};
|
||||
|
||||
/* 导出主题相关的工具 */
|
||||
export const useTheme = () => {
|
||||
return {
|
||||
themeState,
|
||||
themeVars: computedThemeVars,
|
||||
toggleTheme,
|
||||
setThemeColor,
|
||||
colorColumns,
|
||||
};
|
||||
};
|
||||
@@ -1,4 +1,6 @@
|
||||
export const ROLE_ROOT = "ROOT";
|
||||
/**
|
||||
* 常量统一导出
|
||||
*/
|
||||
|
||||
// 🔗 导出所有存储键常量
|
||||
export * from "./storage-keys";
|
||||
// 存储相关常量
|
||||
export * from "./storage.constant";
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import type { App } from "vue";
|
||||
|
||||
import { hasPerm, hasRole } from "./permission";
|
||||
|
||||
// 全局注册 directive
|
||||
export function setupDirective(app: App<Element>) {
|
||||
// 使 v-hasPerm 在所有组件中都可用
|
||||
app.directive("hasPerm", hasPerm);
|
||||
app.directive("hasRole", hasRole);
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import type { Directive, DirectiveBinding } from "vue";
|
||||
|
||||
import { useUserStore } from "@/store";
|
||||
|
||||
/**
|
||||
* 按钮权限
|
||||
*/
|
||||
export const hasPerm: Directive = {
|
||||
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
||||
const requiredPerms = binding.value;
|
||||
|
||||
console.log("requiredPerms", requiredPerms);
|
||||
if (!requiredPerms) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 校验传入的权限值是否合法
|
||||
if (!requiredPerms || (typeof requiredPerms !== "string" && !Array.isArray(requiredPerms))) {
|
||||
throw new Error(
|
||||
"需要提供权限标识!例如:v-has-perm=\"'sys:user:add'\" 或 v-has-perm=\"['sys:user:add', 'sys:user:edit']\""
|
||||
);
|
||||
}
|
||||
|
||||
const { roles = [], perms = [] } = useUserStore().userInfo || {};
|
||||
|
||||
// 超级管理员拥有所有权限
|
||||
if (roles?.includes("ROOT")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查权限
|
||||
const hasAuth = Array.isArray(requiredPerms)
|
||||
? requiredPerms.some((perm) => perms.includes(perm))
|
||||
: perms.includes(requiredPerms);
|
||||
|
||||
// 如果没有权限,移除该元素
|
||||
if (!hasAuth && el.parentNode) {
|
||||
el.parentNode.removeChild(el);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 角色权限指令
|
||||
*/
|
||||
export const hasRole: Directive = {
|
||||
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
||||
const requiredRoles = binding.value;
|
||||
|
||||
// 校验传入的角色值是否合法
|
||||
if (!requiredRoles || (typeof requiredRoles !== "string" && !Array.isArray(requiredRoles))) {
|
||||
throw new Error(
|
||||
"需要提供角色标识!例如:v-has-role=\"'ADMIN'\" 或 v-has-role=\"['ADMIN', 'TEST']\""
|
||||
);
|
||||
}
|
||||
|
||||
const { roles = [] } = useUserStore().userInfo || {};
|
||||
|
||||
// 检查是否有对应角色权限
|
||||
const hasAuth = Array.isArray(requiredRoles)
|
||||
? requiredRoles.some((role) => roles.includes(role))
|
||||
: roles.includes(requiredRoles);
|
||||
|
||||
// 如果没有权限,移除元素
|
||||
if (!hasAuth && el.parentNode) {
|
||||
el.parentNode.removeChild(el);
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* 响应码枚举
|
||||
*/
|
||||
export const enum ResultCodeEnum {
|
||||
/**
|
||||
* 成功
|
||||
*/
|
||||
SUCCESS = "00000",
|
||||
/**
|
||||
* 错误
|
||||
*/
|
||||
ERROR = "B0001",
|
||||
|
||||
/**
|
||||
* 令牌无效或过期
|
||||
*/
|
||||
TOKEN_INVALID = "A0230",
|
||||
}
|
||||
49
src/enums/api-code.enum.ts
Normal file
49
src/enums/api-code.enum.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* API响应码枚举
|
||||
*/
|
||||
export const enum ApiCode {
|
||||
/**
|
||||
* 成功
|
||||
*/
|
||||
SUCCESS = "00000",
|
||||
|
||||
/**
|
||||
* 通用错误
|
||||
*/
|
||||
ERROR = "B0001",
|
||||
|
||||
/**
|
||||
* 令牌无效或过期
|
||||
*/
|
||||
TOKEN_INVALID = "A0230",
|
||||
|
||||
/**
|
||||
* 令牌已过期
|
||||
*/
|
||||
TOKEN_EXPIRED = "A0231",
|
||||
|
||||
/**
|
||||
* 未授权访问
|
||||
*/
|
||||
UNAUTHORIZED = "A0232",
|
||||
|
||||
/**
|
||||
* 禁止访问
|
||||
*/
|
||||
FORBIDDEN = "A0233",
|
||||
|
||||
/**
|
||||
* 参数校验失败
|
||||
*/
|
||||
PARAM_INVALID = "A0400",
|
||||
|
||||
/**
|
||||
* 资源不存在
|
||||
*/
|
||||
NOT_FOUND = "A0404",
|
||||
|
||||
/**
|
||||
* 服务器内部错误
|
||||
*/
|
||||
INTERNAL_ERROR = "B0500",
|
||||
}
|
||||
@@ -1,4 +1,32 @@
|
||||
<script lang="ts" setup>
|
||||
import type { ConfigProviderThemeVars } from "wot-design-uni";
|
||||
|
||||
const themeVars = reactive<ConfigProviderThemeVars>({
|
||||
colorTheme: "#FF5454",
|
||||
tabsNavLineBgColor: "red",
|
||||
navbarColor: "#ffffff",
|
||||
});
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
options: {
|
||||
addGlobalClass: true,
|
||||
virtualHost: true,
|
||||
styleIsolation: "shared",
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>默认布局</div>
|
||||
<slot></slot>
|
||||
<wd-config-provider
|
||||
:theme-vars="themeVars"
|
||||
custom-style="background-color: #f5f5f5;min-height: 100vh"
|
||||
>
|
||||
<slot />
|
||||
<wd-notify />
|
||||
<wd-toast />
|
||||
<wd-message-box />
|
||||
<privacy-popup />
|
||||
</wd-config-provider>
|
||||
</template>
|
||||
|
||||
@@ -8,26 +8,79 @@
|
||||
* 记得注释
|
||||
-->
|
||||
<script lang="ts" setup>
|
||||
import { useTabbar } from "@/composables/useTabbar";
|
||||
import { useTheme } from "@/composables/theme/theme";
|
||||
import { ref, computed, onMounted, nextTick } from "vue";
|
||||
import { useTheme } from "@/composables/useTheme";
|
||||
|
||||
const router = useRouter();
|
||||
// 定义 TabbarItem 接口
|
||||
interface TabbarItem {
|
||||
name: string;
|
||||
value: number | null;
|
||||
active: boolean;
|
||||
title: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
const route = useRoute();
|
||||
// 根据 pages.json 配置的 tabbar 项目
|
||||
const tabbarItems = ref<TabbarItem[]>([
|
||||
{ name: "index", value: null, active: true, title: "首页", icon: "home" },
|
||||
{ name: "mine", value: null, active: false, title: "我的", icon: "user" },
|
||||
]);
|
||||
|
||||
const { themeVars, theme } = useTheme();
|
||||
const { themeVars } = useTheme();
|
||||
|
||||
const { activeTabbar, getTabbarItemValue, setTabbarItemActive, tabbarList } = useTabbar();
|
||||
// 计算属性
|
||||
const tabbarList = computed(() => tabbarItems.value);
|
||||
|
||||
const activeTabbar = computed(() => {
|
||||
const item = tabbarItems.value.find((item) => item.active);
|
||||
return item || tabbarItems.value[0];
|
||||
});
|
||||
|
||||
// 方法
|
||||
const getTabbarItemValue = (name: string) => {
|
||||
const item = tabbarItems.value.find((item) => item.name === name);
|
||||
return item && item.value ? item.value : null;
|
||||
};
|
||||
|
||||
const setTabbarItemActive = (name: string) => {
|
||||
tabbarItems.value.forEach((item) => {
|
||||
if (item.name === name) {
|
||||
item.active = true;
|
||||
} else {
|
||||
item.active = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function handleTabbarChange({ value }: { value: string }) {
|
||||
setTabbarItemActive(value);
|
||||
router.pushTab({ name: value });
|
||||
|
||||
// 根据 tabbar 项目导航到对应页面
|
||||
if (value === "index") {
|
||||
uni.switchTab({
|
||||
url: "/pages/index/index",
|
||||
});
|
||||
} else if (value === "mine") {
|
||||
uni.switchTab({
|
||||
url: "/pages/mine/index",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
if (route.name && route.name !== activeTabbar.value.name) {
|
||||
setTabbarItemActive(route.name);
|
||||
// 获取当前页面路径
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length > 0) {
|
||||
const currentPage = pages[pages.length - 1];
|
||||
const route = currentPage.route;
|
||||
|
||||
// 根据当前路由设置活跃的 tabbar
|
||||
if (route?.includes("pages/index/index")) {
|
||||
setTabbarItemActive("index");
|
||||
} else if (route?.includes("pages/mine/index")) {
|
||||
setTabbarItemActive("mine");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -50,7 +103,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<wd-config-provider :theme-vars="themeVars" custom-style="min-height: 100vh" :theme="theme">
|
||||
<wd-config-provider :theme-vars="themeVars" custom-style="min-height: 100vh" theme="dark">
|
||||
<wd-navbar
|
||||
:title="activeTabbar.title"
|
||||
safe-area-inset-top
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { createSSRApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import setupPlugins from "@/plugins";
|
||||
|
||||
import "uno.css";
|
||||
import "@/styles/global.scss";
|
||||
@@ -11,7 +10,6 @@ export function createApp() {
|
||||
const app = createSSRApp(App);
|
||||
|
||||
setupStore(app);
|
||||
app.use(setupPlugins);
|
||||
return {
|
||||
app,
|
||||
};
|
||||
|
||||
@@ -2,64 +2,97 @@
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"type": "home"
|
||||
"type": "home",
|
||||
"style": {},
|
||||
"layout": "tabbar"
|
||||
},
|
||||
{
|
||||
"path": "pages/login/complete-profile",
|
||||
"type": "page"
|
||||
"type": "page",
|
||||
"style": {}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/index",
|
||||
"type": "page"
|
||||
"type": "page",
|
||||
"style": {}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/index",
|
||||
"type": "page"
|
||||
"type": "page",
|
||||
"style": {},
|
||||
"layout": "tabbar"
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/about/index",
|
||||
"type": "page"
|
||||
"type": "page",
|
||||
"style": {}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/faq/index",
|
||||
"type": "page"
|
||||
"type": "page",
|
||||
"style": {}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/feedback/index",
|
||||
"type": "page"
|
||||
"type": "page",
|
||||
"style": {}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/profile/index",
|
||||
"type": "page"
|
||||
"type": "page",
|
||||
"style": {}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/settings/index",
|
||||
"type": "page"
|
||||
"type": "page",
|
||||
"style": {}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/settings/account/index",
|
||||
"type": "page"
|
||||
"type": "page",
|
||||
"style": {}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/settings/agreement/index",
|
||||
"type": "page"
|
||||
"type": "page",
|
||||
"style": {}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/settings/network/index",
|
||||
"type": "page"
|
||||
"type": "page",
|
||||
"style": {}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/settings/privacy/index",
|
||||
"type": "page"
|
||||
"type": "page",
|
||||
"style": {}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/settings/theme/index",
|
||||
"type": "page"
|
||||
"type": "page",
|
||||
"style": {}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "@uni-helper"
|
||||
"navigationBarTitleText": "vue-uniapp-template"
|
||||
},
|
||||
"tabBar": {
|
||||
"custom": true,
|
||||
"height": "0px",
|
||||
"color": "#00000000",
|
||||
"selectedColor": "#00000000",
|
||||
"backgroundColor": "#00000000",
|
||||
"list": [
|
||||
{
|
||||
"text": "首页",
|
||||
"pagePath": "pages/index/index"
|
||||
},
|
||||
{
|
||||
"text": "我的",
|
||||
"pagePath": "pages/mine/index"
|
||||
}
|
||||
]
|
||||
},
|
||||
"subPackages": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,17 +92,25 @@
|
||||
<script setup lang="ts">
|
||||
import { dayjs } from "wot-design-uni";
|
||||
|
||||
import LogAPI, { VisitStatsVO } from "@/api/system/log";
|
||||
// 定义访问统计数据类型
|
||||
interface VisitStatsVO {
|
||||
todayUvCount: number;
|
||||
uvGrowthRate: number;
|
||||
totalUvCount: number;
|
||||
todayPvCount: number;
|
||||
pvGrowthRate: number;
|
||||
totalPvCount: number;
|
||||
}
|
||||
|
||||
const current = ref<number>(0);
|
||||
|
||||
const visitStatsData = ref<VisitStatsVO>({
|
||||
todayUvCount: 0,
|
||||
uvGrowthRate: 0,
|
||||
totalUvCount: 0,
|
||||
todayPvCount: 0,
|
||||
pvGrowthRate: 0,
|
||||
totalPvCount: 0,
|
||||
todayUvCount: 1234,
|
||||
uvGrowthRate: 15.6,
|
||||
totalUvCount: 45678,
|
||||
todayPvCount: 5678,
|
||||
pvGrowthRate: 23.4,
|
||||
totalPvCount: 123456,
|
||||
});
|
||||
|
||||
// 图表数据
|
||||
@@ -163,6 +171,31 @@ const navList = reactive([
|
||||
},
|
||||
]);
|
||||
|
||||
// 生成静态的访问趋势数据
|
||||
const generateStaticTrendData = (days: number) => {
|
||||
const dates = [];
|
||||
const ipList = [];
|
||||
const pvList = [];
|
||||
|
||||
const today = new Date();
|
||||
|
||||
for (let i = days - 1; i >= 0; i--) {
|
||||
const date = new Date(today);
|
||||
date.setDate(today.getDate() - i);
|
||||
dates.push(dayjs(date).format("MM-DD"));
|
||||
|
||||
// 生成模拟数据
|
||||
ipList.push(Math.floor(Math.random() * 500) + 200);
|
||||
pvList.push(Math.floor(Math.random() * 1000) + 500);
|
||||
}
|
||||
|
||||
return {
|
||||
dates,
|
||||
ipList,
|
||||
pvList,
|
||||
};
|
||||
};
|
||||
|
||||
function handleClick(e: any) {
|
||||
console.log(e);
|
||||
}
|
||||
@@ -170,25 +203,27 @@ function onChange(e: any) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
// 加载访问统计数据
|
||||
// 加载访问统计数据(使用静态数据)
|
||||
const loadVisitStatsData = async () => {
|
||||
LogAPI.getVisitStats().then((data) => {
|
||||
visitStatsData.value = data;
|
||||
});
|
||||
// 模拟异步加载
|
||||
setTimeout(() => {
|
||||
visitStatsData.value = {
|
||||
todayUvCount: 1234,
|
||||
uvGrowthRate: 15.6,
|
||||
totalUvCount: 45678,
|
||||
todayPvCount: 5678,
|
||||
pvGrowthRate: 23.4,
|
||||
totalPvCount: 123456,
|
||||
};
|
||||
}, 100);
|
||||
};
|
||||
|
||||
// 加载访问趋势数据
|
||||
// 加载访问趋势数据(使用静态数据)
|
||||
const loadVisitTrendData = () => {
|
||||
const endDate = new Date();
|
||||
const startDate = new Date(endDate);
|
||||
startDate.setDate(endDate.getDate() - recentDaysRange.value + 1);
|
||||
// 模拟异步加载
|
||||
setTimeout(() => {
|
||||
const data = generateStaticTrendData(recentDaysRange.value);
|
||||
|
||||
const visitTrendQuery = {
|
||||
startDate: dayjs(startDate).format("YYYY-MM-DD"),
|
||||
endDate: dayjs(endDate).format("YYYY-MM-DD"),
|
||||
};
|
||||
|
||||
LogAPI.getVisitTrend(visitTrendQuery).then((data) => {
|
||||
const res = {
|
||||
categories: data.dates,
|
||||
series: [
|
||||
@@ -203,7 +238,7 @@ const loadVisitTrendData = () => {
|
||||
],
|
||||
};
|
||||
chartData.value = JSON.parse(JSON.stringify(res));
|
||||
});
|
||||
}, 100);
|
||||
};
|
||||
|
||||
// 数据范围变化
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
import { ref, reactive, computed } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { useToast } from "wot-design-uni";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { useUserStore } from "@/store/modules/user.store";
|
||||
import UserAPI, { type UserProfileForm } from "@/api/user";
|
||||
import FileAPI, { type FileInfo } from "@/api/file";
|
||||
import WechatProfile from "@/components/WechatProfile.vue";
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { type LoginFormData } from "@/api/auth";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { useUserStore } from "@/store/modules/user.store";
|
||||
import { useToast } from "wot-design-uni";
|
||||
import { ref } from "vue";
|
||||
|
||||
|
||||
@@ -153,8 +153,8 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useToast } from "wot-design-uni";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { useThemeStore } from "@/store/modules/theme";
|
||||
import { useUserStore } from "@/store/modules/user.store";
|
||||
import { useThemeStore } from "@/store/modules/theme.store";
|
||||
import { computed } from "vue";
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { useUserStore } from "@/store/modules/user.store";
|
||||
import { checkLogin } from "@/utils/auth";
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
@@ -193,8 +193,6 @@ const handleLogout = () => {
|
||||
|
||||
// 检查登录状态
|
||||
onLoad(() => {
|
||||
if (!checkLogin()) return;
|
||||
|
||||
getCacheSize();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -114,12 +114,12 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||||
import { useTheme } from "@/composables/theme/theme";
|
||||
import { useThemeStore } from "@/store/modules/theme";
|
||||
import { useTheme } from "@/composables/useTheme";
|
||||
import { useThemeStore } from "@/store/modules/theme.store";
|
||||
import { applyThemeOnPageShow } from "@/utils/theme";
|
||||
import { ref, computed, watch, onMounted } from "vue";
|
||||
|
||||
const { toggleTheme, themeVars, theme, setThemeColor } = useTheme();
|
||||
const { toggleTheme, themeVars, themeState, setThemeColor } = useTheme();
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
// 暗黑模式状态
|
||||
@@ -151,7 +151,7 @@ const currentThemeColor = computed(() => {
|
||||
});
|
||||
|
||||
// 监听暗黑模式变化
|
||||
watch(theme, (newTheme) => {
|
||||
watch(themeState, (newTheme) => {
|
||||
isDark.value = newTheme === "dark";
|
||||
});
|
||||
|
||||
@@ -240,8 +240,8 @@ const resetTheme = () => {
|
||||
customColor.value = defaultColor;
|
||||
|
||||
// 重置为浅色模式
|
||||
if (theme.value === "dark") {
|
||||
toggleTheme("light");
|
||||
if (themeState.value === "dark") {
|
||||
toggleTheme();
|
||||
}
|
||||
|
||||
uni.showToast({
|
||||
@@ -261,7 +261,7 @@ onLoad(() => {
|
||||
|
||||
onMounted(() => {
|
||||
// 初始化暗黑模式状态
|
||||
isDark.value = theme.value === "dark";
|
||||
isDark.value = themeState.value === "dark";
|
||||
customColor.value = currentThemeColor.value;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { App } from "vue";
|
||||
|
||||
import { setupDirective } from "@/directive";
|
||||
export default {
|
||||
install(app: App<Element>) {
|
||||
// 自定义指令(directive)
|
||||
setupDirective(app);
|
||||
},
|
||||
};
|
||||
@@ -8,6 +8,6 @@ export function setupStore(app: App<Element>) {
|
||||
app.use(store);
|
||||
}
|
||||
|
||||
export * from "./modules/user";
|
||||
export * from "./modules/theme";
|
||||
export * from "./modules/user.store";
|
||||
export * from "./modules/theme.store";
|
||||
export { store };
|
||||
|
||||
@@ -1,7 +1,24 @@
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { useUserStore } from "@/store/modules/user.store";
|
||||
import { Storage } from "./storage";
|
||||
import { ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY } from "@/constants";
|
||||
|
||||
/**
|
||||
* 认证工具函数
|
||||
*
|
||||
* 使用示例:
|
||||
*
|
||||
* 1. 检查登录状态并自动跳转:
|
||||
* if (!checkLogin()) return; // 未登录会自动跳转到登录页
|
||||
*
|
||||
* 2. 静默检查登录状态:
|
||||
* if (!isLoggedIn()) {
|
||||
* // 处理未登录逻辑,不会自动跳转
|
||||
* }
|
||||
*
|
||||
* 3. 强制要求登录:
|
||||
* requireLogin(); // 清除无效状态并跳转到登录页
|
||||
*/
|
||||
|
||||
/**
|
||||
* 获取访问令牌
|
||||
* @returns 返回访问令牌,如果不存在则返回null
|
||||
@@ -44,21 +61,82 @@ export function clearTokens(): void {
|
||||
|
||||
/**
|
||||
* 检查用户登录状态,未登录则跳转到登录页面
|
||||
* @param silent 是否静默检查,不跳转登录页面
|
||||
* @returns 返回用户是否已登录
|
||||
*/
|
||||
export function checkLogin(): boolean {
|
||||
export function checkLogin(silent: boolean = false): boolean {
|
||||
const userStore = useUserStore();
|
||||
const accessToken = getAccessToken();
|
||||
|
||||
if (!userStore.userInfo) {
|
||||
const pages = getCurrentPages();
|
||||
const currentPage = pages[pages.length - 1];
|
||||
const currentPagePath = `/${currentPage.route}`;
|
||||
// 检查 token 和用户信息是否都存在
|
||||
const isLoggedIn = !!(accessToken && userStore.userInfo);
|
||||
|
||||
uni.navigateTo({
|
||||
url: `/pages/login/index?redirect=${encodeURIComponent(currentPagePath)}`,
|
||||
});
|
||||
return false;
|
||||
if (!isLoggedIn && !silent) {
|
||||
try {
|
||||
// 获取当前页面路径
|
||||
let currentPagePath = "/pages/index/index"; // 默认路径
|
||||
|
||||
const pages = getCurrentPages();
|
||||
if (pages && pages.length > 0) {
|
||||
const currentPage = pages[pages.length - 1];
|
||||
if (currentPage && currentPage.route) {
|
||||
currentPagePath = `/${currentPage.route}`;
|
||||
|
||||
// 处理页面参数 - 使用类型断言
|
||||
const pageOptions = (currentPage as any).options;
|
||||
if (pageOptions && Object.keys(pageOptions).length > 0) {
|
||||
const params = new URLSearchParams(pageOptions as Record<string, string>);
|
||||
currentPagePath += `?${params.toString()}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转到登录页面
|
||||
uni.navigateTo({
|
||||
url: `/pages/login/index?redirect=${encodeURIComponent(currentPagePath)}`,
|
||||
fail: (error) => {
|
||||
console.error("跳转登录页面失败:", error);
|
||||
// 如果 navigateTo 失败,尝试使用 reLaunch
|
||||
uni.reLaunch({
|
||||
url: "/pages/login/index",
|
||||
});
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("检查登录状态时发生错误:", error);
|
||||
// 发生错误时,尝试直接跳转到登录页
|
||||
uni.reLaunch({
|
||||
url: "/pages/login/index",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return isLoggedIn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否已登录(静默检查,不跳转)
|
||||
* @returns 返回用户是否已登录
|
||||
*/
|
||||
export function isLoggedIn(): boolean {
|
||||
return checkLogin(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制用户登录,清除无效的登录状态
|
||||
*/
|
||||
export function requireLogin(): void {
|
||||
const userStore = useUserStore();
|
||||
const accessToken = getAccessToken();
|
||||
|
||||
if (!accessToken || !userStore.userInfo) {
|
||||
// 清除可能存在的无效状态
|
||||
clearTokens();
|
||||
userStore.logout();
|
||||
|
||||
// 跳转到登录页
|
||||
uni.reLaunch({
|
||||
url: "/pages/login/index",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getAccessToken, clearTokens } from "@/utils/auth";
|
||||
import { ResultCodeEnum } from "@/enums/ResultCodeEnum";
|
||||
import { ApiCode } from "@/enums/api-code.enum";
|
||||
|
||||
export default function request<T>(options: UniApp.RequestOptions): Promise<T> {
|
||||
// H5 使用 VITE_APP_BASE_API 作为代理路径,其他平台使用 VITE_APP_API_URL 作为请求路径
|
||||
@@ -21,11 +21,11 @@ export default function request<T>(options: UniApp.RequestOptions): Promise<T> {
|
||||
const resData = response.data as ResponseData<T>;
|
||||
|
||||
// 业务状态码 00000 表示成功
|
||||
if (resData.code === ResultCodeEnum.SUCCESS) {
|
||||
if (resData.code === ApiCode.SUCCESS) {
|
||||
resolve(resData.data);
|
||||
}
|
||||
// 令牌失效或过期处理
|
||||
else if (resData.code === ResultCodeEnum.TOKEN_INVALID) {
|
||||
else if (resData.code === ApiCode.TOKEN_INVALID) {
|
||||
console.log("令牌失效或过期处理");
|
||||
clearTokens();
|
||||
// 跳转到登录页
|
||||
|
||||
2
uni-pages.d.ts
vendored
2
uni-pages.d.ts
vendored
@@ -22,7 +22,7 @@ interface NavigateToOptions {
|
||||
interface RedirectToOptions extends NavigateToOptions {}
|
||||
|
||||
interface SwitchTabOptions {
|
||||
|
||||
url: "/pages/index/index" | "/pages/mine/index"
|
||||
}
|
||||
|
||||
type ReLaunchOptions = NavigateToOptions | SwitchTabOptions;
|
||||
|
||||
@@ -5,6 +5,9 @@ import UniLayouts from "@uni-helper/vite-plugin-uni-layouts";
|
||||
import UniPages from "@uni-helper/vite-plugin-uni-pages";
|
||||
import { uniuseAutoImports } from "@uni-helper/uni-use";
|
||||
|
||||
import Components from "@uni-helper/vite-plugin-uni-components";
|
||||
import { WotResolver } from "@uni-helper/vite-plugin-uni-components/resolvers";
|
||||
|
||||
export default defineConfig(async ({ mode }: ConfigEnv): Promise<UserConfig> => {
|
||||
const UnoCss = await import("unocss/vite").then((i) => i.default);
|
||||
const env = loadEnv(mode, process.cwd());
|
||||
@@ -28,13 +31,19 @@ export default defineConfig(async ({ mode }: ConfigEnv): Promise<UserConfig> =>
|
||||
cssTarget: "chrome61",
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ["wot-design-uni"],
|
||||
exclude: ["vue-demi"],
|
||||
},
|
||||
plugins: [
|
||||
// 在 uni() 之前使用
|
||||
// make sure put it before `Uni()`
|
||||
UnoCss(),
|
||||
UniLayouts(),
|
||||
UniPages(),
|
||||
|
||||
Components({
|
||||
resolvers: [WotResolver()],
|
||||
}),
|
||||
|
||||
AutoImport({
|
||||
imports: ["vue", "uni-app", uniuseAutoImports()],
|
||||
dts: "src/types/auto-imports.d.ts", // 自动生成的类型声明文件
|
||||
|
||||
Reference in New Issue
Block a user