refactor(mine): 恢复个人中心页面优化
- 修复暗黑模式图标颜色问题 - 优化页面布局,新增系统设置入口 - 重构异步代码为 async/await 模式
This commit is contained in:
@@ -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
3
components.d.ts
vendored
@@ -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']
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"skills": {
|
||||
"wot-ui": {
|
||||
"source": "wot-ui/wot-starter",
|
||||
"sourceType": "github",
|
||||
"computedHash": "b4f8b0577871b28dd12278f26961d015e378d7d4f11435599631f1ee6860c8eb"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 边框颜色
|
||||
|
||||
Reference in New Issue
Block a user