feat: 优化工作台、个人中心、消息页面

- 重构工作台用户头部和菜单区域组件
- 优化个人中心页面布局
- 更新消息页面

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
lzh
2026-02-28 14:30:24 +08:00
parent e61b5a2673
commit ae9075c43f
5 changed files with 552 additions and 181 deletions

View File

@@ -1,79 +1,142 @@
<template>
<scroll-view class="min-h-0 flex-1" scroll-y scroll-with-animation>
<!-- 常用分组 -->
<view class="mx-20rpx mt-20rpx overflow-hidden rounded-16rpx bg-white">
<view class="flex items-center justify-between px-24rpx py-20rpx">
<text class="text-28rpx text-#333 font-500">常用</text>
<view class="p-10rpx" @click="handleGotoFavoriteSettings">
<wd-icon name="setting" size="32rpx" color="#999" />
<!-- 常用应用 -->
<view class="mx-32rpx mt-24rpx">
<view class="mb-24rpx flex items-center">
<view class="mr-8rpx h-32rpx w-6rpx rounded-4rpx bg-[#F97316]" />
<text class="text-28rpx text-[#1F2937] font-bold">常用应用</text>
</view>
<view class="ai-card p-32rpx">
<view class="grid grid-cols-3 gap-y-40rpx">
<view
v-for="app in quickApps"
:key="app.key"
class="flex flex-col items-center"
@click="handleQuickApp(app)"
>
<view
class="app-icon-wrap"
:style="{ background: app.bgLight }"
>
<view :class="app.icon" class="text-44rpx" :style="{ color: app.color }" />
</view>
<text class="mt-16rpx text-24rpx text-[#1F2937] font-600">{{ app.name }}</text>
</view>
</view>
</view>
<MenuGrid v-if="favoriteMenuItems.length > 0" :menus="favoriteMenuItems" />
<view
v-else
class="mx-24rpx mb-24rpx flex items-center border-1rpx border-#ddd rounded-12rpx border-dashed px-24rpx py-12rpx"
@click="handleGotoFavoriteSettings"
>
<wd-icon name="add" size="32rpx" color="#999" />
<text class="pl-10rpx text-28rpx text-#999">添加我常用的</text>
</view>
<!-- 区域预警暂时隐藏 -->
<!--
<view class="mx-32rpx mt-48rpx">
<view class="mb-16rpx flex items-center">
<view class="mr-8rpx h-32rpx w-6rpx rounded-4rpx bg-[#F97316]" />
<text class="text-28rpx text-[#1F2937] font-bold">区域预警</text>
</view>
<view class="ai-card p-32rpx">
<view v-for="(warning, index) in areaWarnings" :key="index" class="warning-item">
<view class="mb-12rpx flex items-center justify-between">
<view class="flex items-center">
<text class="text-28rpx text-[#1F2937] font-600">{{ warning.area }}</text>
<view
class="ml-12rpx rounded-999px px-16rpx py-4rpx text-22rpx font-bold"
:class="warning.level === '高' ? 'ai-badge&#45;&#45;red' : warning.level === '中' ? 'ai-badge&#45;&#45;orange' : 'ai-badge&#45;&#45;green'"
>
{{ warning.level }}风险
</view>
</view>
</view>
</view>
</view>
</view>
-->
<!-- 工作流程 -->
<view v-for="group in menuGroups" :key="group.key" class="mx-32rpx mt-32rpx">
<view class="mb-16rpx flex items-center justify-between" @click="toggleGroup(group.key)">
<view class="flex items-center">
<view class="mr-8rpx h-32rpx w-6rpx rounded-4rpx bg-[#F97316]" />
<text class="text-28rpx text-[#1F2937] font-bold">{{ group.name }}</text>
</view>
<view
class="i-carbon-chevron-down text-16px text-[#9CA3AF] transition-transform"
:class="{ 'rotate-180': expandedGroups.includes(group.key) }"
/>
</view>
<view v-show="expandedGroups.includes(group.key)" class="ai-card overflow-hidden">
<MenuGrid :menus="group.menus" />
</view>
</view>
<!-- 菜单分组 -->
<view v-for="group in menuGroups" :key="group.key" class="mx-20rpx mt-20rpx overflow-hidden rounded-16rpx bg-white">
<view class="px-24rpx pb-0 pt-20rpx">
<text class="text-28rpx text-#333 font-500">{{ group.name }}</text>
</view>
<MenuGrid :menus="group.menus" />
</view>
<!-- 底部安全区域 -->
<view class="h-40rpx" />
<!-- 底部留白 tabbar 腾出空间 -->
<view class="pb-safe" style="height: 140rpx;" />
</scroll-view>
</template>
<script lang="ts" setup>
import type { MenuGroup, MenuItem } from '../index'
import { useUserStore } from '@/store/user'
import { getMenuGroups, getMenuItemByKey } from '../index'
import type { MenuGroup } from '../index'
import { useToast } from 'wot-design-uni'
import { getMenuGroups } from '../index'
import MenuGrid from './menu-grid.vue'
defineOptions({
name: 'MenuSection',
})
const userStore = useUserStore()
const toast = useToast()
/** 菜单分组列表 */
/** 菜单分组列表(仅保留工作流程) */
const menuGroups = ref<MenuGroup[]>([])
/** 常用服务菜单(从 store 中计算得出) */
const favoriteMenuItems = computed<MenuItem[]>(() => {
const keys = userStore.favoriteMenus
if (!keys || keys.length === 0) {
return []
/** 展开的分组 */
const expandedGroups = ref<string[]>([])
/** 快速应用 — 重新设计图标与配色 */
const quickApps = [
{ key: 'workOrder', name: '工单池', icon: 'i-carbon-task-complete', color: '#F97316', bgLight: '#FFF7ED', url: '/pages/scan/work-order/index' },
{ key: 'staff', name: '员工管理', icon: 'i-carbon-events', color: '#3B82F6', bgLight: '#EFF6FF', url: '/pages/scan/staff/index' },
{ key: 'inspection', name: '扫码巡检', icon: 'i-carbon-qr-code', color: '#8B5CF6', bgLight: '#F5F3FF', url: '/pages/scan/index' },
{ key: 'dashboard', name: '数据看板', icon: 'i-carbon-analytics', color: '#10B981', bgLight: '#ECFDF5' },
{ key: 'attendance', name: '考勤打卡', icon: 'i-carbon-location-person', color: '#06B6D4', bgLight: '#ECFEFF' },
{ key: 'repair', name: '报修管理', icon: 'i-carbon-settings-adjust', color: '#EC4899', bgLight: '#FDF2F8' },
]
function handleQuickApp(app: any) {
if (app.url) {
uni.navigateTo({ url: app.url })
} else {
toast.info(`${app.name}功能开发中`)
}
return keys.map(key => getMenuItemByKey(key)).filter(Boolean) as MenuItem[]
})
}
/** 初始化数据 */
function toggleGroup(key: string) {
const idx = expandedGroups.value.indexOf(key)
if (idx === -1) {
expandedGroups.value.push(key)
} else {
expandedGroups.value.splice(idx, 1)
}
}
/** 初始化数据:过滤掉系统管理和基础设施 */
function initData() {
menuGroups.value = getMenuGroups()
menuGroups.value = getMenuGroups().filter(
g => g.key !== 'system' && g.key !== 'infra',
)
}
/** 跳转到常用服务设置页面 */
function handleGotoFavoriteSettings() {
uni.navigateTo({
url: '/pages/index/settings/index',
})
}
/**
* 初始化
*
* 不使用 onMounted 的原因是:登录后,页面可能已经挂载,但数据需要重新初始化
*/
onShow(() => {
initData()
})
</script>
<style lang="scss" scoped>
.app-icon-wrap {
width: 96rpx;
height: 96rpx;
border-radius: 28rpx;
display: flex;
align-items: center;
justify-content: center;
}
</style>

View File

@@ -1,19 +1,99 @@
<template>
<view class="mx-20rpx mt-20rpx overflow-hidden rounded-16rpx bg-white">
<view class="flex items-center p-24rpx">
<view class="avatar-wrapper mr-20rpx h-100rpx w-100rpx overflow-hidden rounded-full">
<image
:src="userInfo.avatar"
mode="aspectFill"
class="h-full w-full"
/>
</view>
<view class="flex-1">
<view class="text-32rpx text-#333 font-500">
{{ greeting }}{{ userInfo.nickname || userInfo.username }}
<view class="ai-gradient-header">
<view class="ai-header-pattern">
<view class="ai-header-circle-bl" />
</view>
<view class="relative z-10 px-32rpx pb-40rpx">
<!--
用户信息区头像占左侧两行右侧上下两行
第一行(与胶囊齐平)用户名 + 角色标签
第二行(胶囊下方)问候语 + 消息铃铛
-->
<view class="flex" :style="{ paddingTop: topPadding }">
<!-- 左侧头像跨两行垂直居中 -->
<view class="mr-16rpx flex flex-shrink-0 items-center">
<view class="avatar-wrapper overflow-hidden rounded-full" :style="avatarStyle">
<image
:src="userInfo.avatar || '/static/images/default-avatar.png'"
mode="aspectFill"
class="h-full w-full"
/>
</view>
</view>
<view class="mt-8rpx text-26rpx text-#999">
{{ description }}
<!-- 右侧两行内容 -->
<view class="flex flex-1 flex-col justify-between">
<!-- 第一行用户名 + 角色 与胶囊齐平 -->
<view class="flex items-center" :style="row1Style">
<text class="text-30rpx text-white font-bold leading-tight">
{{ userInfo.nickname || userInfo.username }}
</text>
<view class="ml-12rpx rounded-999px bg-white/20 px-14rpx py-2rpx text-20rpx text-white font-bold">
物业管理员
</view>
</view>
<!-- 第二行问候语 + 消息铃铛 胶囊下方 -->
<view class="flex items-center justify-between" :style="row2Style">
<view class="text-26rpx text-white/80">
{{ greeting }}{{ description }}
</view>
<view class="relative" @click="goToMessage">
<view class="h-56rpx w-56rpx flex items-center justify-center rounded-full bg-white/15">
<view class="i-carbon-notification text-18px text-white" />
</view>
<view class="absolute h-14rpx w-14rpx rounded-full bg-red-500 -right-2rpx -top-2rpx" />
</view>
</view>
</view>
</view>
<!-- 统计数据2 + 竖线分隔 -->
<view class="mt-32rpx flex px-8rpx">
<view class="flex flex-1 flex-col">
<text class="stat-label">实时客流统计</text>
<text class="stat-value">2,450</text>
</view>
<view class="stat-divider flex flex-1 flex-col pl-32rpx">
<text class="stat-label">实时工单统计</text>
<text class="stat-value">128</text>
</view>
</view>
<!-- 自定义柱状图 -->
<view class="chart-area mt-24rpx">
<!-- tooltip 气泡 -->
<view
v-if="activeBar >= 0"
class="chart-tip"
:style="{ left: `${(activeBar + 0.5) * (100 / chartData.length)}%` }"
>
<text class="chart-tip-text">{{ chartData[activeBar].label }} {{ chartData[activeBar].value }}</text>
<view class="chart-tip-arrow" />
</view>
<!-- 柱子区域 -->
<view class="chart-bars">
<view
v-for="(item, i) in chartData"
:key="i"
class="chart-col"
@click="handleBarTap(i)"
>
<view
class="chart-bar"
:class="{ 'chart-bar--active': activeBar === i }"
:style="{ height: `${item.percent}%` }"
/>
</view>
</view>
<!-- X 轴时间标签 -->
<view class="chart-x-axis">
<text
v-for="(item, i) in chartData"
:key="i"
class="chart-x-label"
:class="{ 'chart-x-label--active': activeBar === i }"
>
{{ item.showLabel ? item.label : '' }}
</text>
</view>
</view>
</view>
@@ -23,6 +103,7 @@
<script lang="ts" setup>
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/store'
import { menuButtonInfo, statusBarHeight } from '@/utils/systemInfo'
defineOptions({
name: 'UserHeader',
@@ -31,46 +112,232 @@ defineOptions({
const userStore = useUserStore()
const { userInfo } = storeToRefs(userStore)
/**
* 小程序胶囊适配 — 所有间距从胶囊参数推导:
* capsuleGap = capsule.top - statusBarHeight胶囊上边距≈4-8px
* 以 capsuleGap 为基本间距单位,保持节奏一致
*/
const isMp = menuButtonInfo.height > 0
// 胶囊距导航栏顶部的间距(也作为行间距基准)
const capsuleGap = isMp ? menuButtonInfo.top - statusBarHeight : 0
// 整体顶部间距:与胶囊上边距对齐
const topPadding = computed(() => isMp ? `${capsuleGap}px` : '24rpx')
// 第一行:高度 = 胶囊高度,与胶囊垂直居中
const row1Style = computed(() => {
if (isMp) {
return { height: `${menuButtonInfo.height}px` }
}
return {}
})
// 两行之间的间距 = capsuleGap与胶囊上下边距保持一致
const rowGap = computed(() => isMp ? `${capsuleGap}px` : '12rpx')
// 第二行:自然高度,不强制等高
const row2Style = computed(() => {
return { marginTop: rowGap.value }
})
// 头像尺寸:第一行高度 + 间距 + 第二行预估高度(≈胶囊高度),整体略小留呼吸感
const avatarStyle = computed(() => {
if (isMp) {
const size = menuButtonInfo.height * 2 + capsuleGap - 4
return { width: `${size}px`, height: `${size}px` }
}
return { width: '80rpx', height: '80rpx' }
})
// ---- 柱状图数据 ----
const rawData = [
{ label: '08:00', value: 180 },
{ label: '09:00', value: 360 },
{ label: '10:00', value: 520 },
{ label: '11:00', value: 720 },
{ label: '12:00', value: 490 },
{ label: '13:00', value: 410 },
{ label: '14:00', value: 670 },
{ label: '15:00', value: 810 },
{ label: '16:00', value: 580 },
{ label: '17:00', value: 450 },
{ label: '18:00', value: 320 },
{ label: '19:00', value: 240 },
]
const maxValue = Math.max(...rawData.map(d => d.value))
const chartData = rawData.map((d, i) => ({
...d,
percent: (d.value / maxValue) * 100,
showLabel: i % 2 === 0, // 隔一个显示
}))
/** 当前选中的柱子,-1 未选中 */
const activeBar = ref(-1)
function handleBarTap(index: number) {
activeBar.value = activeBar.value === index ? -1 : index
}
/** 根据时间获取问候语 */
const greeting = computed(() => {
const hour = new Date().getHours()
if (hour < 6) {
if (hour < 6)
return '凌晨好'
} else if (hour < 9) {
if (hour < 9)
return '早上好'
} else if (hour < 12) {
if (hour < 12)
return '上午好'
} else if (hour < 14) {
if (hour < 14)
return '中午好'
} else if (hour < 17) {
if (hour < 17)
return '下午好'
} else if (hour < 19) {
if (hour < 19)
return '傍晚好'
} else {
return '晚上好'
}
return '晚上好'
})
/** 描述语 */
const description = computed(() => {
const hour = new Date().getHours()
if (hour < 9) {
return '开始新的一天,加油!'
} else if (hour < 12) {
return '工作顺利,效率满满'
} else if (hour < 14) {
return '午休时间,记得休息~'
} else if (hour < 18) {
return '继续努力,收获满满!'
} else {
return '辛苦了,注意休息!'
}
if (hour < 9)
return '开始新的一天'
if (hour < 12)
return '效率满满'
if (hour < 14)
return '记得休息'
if (hour < 18)
return '继续加油'
return '注意休息'
})
function goToMessage() {
uni.navigateTo({ url: '/pages/message/index' })
}
</script>
<style lang="scss" scoped>
.avatar-wrapper {
border: 3rpx solid #f0f0f0;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
border: 3rpx solid rgba(255, 255, 255, 0.4);
flex-shrink: 0;
}
.stat-label {
font-size: 18rpx;
font-weight: 700;
color: rgba(255, 255, 255, 0.7);
letter-spacing: 4rpx;
text-transform: uppercase;
margin-bottom: 8rpx;
}
.stat-value {
font-size: 48rpx;
font-weight: 900;
color: #fff;
letter-spacing: -2rpx;
}
.stat-divider {
border-left: 2rpx solid rgba(255, 255, 255, 0.2);
}
/* ---- 自定义柱状图 ---- */
.chart-area {
position: relative;
padding-top: 48rpx; /* tooltip 空间 */
}
.chart-bars {
display: flex;
align-items: flex-end;
height: 180rpx;
gap: 10rpx;
}
.chart-col {
flex: 1;
display: flex;
align-items: flex-end;
justify-content: center;
height: 100%;
cursor: pointer;
}
.chart-bar {
width: 100%;
max-width: 36rpx;
background: rgba(255, 255, 255, 0.3);
border-radius: 8rpx 8rpx 0 0;
min-height: 8rpx;
transition:
background 0.2s ease,
transform 0.15s ease;
}
.chart-bar--active {
background: #fff;
transform: scaleY(1.03);
}
/* X 轴 */
.chart-x-axis {
display: flex;
margin-top: 12rpx;
}
.chart-x-label {
flex: 1;
text-align: center;
font-size: 18rpx;
font-weight: 600;
color: rgba(255, 255, 255, 0.4);
min-height: 24rpx;
}
.chart-x-label--active {
color: #fff;
}
/* tooltip */
.chart-tip {
position: absolute;
top: 0;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
z-index: 10;
animation: tipIn 0.15s ease;
}
.chart-tip-text {
background: #fff;
color: #f97316;
font-size: 22rpx;
font-weight: 800;
padding: 8rpx 20rpx;
border-radius: 12rpx;
white-space: nowrap;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.15);
}
.chart-tip-arrow {
width: 0;
height: 0;
border-left: 10rpx solid transparent;
border-right: 10rpx solid transparent;
border-top: 10rpx solid #fff;
}
@keyframes tipIn {
from {
opacity: 0;
transform: translateX(-50%) translateY(6rpx);
}
to {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
</style>

View File

@@ -1,21 +1,13 @@
<template>
<view class="yd-page-container">
<!-- 顶部导航栏 -->
<wd-navbar
title="工作台"
placeholder safe-area-inset-top fixed
/>
<!-- 用户信息头部 -->
<view class="page-home">
<!-- 用户信息头部含橙色渐变 + 统计 + 消息铃铛 -->
<UserHeader />
<!-- Banner 轮播图 -->
<HomeBanner />
<!-- 菜单区域 -->
<!-- 常用应用 + 区域预警 + 菜单分组 -->
<MenuSection />
</view>
</template>
<script lang="ts" setup>
import HomeBanner from './components/banner.vue'
import MenuSection from './components/menu-section.vue'
import UserHeader from './components/user-header.vue'
@@ -30,3 +22,21 @@ definePage({
},
})
</script>
<style lang="scss">
/* 禁止原生页面滚动,让内部 scroll-view 接管 */
page {
height: 100%;
overflow: hidden;
background-color: #fffdf5;
}
</style>
<style lang="scss" scoped>
.page-home {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
</style>

View File

@@ -1,9 +1,12 @@
<template>
<view class="yd-page-container">
<!-- 顶部导航栏 -->
<wd-navbar
title="我的消息"
placeholder safe-area-inset-top fixed
left-arrow
placeholder
safe-area-inset-top
fixed
@click-left="uni.navigateBack()"
/>
<!-- 搜索组件 -->

View File

@@ -1,74 +1,100 @@
<template>
<view class="yd-page-container">
<!-- 顶部背景区域 -->
<view class="header-bg h-120rpx w-full flex items-center justify-center" />
<!-- 用户信息卡片 -->
<view class="relative mx-24rpx -mt-60rpx">
<view
class="user-card flex items-center rounded-12rpx bg-white p-32rpx"
@click="handleGoProfile"
>
<view class="avatar-wrapper mr-24rpx h-120rpx w-120rpx overflow-hidden rounded-full">
<!-- 橙色渐变头部 -->
<view class="ai-gradient-header">
<view class="ai-header-pattern">
<view class="ai-header-circle-bl" />
</view>
<view class="relative z-10 flex flex-col items-center pb-120rpx" :style="{ paddingTop: navBarPaddingTop }">
<view class="avatar-wrapper h-160rpx w-160rpx overflow-hidden rounded-full" @click="handleGoProfile">
<image
:src="userInfo.avatar"
:src="userInfo.avatar || '/static/images/default-avatar.png'"
mode="aspectFill"
class="h-full w-full"
/>
</view>
<view class="flex-1">
<view class="mb-8rpx text-40rpx text-[#323333] font-semibold">
{{ userInfo.nickname || userInfo.username }}
<view class="mt-20rpx text-32rpx text-white font-bold">
{{ userInfo.nickname || userInfo.username }}
</view>
<view class="mt-8rpx flex items-center">
<text class="text-24rpx text-white/70">工号: AIV-2024001</text>
<view class="mx-16rpx h-24rpx w-2rpx bg-white/30" />
<view class="rounded-999px bg-white/20 px-16rpx py-4rpx text-22rpx text-white font-bold">
物业管理员
</view>
<view class="text-30rpx text-[#777]">
{{ userProfile ? (userProfile.dept?.name || '暂无部门') : '' }}
</view>
</view>
</view>
<!-- 统计卡片 margin 重叠 -->
<view class="relative z-10 mx-40rpx -mt-100rpx">
<view class="stat-card ai-card flex py-40rpx">
<view class="flex-1 text-center">
<view class="text-48rpx text-[#F97316] font-900 tracking-tight">
128
</view>
<view class="mt-8rpx text-22rpx text-[#6B7280] font-600">
本月工单
</view>
</view>
<view class="w-2rpx bg-[#F9FAFB]" style="height: 64rpx; align-self: center;" />
<view class="flex-1 text-center">
<view class="text-48rpx text-[#22C55E] font-900 tracking-tight">
98%
</view>
<view class="mt-8rpx text-22rpx text-[#6B7280] font-600">
合格率
</view>
</view>
<view class="w-2rpx bg-[#F9FAFB]" style="height: 64rpx; align-self: center;" />
<view class="flex-1 text-center">
<view class="text-48rpx text-[#3B82F6] font-900 tracking-tight">
3.2m
</view>
<view class="mt-8rpx text-22rpx text-[#6B7280] font-600">
平均响应
</view>
</view>
</view>
</view>
<!-- 菜单区域 -->
<view class="mx-24rpx mt-32rpx">
<wd-cell-group custom-class="menu-group" border>
<wd-cell title="个人资料" is-link @click="handleGoProfile">
<template #icon>
<wd-icon name="user" size="20px" color="#1890ff" class="mr-16rpx" />
</template>
</wd-cell>
<wd-cell title="账号安全" is-link @click="handleGoSecurity">
<template #icon>
<wd-icon name="lock-on" size="20px" color="#52c41a" class="mr-16rpx" />
</template>
</wd-cell>
</wd-cell-group>
<wd-cell-group custom-class="menu-group mt-24rpx" border>
<wd-cell title="常见问题" is-link @click="handleGoFaq">
<template #icon>
<wd-icon name="warning" size="20px" color="#faad14" class="mr-16rpx" />
</template>
</wd-cell>
<wd-cell title="意见反馈" is-link @click="handleGoFeedback">
<template #icon>
<wd-icon name="edit" size="20px" color="#722ed1" class="mr-16rpx" />
</template>
</wd-cell>
<wd-cell title="联系客服" is-link @click="handleGoContact">
<template #icon>
<wd-icon name="phone" size="20px" color="#13c2c2" class="mr-16rpx" />
</template>
</wd-cell>
<wd-cell title="应用设置" is-link @click="handleGoSettings">
<template #icon>
<wd-icon name="setting" size="20px" color="#1890ff" class="mr-16rpx" />
</template>
</wd-cell>
</wd-cell-group>
<view class="mt-48rpx">
<wd-button block type="error" @click="handleLogout">
退出登录
</wd-button>
<view class="mx-40rpx mt-48rpx">
<view class="ai-card overflow-hidden">
<view
v-for="(item, index) in menuItems"
:key="item.title"
class="menu-item flex items-center px-32rpx py-28rpx"
:class="{ 'border-t-2rpx border-[#FFF7ED]': index > 0 }"
@click="item.action"
>
<view
class="mr-20rpx h-72rpx w-72rpx flex items-center justify-center rounded-24rpx bg-[#FFF7ED]"
>
<wd-icon :name="item.icon" size="20px" :color="item.iconColor" />
</view>
<view class="flex-1 text-28rpx text-[#1F2937] font-600">
{{ item.title }}
</view>
<wd-icon name="arrow-right" size="16px" color="#D1D5DB" />
</view>
</view>
</view>
<!-- 退出登录 -->
<view class="mx-40rpx mt-48rpx">
<view
class="flex items-center justify-center rounded-48rpx bg-[#FEF2F2] py-28rpx"
@click="handleLogout"
>
<text class="text-28rpx text-[#EF4444] font-bold">退出登录</text>
</view>
</view>
<!-- 版本号 -->
<view class="mb-40rpx mt-48rpx text-center text-22rpx text-[#D1D5DB]">
AIOT v1.0.0
</view>
</view>
</template>
@@ -81,6 +107,7 @@ import { getUserProfile } from '@/api/system/user/profile'
import { LOGIN_PAGE } from '@/router/config'
import { useUserStore } from '@/store'
import { useTokenStore } from '@/store/token'
import { customNavBarHeight } from '@/utils/systemInfo'
definePage({
style: {
@@ -92,7 +119,16 @@ const userStore = useUserStore()
const tokenStore = useTokenStore()
const toast = useToast()
const { userInfo } = storeToRefs(userStore)
const userProfile = ref<UserProfileVO | null>(null) // 用户详细信息
const userProfile = ref<UserProfileVO | null>(null)
// 小程序胶囊按钮适配ai-gradient-header 已处理状态栏
// 这里只需补上导航栏高度(胶囊按钮区域)
const navBarPaddingTop = computed(() => {
if (customNavBarHeight > 0) {
return `${customNavBarHeight}px`
}
return '48rpx'
})
/** 页面加载时获取用户信息 */
onMounted(async () => {
@@ -100,37 +136,39 @@ onMounted(async () => {
await userStore.fetchUserInfo()
})
/** 跳转到个人资料 */
const menuItems = [
{ title: '个人资料', icon: 'user', iconColor: '#F97316', action: () => handleGoProfile() },
{ title: '账号安全', icon: 'lock-on', iconColor: '#22C55E', action: () => handleGoSecurity() },
{ title: '常见问题', icon: 'warning', iconColor: '#CA8A04', action: () => handleGoFaq() },
{ title: '意见反馈', icon: 'edit', iconColor: '#8B5CF6', action: () => handleGoFeedback() },
{ title: '联系客服', icon: 'phone', iconColor: '#06B6D4', action: () => handleGoContact() },
{ title: '应用设置', icon: 'setting', iconColor: '#6366F1', action: () => handleGoSettings() },
]
function handleGoProfile() {
uni.navigateTo({ url: '/pages-core/user/profile/index' })
}
/** 跳转到账号安全 */
function handleGoSecurity() {
uni.navigateTo({ url: '/pages-core/user/security/index' })
}
/** 跳转到常见问题 */
function handleGoFaq() {
uni.navigateTo({ url: '/pages-core/user/faq/index' })
}
/** 跳转到意见反馈 */
function handleGoFeedback() {
uni.navigateTo({ url: '/pages-core/user/feedback/index' })
}
/** 跳转联系客服 */
function handleGoContact() {
uni.navigateTo({ url: '/pages-core/user/contact/index' })
}
/** 跳转到应用设置 */
function handleGoSettings() {
uni.navigateTo({ url: '/pages-core/user/settings/index' })
}
/** 退出登录 */
function handleLogout() {
uni.showModal({
title: '提示',
@@ -150,26 +188,16 @@ function handleLogout() {
</script>
<style lang="scss" scoped>
// 顶部渐变背景
.header-bg {
background: linear-gradient(135deg, #1890ff 0%, #36cfc9 100%);
}
// 用户卡片阴影
.user-card {
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.08);
}
// 头像边框
.avatar-wrapper {
border: 4rpx solid #f5f5f5;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
border: 4rpx solid rgba(255, 255, 255, 0.3);
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15);
}
// 菜单组样式
:deep(.menu-group) {
border-radius: 12rpx;
overflow: hidden;
box-shadow: 0 3rpx 8rpx rgba(24, 144, 255, 0.06);
.stat-card {
box-shadow: 0 16rpx 48rpx rgba(249, 115, 22, 0.1);
}
.menu-item:active {
background: #fffdf5;
}
</style>