refactor(mine): 恢复个人中心页面优化

- 修复暗黑模式图标颜色问题
- 优化页面布局,新增系统设置入口
- 重构异步代码为 async/await 模式
This commit is contained in:
Ray.Hao
2026-05-15 23:27:56 +08:00
parent c9fbc441bd
commit 1bfe7964b3
6 changed files with 63 additions and 331 deletions

View File

@@ -1,274 +0,0 @@
---
alwaysApply: true
---
# CSS样式与UnoCSS使用规范
## 样式选择原则
### UnoCSS原子类使用规则
- **≤5个原子类**: 直接使用UnoCSS原子类
- **>5个原子类**: 必须提取为语义化CSS类避免模板膨胀
```vue
<!-- ✅ 好的做法5个以内原子类 -->
<view class="flex items-center p-20rpx text-center bg-white"></view>
```
### 优先使用UnoCSS预设shortcuts
参考 [unocss.config.ts](mdc:unocss.config.ts) 中定义的shortcuts
```typescript
// 已定义的shortcuts优先使用
"flex-center": "flex justify-center items-center"
"flex-start": "flex justify-start items-center"
"flex-between": "flex justify-between items-center"
"flex-col-center": "flex flex-col items-center"
```
## CSS类命名规范
### BEM命名规范
严格遵循BEM (Block\_\_Element--Modifier) 命名规范:
```scss
// Block: 独立的功能组件
.user-profile {
}
// Element: Block的子元素
.user-profile__avatar {
}
.user-profile__action {
}
.user-profile__badge {
}
// Modifier: Block或Element的状态/变体
.user-profile--dark {
}
.user-profile__action--active {
}
```
### 命名要求
1. **使用简短、易理解的英文单词**
2. **符合业务场景语义**
3. **避免缩写,除非是通用缩写**
```scss
// ✅ 好的命名
.user-profile__action
.stats-card
.service-icon
.tool-item
// ❌ 坏的命名
.up-act
.sc
.si
.ti
```
## 样式组织原则
### 1. 复用性判断
- 同一组合在项目中出现 >3次 → 提取为语义化类
- 具备明确语义的区域 → 提取为语义化类
- 需要主题适配的样式 → 提取为语义化类
### 2. 常用组合添加到shortcuts
当发现重复的原子类组合时,添加到 [unocss.config.ts](mdc:unocss.config.ts)
```typescript
shortcuts: [
{
// 现有shortcuts...
// 新增常用组合
"card-container": "mx-30rpx my-20rpx bg-white rounded-16rpx shadow-sm",
"action-button": "flex-center w-70rpx h-70rpx bg-white bg-opacity-90 rounded-full",
},
];
```
### 3. 主题适配
使用wot-design-uni的CSS变量或自定义变量
```scss
// ✅ 使用主题变量
.service-icon {
background-color: var(--wot-color-bg-light, #f3f4f6);
}
// ✅ 响应式主题色
:color="themeStore.isDark ? '#fff' : '#333'";
```
### 4. 暗黑模式适配规则
#### 优先级顺序
1. **优先使用wot-design-uni内置变量** - 自动适配暗黑模式
2. **使用项目 [theme.scss](mdc:src/styles/theme.scss) 定义的变量** - 统一管理主题色
3. **最后考虑组件内部定义** - 仅用于特殊场景
#### 自定义主题变量定义
当需要自定义颜色时,必须在 [theme.scss](mdc:src/styles/theme.scss) 中定义:
```scss
/* 在 theme.scss 中添加自定义变量 */
:root,
page {
--custom-text-color: #333333;
--custom-bg-color: #ffffff;
--custom-border-color: #e5e7eb;
}
.wot-theme-dark {
--custom-text-color: #ffffff;
--custom-bg-color: #1a1a1a;
--custom-border-color: #374151;
}
```
#### 组件中使用主题变量
```scss
// ✅ 推荐使用wot内置变量
.my-component {
background-color: var(--wot-color-bg);
color: var(--wot-color-text);
}
// ✅ 推荐使用theme.scss定义的变量
.my-component {
background-color: var(--custom-bg-color);
border-color: var(--custom-border-color);
}
// ❌ 避免:组件内部硬编码暗黑模式
.my-component {
background-color: #ffffff;
}
.wot-theme-dark .my-component {
background-color: #1a1a1a;
}
```
#### 图标和文字颜色适配
```vue
<template>
<!-- ✅ 推荐:使用计算属性适配 -->
<wd-icon :color="iconColor" />
<!-- ✅ 推荐使用CSS变量 -->
<text style="color: var(--wot-color-text)">文本</text>
</template>
<script setup>
// 图标颜色适配
const iconColor = computed(() => (themeStore.isDark ? "#ffffff" : "#333333"));
</script>
```
## 实践示例
### 数据统计卡片
```vue
<!-- 使用wot组件 + 少量原子类 -->
<wd-card custom-class="mx-30rpx my-20rpx">
<wd-grid :column="3" border>
<wd-grid-item class="flex-col-center py-20rpx">
<view class="text-36rpx font-600">0.00</view>
<view class="text-26rpx text-gray-500">我的余额</view>
</wd-grid-item>
</wd-grid>
</wd-card>
```
### 用户操作按钮
```vue
<template>
<view class="user-profile__action" @click="handleClick">
<wd-icon name="setting1" :color="iconColor" />
</view>
</template>
<style scoped>
.user-profile__action {
display: flex;
align-items: center;
justify-content: center;
width: 70rpx;
height: 70rpx;
background-color: rgba(255, 255, 255, 0.9);
border-radius: 50%;
}
</style>
```
## 禁止项
❌ **不要在SCSS中重复定义UnoCSS已提供的工具类**
❌ **不要使用 `@apply` 指令**
❌ **不要把一次性样式放到全局**
❌ **不要使用非语义化的类名**
❌ **不要在模板中堆叠超过5个原子类**
❌ **不要混用UnoCSS原子类和自定义CSS类** - 一个元素应该只使用原子类或只使用自定义类,避免样式管理混乱
### 混用问题示例
```vue
<!-- ❌ 错误:混用原子类和自定义类 -->
<view class="flex-col-center py-20rpx tool-item">内容</view>
<wd-card custom-class="mx-30rpx my-20rpx stats-card">内容</wd-card>
<!-- ✅ 正确只使用原子类≤5个 -->
<view class="flex-col-center py-20rpx">内容</view>
<!-- ✅ 正确:只使用自定义类 -->
<view class="tool-item">内容</view>
<wd-card custom-class="stats-card">内容</wd-card>
```
### 解决方案
当需要混用时,选择以下方案之一:
1. **原子类数量≤5个**:移除自定义类,只使用原子类
2. **原子类数量>5个**:将所有样式合并到自定义类中
3. **语义明确的区域**:优先使用自定义类,将原子类样式合并进去
## 组件库样式覆盖
使用wot-design-uni组件时
- 使用 `custom-class` 传入类名
- 使用 `:deep()` 进行样式穿透
- 添加 `!important` 提升优先级
```vue
<wd-button custom-class="logout-button" @click="logout">
退出登录
</wd-button>
<style scoped>
:deep(.logout-button) {
width: 100% !important;
height: 80rpx !important;
border-radius: 40rpx !important;
}
</style>
```

3
components.d.ts vendored
View File

@@ -10,6 +10,7 @@ declare module 'vue' {
CuDateQuery: typeof import('./src/components/cu-date-query/index.vue')['default']
CustomNavbar: typeof import('./src/components/custom-navbar/index.vue')['default']
CustomTree: typeof import('./src/components/custom-tree/index.vue')['default']
EmptyState: typeof import('./src/components/empty-state/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']
@@ -23,6 +24,7 @@ declare module 'vue' {
WdButton: typeof import('wot-design-uni/components/wd-button/wd-button.vue')['default']
WdCalendar: typeof import('wot-design-uni/components/wd-calendar/wd-calendar.vue')['default']
WdCard: typeof import('wot-design-uni/components/wd-card/wd-card.vue')['default']
WdCascader: typeof import('wot-design-uni/components/wd-cascader/wd-cascader.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']
WdCheckbox: typeof import('wot-design-uni/components/wd-checkbox/wd-checkbox.vue')['default']
@@ -33,6 +35,7 @@ declare module 'vue' {
WdDivider: typeof import('wot-design-uni/components/wd-divider/wd-divider.vue')['default']
WdDropMenu: typeof import('wot-design-uni/components/wd-drop-menu/wd-drop-menu.vue')['default']
WdDropMenuItem: typeof import('wot-design-uni/components/wd-drop-menu-item/wd-drop-menu-item.vue')['default']
WdEmpty: typeof import('wot-design-uni/components/wd-empty/wd-empty.vue')['default']
WdFab: typeof import('wot-design-uni/components/wd-fab/wd-fab.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']

View File

@@ -1,10 +0,0 @@
{
"version": 1,
"skills": {
"wot-ui": {
"source": "wot-ui/wot-starter",
"sourceType": "github",
"computedHash": "b4f8b0577871b28dd12278f26961d015e378d7d4f11435599631f1ee6860c8eb"
}
}
}

View File

@@ -273,58 +273,56 @@ const handleOpenDialog = (type: DialogType) => {
* @param contactType 联系方式类型 MOBILE: 手机号码 EMAIL: 邮箱
*/
const handleSendVerificationCode = async (contactType: string) => {
if (contactType === "MOBILE") {
mobileBindingFormRef.value.validate("mobile").then(({ valid }: { valid: boolean }) => {
try {
if (contactType === "MOBILE") {
const { valid } = await mobileBindingFormRef.value.validate("mobile");
if (valid) {
UserAPI.sendVerificationCode(mobileBindingForm.mobile!, "MOBILE").then(() => {
uni.showToast({ title: "验证码已发送", icon: "none" });
startMobileCountdown();
});
await UserAPI.sendVerificationCode(mobileBindingForm.mobile!, "MOBILE");
uni.showToast({ title: "验证码已发送", icon: "none" });
startMobileCountdown();
}
});
} else if (contactType === "EMAIL") {
emailBindingFormRef.value.validate("email").then(({ valid }: { valid: boolean }) => {
} else if (contactType === "EMAIL") {
const { valid } = await emailBindingFormRef.value.validate("email");
if (valid) {
UserAPI.sendVerificationCode(emailBindingForm.email!, "EMAIL").then(() => {
uni.showToast({ title: "验证码已发送", icon: "none" });
startEmailCountdown();
});
await UserAPI.sendVerificationCode(emailBindingForm.email!, "EMAIL");
uni.showToast({ title: "验证码已发送", icon: "none" });
startEmailCountdown();
}
});
}
} catch (error) {
console.error("发送验证码失败:", error);
}
};
// 提交表单
function handleSubmit() {
if (dialog.type === DialogType.PASSWORD) {
passwordChangeFormRef.value.validate().then(({ valid }: { valid: boolean }) => {
async function handleSubmit() {
try {
if (dialog.type === DialogType.PASSWORD) {
const { valid } = await passwordChangeFormRef.value.validate();
if (valid) {
UserAPI.changePassword(passwordChangeForm).then(() => {
uni.showToast({ title: "密码修改成功", icon: "none" });
dialog.visible = false;
});
await UserAPI.changePassword(passwordChangeForm);
uni.showToast({ title: "密码修改成功", icon: "none" });
dialog.visible = false;
}
});
} else if (dialog.type === DialogType.MOBILE) {
mobileBindingFormRef.value.validate().then(({ valid }: { valid: boolean }) => {
} else if (dialog.type === DialogType.MOBILE) {
const { valid } = await mobileBindingFormRef.value.validate();
if (valid) {
UserAPI.bindMobile(mobileBindingForm).then(() => {
uni.showToast({ title: "手机号绑定成功", icon: "none" });
dialog.visible = false;
loadUserProfile();
});
await UserAPI.bindMobile(mobileBindingForm);
uni.showToast({ title: "手机号绑定成功", icon: "none" });
dialog.visible = false;
loadUserProfile();
}
});
} else if (dialog.type === DialogType.EMAIL) {
emailBindingFormRef.value.validate().then(({ valid }: { valid: boolean }) => {
} else if (dialog.type === DialogType.EMAIL) {
const { valid } = await emailBindingFormRef.value.validate();
if (valid) {
UserAPI.bindEmail(emailBindingForm).then(() => {
uni.showToast({ title: "邮箱绑定成功", icon: "none" });
dialog.visible = false;
loadUserProfile();
});
await UserAPI.bindEmail(emailBindingForm);
uni.showToast({ title: "邮箱绑定成功", icon: "none" });
dialog.visible = false;
loadUserProfile();
}
});
}
} catch (error) {
console.error("提交表单失败:", error);
}
}

View File

@@ -159,9 +159,19 @@
<view class="section-card">
<text class="section-title">帮助与支持</text>
<view class="menu-list menu-list--flat">
<view class="menu-row" @click="openSettings">
<view class="menu-row__icon menu-row__icon--primary">
<wd-icon name="setting" size="18" :color="`rgba(77, 128, 240, 0.12)`" />
</view>
<view class="menu-row__main">
<text class="menu-row__title">系统设置</text>
<text class="menu-row__desc">主题语言通知等设置</text>
</view>
<wd-icon name="arrow-right" size="16" color="var(--color-text-placeholder)" />
</view>
<view class="menu-row" @click="openUserAgreement">
<view class="menu-row__icon menu-row__icon--success">
<wd-icon name="secured" size="18" color="var(--color-success-dark)" />
<wd-icon name="secured" size="18" :color="`rgba(52, 209, 157, 0.12)`" />
</view>
<view class="menu-row__main">
<text class="menu-row__title">用户协议</text>
@@ -171,7 +181,7 @@
</view>
<view class="menu-row" @click="openAbout">
<view class="menu-row__icon menu-row__icon--teal">
<wd-icon name="info-circle" size="18" color="var(--color-success-dark)" />
<wd-icon name="info-circle" size="18" :color="`rgba(52, 209, 157, 0.12)`" />
</view>
<view class="menu-row__main">
<text class="menu-row__title">关于系统</text>
@@ -182,10 +192,6 @@
</view>
</view>
</view>
<view v-if="isLogin" class="logout-section">
<wd-button custom-class="logout-btn" plain @click="handleLogout">退出登录</wd-button>
</view>
</view>
<wd-loading
@@ -332,6 +338,11 @@ const openThemeSettings = () => {
router.push({ path: "/pages/mine/settings/theme/index" });
};
// 系统设置
const openSettings = () => {
router.push({ path: "/pages/mine/settings/index" });
};
// 用户协议
const openUserAgreement = () => {
router.push({ path: "/pages/mine/settings/agreement/index" });
@@ -844,6 +855,10 @@ const openOfficialAccount = () => {
height: 72rpx;
border-radius: 22rpx;
&--primary {
background: rgba(77, 128, 240, 0.12);
}
&--warning {
background: var(--color-warning-light);
}
@@ -853,11 +868,11 @@ const openOfficialAccount = () => {
}
&--success {
background: var(--color-success-light);
background: rgba(52, 209, 157, 0.12);
}
&--teal {
background: var(--color-success-light);
background: rgba(52, 209, 157, 0.12);
}
}

View File

@@ -146,7 +146,7 @@ page {
--color-text-secondary: #9ca3af;
--color-text-placeholder: #6b7280;
--color-text-disabled: #4b5563;
--color-text-inverse: #1f2937;
--color-text-inverse: #ffffff;
// -----------------------------------------------------------------------
// 边框颜色