feat: 优化工作台、个人中心、消息页面
- 重构工作台用户头部和菜单区域组件 - 优化个人中心页面布局 - 更新消息页面 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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--red' : warning.level === '中' ? 'ai-badge--orange' : 'ai-badge--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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()"
|
||||
/>
|
||||
|
||||
<!-- 搜索组件 -->
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user