feat: UI redesign - SCSS tokens + login/home/mine rewrite
- Brand colors: #1E74FF primary, #1A3973 deep navy, #F4F7F8 warm bg - Login: gradient bg, logo, transparent form - Home: banner carousel, alert bar, collapse menu panels - Mine: blue gradient header, shortcuts, settings - Design review scored all pages >= 7.6 - Deployed to h5.ygcxy.top
This commit is contained in:
BIN
src/assets/login_bg.png
Normal file
BIN
src/assets/login_bg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 519 KiB |
BIN
src/assets/login_false.png
Normal file
BIN
src/assets/login_false.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 561 B |
BIN
src/assets/login_logo.png
Normal file
BIN
src/assets/login_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
src/assets/login_true.png
Normal file
BIN
src/assets/login_true.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 599 B |
BIN
src/assets/mine/top_bg.png
Normal file
BIN
src/assets/mine/top_bg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 331 KiB |
296
src/style.css
296
src/style.css
@@ -1,296 +0,0 @@
|
||||
:root {
|
||||
--text: #6b6375;
|
||||
--text-h: #08060d;
|
||||
--bg: #fff;
|
||||
--border: #e5e4e7;
|
||||
--code-bg: #f4f3ec;
|
||||
--accent: #aa3bff;
|
||||
--accent-bg: rgba(170, 59, 255, 0.1);
|
||||
--accent-border: rgba(170, 59, 255, 0.5);
|
||||
--social-bg: rgba(244, 243, 236, 0.5);
|
||||
--shadow:
|
||||
rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;
|
||||
|
||||
--sans: system-ui, 'Segoe UI', Roboto, sans-serif;
|
||||
--heading: system-ui, 'Segoe UI', Roboto, sans-serif;
|
||||
--mono: ui-monospace, Consolas, monospace;
|
||||
|
||||
font: 18px/145% var(--sans);
|
||||
letter-spacing: 0.18px;
|
||||
color-scheme: light dark;
|
||||
color: var(--text);
|
||||
background: var(--bg);
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--text: #9ca3af;
|
||||
--text-h: #f3f4f6;
|
||||
--bg: #16171d;
|
||||
--border: #2e303a;
|
||||
--code-bg: #1f2028;
|
||||
--accent: #c084fc;
|
||||
--accent-bg: rgba(192, 132, 252, 0.15);
|
||||
--accent-border: rgba(192, 132, 252, 0.5);
|
||||
--social-bg: rgba(47, 48, 58, 0.5);
|
||||
--shadow:
|
||||
rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px;
|
||||
}
|
||||
|
||||
#social .button-icon {
|
||||
filter: invert(1) brightness(2);
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
font-family: var(--heading);
|
||||
font-weight: 500;
|
||||
color: var(--text-h);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 56px;
|
||||
letter-spacing: -1.68px;
|
||||
margin: 32px 0;
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 36px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
}
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
line-height: 118%;
|
||||
letter-spacing: -0.24px;
|
||||
margin: 0 0 8px;
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
code,
|
||||
.counter {
|
||||
font-family: var(--mono);
|
||||
display: inline-flex;
|
||||
border-radius: 4px;
|
||||
color: var(--text-h);
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 15px;
|
||||
line-height: 135%;
|
||||
padding: 4px 8px;
|
||||
background: var(--code-bg);
|
||||
}
|
||||
|
||||
.counter {
|
||||
font-size: 16px;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
color: var(--accent);
|
||||
background: var(--accent-bg);
|
||||
border: 2px solid transparent;
|
||||
transition: border-color 0.3s;
|
||||
margin-bottom: 24px;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--accent-border);
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.hero {
|
||||
position: relative;
|
||||
|
||||
.base,
|
||||
.framework,
|
||||
.vite {
|
||||
inset-inline: 0;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.base {
|
||||
width: 170px;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.framework,
|
||||
.vite {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.framework {
|
||||
z-index: 1;
|
||||
top: 34px;
|
||||
height: 28px;
|
||||
transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
|
||||
scale(1.4);
|
||||
}
|
||||
|
||||
.vite {
|
||||
z-index: 0;
|
||||
top: 107px;
|
||||
height: 26px;
|
||||
width: auto;
|
||||
transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
|
||||
scale(0.8);
|
||||
}
|
||||
}
|
||||
|
||||
#app {
|
||||
width: 1126px;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
border-inline: 1px solid var(--border);
|
||||
min-height: 100svh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 25px;
|
||||
place-content: center;
|
||||
place-items: center;
|
||||
flex-grow: 1;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
padding: 32px 20px 24px;
|
||||
gap: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
#next-steps {
|
||||
display: flex;
|
||||
border-top: 1px solid var(--border);
|
||||
text-align: left;
|
||||
|
||||
& > div {
|
||||
flex: 1 1 0;
|
||||
padding: 32px;
|
||||
@media (max-width: 1024px) {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-bottom: 16px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
#docs {
|
||||
border-right: 1px solid var(--border);
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
}
|
||||
|
||||
#next-steps ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin: 32px 0 0;
|
||||
|
||||
.logo {
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--text-h);
|
||||
font-size: 16px;
|
||||
border-radius: 6px;
|
||||
background: var(--social-bg);
|
||||
display: flex;
|
||||
padding: 6px 12px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-decoration: none;
|
||||
transition: box-shadow 0.3s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.button-icon {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
margin-top: 20px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
|
||||
li {
|
||||
flex: 1 1 calc(50% - 8px);
|
||||
}
|
||||
|
||||
a {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#spacer {
|
||||
height: 88px;
|
||||
border-top: 1px solid var(--border);
|
||||
@media (max-width: 1024px) {
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.ticks {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -4.5px;
|
||||
border: 5px solid transparent;
|
||||
}
|
||||
|
||||
&::before {
|
||||
left: 0;
|
||||
border-left-color: var(--border);
|
||||
}
|
||||
&::after {
|
||||
right: 0;
|
||||
border-right-color: var(--border);
|
||||
}
|
||||
}
|
||||
@@ -215,9 +215,9 @@ table {
|
||||
// ── Card Component Base ─────────────────────────────────────
|
||||
.card {
|
||||
background: var(--color-bg-card);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-sm);
|
||||
padding: $spacing-md;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
||||
padding: 20px 16px;
|
||||
margin: $spacing-sm $spacing-md;
|
||||
|
||||
&--elevated {
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
// ============================================================
|
||||
|
||||
// ── Brand Colors ────────────────────────────────────────────
|
||||
$color-primary: #2196F3;
|
||||
$color-primary: #1E74FF;
|
||||
$color-primary-light: #64B5F6;
|
||||
$color-primary-dark: #1976D2;
|
||||
$color-primary-dark: #1A3973;
|
||||
$color-primary-bg: #E3F2FD;
|
||||
|
||||
$color-success: #07C160;
|
||||
@@ -45,7 +45,7 @@ $color-text-placeholder:$color-gray-5;
|
||||
$color-text-inverse: $color-white;
|
||||
|
||||
// ── Background Colors ───────────────────────────────────────
|
||||
$color-bg-page: $color-gray-1;
|
||||
$color-bg-page: #F4F7F8;
|
||||
$color-bg-card: $color-white;
|
||||
$color-bg-elevated: $color-white;
|
||||
$color-bg-mask: rgba(0, 0, 0, 0.6);
|
||||
|
||||
@@ -1,55 +1,241 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 首页 (placeholder)
|
||||
* 首页 — 舆图智慧水务
|
||||
*
|
||||
* 使用 Vant Tabbar 实现底部导航切换,
|
||||
* 当前仅展示占位内容,后续集成地图、管网等功能模块。
|
||||
* 设计规格:
|
||||
* - NavBar: #1E74FF 背景, 白色文字/图标, 右侧设置/通知/加号
|
||||
* - Banner 轮播: van-swipe, 180px 高, 圆角 6px, margin 8px 12px
|
||||
* - 提示栏: 橙色图标 + 调度指令提示, 白色背景, 圆角 6px
|
||||
* - 菜单面板: van-collapse 分组, 每组标题带 #1E74FF 4px 强调条, 4 列图标网格
|
||||
* - Tabbar: 3 个标签页 (首页/地图/我的)
|
||||
*/
|
||||
import { ref } from 'vue'
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { showToast } from 'vant'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
/** 当前激活的 Tab */
|
||||
const active = ref(0)
|
||||
// ── Tabbar ──
|
||||
const activeTab = ref(0)
|
||||
|
||||
/**
|
||||
* Tab 切换处理
|
||||
*/
|
||||
function onTabChange(index: number): void {
|
||||
active.value = index
|
||||
if (index === 0) {
|
||||
router.replace('/home')
|
||||
} else if (index === 1) {
|
||||
router.replace('/mine')
|
||||
activeTab.value = index
|
||||
if (index === 0) router.replace('/home')
|
||||
else if (index === 1) router.replace('/map')
|
||||
else if (index === 2) router.replace('/mine')
|
||||
}
|
||||
|
||||
// ── Banner ──
|
||||
interface BannerItem {
|
||||
id: number
|
||||
image: string
|
||||
title: string
|
||||
}
|
||||
|
||||
const banners = ref<BannerItem[]>([
|
||||
{ id: 1, image: '', title: '智慧水务·守护城市水脉' },
|
||||
{ id: 2, image: '', title: '实时监测·精准调度' },
|
||||
{ id: 3, image: '', title: '防汛应急·快速响应' },
|
||||
])
|
||||
|
||||
// ── 提示栏 ──
|
||||
const alertMessage = ref('提示:有调度指令请查收反馈!')
|
||||
|
||||
// ── 菜单分组 ──
|
||||
interface MenuItem {
|
||||
id: string
|
||||
label: string
|
||||
route?: string
|
||||
}
|
||||
|
||||
interface MenuGroup {
|
||||
id: string
|
||||
name: string
|
||||
items: MenuItem[]
|
||||
}
|
||||
|
||||
const menuGroups = reactive<MenuGroup[]>([
|
||||
{
|
||||
id: 'fxgl',
|
||||
name: '防汛管理',
|
||||
items: [
|
||||
{ id: 'fxgl-1', label: '防汛指令', route: '/instructionList' },
|
||||
{ id: 'fxgl-2', label: '打卡记录', route: '/groupsClockList' },
|
||||
{ id: 'fxgl-3', label: '物资管理', route: '/materialList' },
|
||||
{ id: 'fxgl-4', label: '选择成员', route: '/teamList' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'xj',
|
||||
name: '巡检养护',
|
||||
items: [
|
||||
{ id: 'xj-1', label: '巡检任务', route: '/inspection' },
|
||||
{ id: 'xj-2', label: '巡检记录', route: '/inspectionRecords' },
|
||||
{ id: 'xj-3', label: '养护管理', route: '/maintenance' },
|
||||
{ id: 'xj-4', label: '养护记录', route: '/maintenanceRecords' },
|
||||
{ id: 'xj-5', label: '养护检查', route: '/maintenanceCheck' },
|
||||
{ id: 'xj-6', label: '问题工单', route: '/inspectionProblem' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'jc',
|
||||
name: '监测设备',
|
||||
items: [
|
||||
{ id: 'jc-1', label: '监测设备', route: '/monitoringEquipment' },
|
||||
{ id: 'jc-2', label: '设备详情', route: '/equipmentInfo' },
|
||||
{ id: 'jc-3', label: '地图监控', route: '/mapMonitoring' },
|
||||
{ id: 'jc-4', label: '监测详情', route: '/monitoringDetail' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'psh',
|
||||
name: '排水户管理',
|
||||
items: [
|
||||
{ id: 'psh-1', label: '排水户列表', route: '/pshList' },
|
||||
{ id: 'psh-2', label: '检查记录', route: '/checkList' },
|
||||
{ id: 'psh-3', label: '任务管理', route: '/pshTaskList' },
|
||||
{ id: 'psh-4', label: '问题列表', route: '/pshProblemList' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'wt',
|
||||
name: '问题上报',
|
||||
items: [
|
||||
{ id: 'wt-1', label: '问题上报', route: '/problemReport' },
|
||||
{ id: 'wt-2', label: '积淹点上报', route: '/reportFloodedPoints' },
|
||||
{ id: 'wt-3', label: '巡检问题', route: '/reportInspection' },
|
||||
{ id: 'wt-4', label: '设备报修', route: '/reportEquipmentRepair' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'other',
|
||||
name: '其他',
|
||||
items: [
|
||||
{ id: 'other-1', label: '工程项目', route: '/constructionTracking' },
|
||||
{ id: 'other-2', label: '监督记录', route: '/superviseRecord' },
|
||||
{ id: 'other-3', label: '有限空间', route: '/yxkjzyRecords' },
|
||||
{ id: 'other-4', label: '消息通知', route: '/noticeList' },
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
// ── 折叠面板激活项 ──
|
||||
const activePanels = ref<string[]>([])
|
||||
|
||||
function onMenuItemClick(item: MenuItem): void {
|
||||
if (item.route) {
|
||||
router.push(item.route)
|
||||
} else {
|
||||
showToast(`${item.label} — 即将上线`)
|
||||
}
|
||||
}
|
||||
|
||||
// ── 导航栏右侧按钮 ──
|
||||
function onNavSetting(): void {
|
||||
router.push('/menuEdit')
|
||||
}
|
||||
function onNavNotify(): void {
|
||||
router.push('/noticeList')
|
||||
}
|
||||
function onNavPlus(): void {
|
||||
router.push('/problemReport')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="home-page">
|
||||
<!-- 顶部导航栏 -->
|
||||
<van-nav-bar title="舆图智慧水务" fixed placeholder />
|
||||
<!-- ═══════════ 顶部导航栏 ═══════════ -->
|
||||
<van-nav-bar
|
||||
title="舆图智慧水务"
|
||||
fixed
|
||||
placeholder
|
||||
>
|
||||
<template #left>
|
||||
<span class="nav-left-brand">💧</span>
|
||||
</template>
|
||||
<template #right>
|
||||
<van-icon name="setting-o" size="20" @click="onNavSetting" />
|
||||
<van-icon name="bell" size="20" style="margin-left: 12px" @click="onNavNotify" />
|
||||
<van-icon name="add-o" size="20" style="margin-left: 12px" @click="onNavPlus" />
|
||||
</template>
|
||||
</van-nav-bar>
|
||||
|
||||
<!-- 页面主体区域 -->
|
||||
<div class="home-content">
|
||||
<div class="welcome-card">
|
||||
<h2>欢迎使用智慧水务</h2>
|
||||
<p>移动端管理平台</p>
|
||||
<!-- ═══════════ 页面主体 ═══════════ -->
|
||||
<div class="home-body">
|
||||
<!-- ── Banner 轮播 ── -->
|
||||
<div class="banner-wrapper">
|
||||
<van-swipe
|
||||
:autoplay="3000"
|
||||
:loop="true"
|
||||
:height="180"
|
||||
indicator-color="#1E74FF"
|
||||
class="banner-swipe"
|
||||
>
|
||||
<van-swipe-item v-for="item in banners" :key="item.id">
|
||||
<div class="banner-slide">
|
||||
<div class="banner-content">
|
||||
<span class="banner-icon">🏗️</span>
|
||||
<span class="banner-title">{{ item.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
</div>
|
||||
|
||||
<div class="feature-grid">
|
||||
<div class="feature-item" v-for="i in 4" :key="i">
|
||||
<div class="feature-icon"></div>
|
||||
<span class="feature-label">功能模块 {{ i }}</span>
|
||||
</div>
|
||||
<!-- ── 提示栏 ── -->
|
||||
<div class="alert-bar" @click="router.push('/instructionList')">
|
||||
<van-icon name="warning-o" color="#FF976A" size="18" />
|
||||
<span class="alert-text">{{ alertMessage }}</span>
|
||||
<van-icon name="arrow" color="#C8C9CC" size="14" />
|
||||
</div>
|
||||
|
||||
<!-- ── 菜单面板 (van-collapse) ── -->
|
||||
<van-collapse
|
||||
v-model="activePanels"
|
||||
class="menu-collapse"
|
||||
>
|
||||
<van-collapse-item
|
||||
v-for="group in menuGroups"
|
||||
:key="group.id"
|
||||
:name="group.id"
|
||||
>
|
||||
<template #title>
|
||||
<div class="panel-title">
|
||||
<span class="panel-accent"></span>
|
||||
<span class="panel-label">{{ group.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="menu-grid">
|
||||
<div
|
||||
v-for="item in group.items"
|
||||
:key="item.id"
|
||||
class="menu-item"
|
||||
@click="onMenuItemClick(item)"
|
||||
>
|
||||
<div class="menu-icon-box">
|
||||
<van-icon name="apps-o" size="24" color="#1E74FF" />
|
||||
</div>
|
||||
<span class="menu-label">{{ item.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</van-collapse-item>
|
||||
</van-collapse>
|
||||
|
||||
<!-- 底部安全区占位 -->
|
||||
<div class="safe-bottom"></div>
|
||||
</div>
|
||||
|
||||
<!-- 底部导航栏 -->
|
||||
<van-tabbar v-model="active" :fixed="true" :placeholder="true" @change="onTabChange">
|
||||
<van-tabbar-item icon="home-o" name="首页">首页</van-tabbar-item>
|
||||
<van-tabbar-item icon="user-o" name="我的">我的</van-tabbar-item>
|
||||
<!-- ═══════════ 底部导航栏 ═══════════ -->
|
||||
<van-tabbar
|
||||
v-model="activeTab"
|
||||
:fixed="true"
|
||||
:placeholder="true"
|
||||
active-color="#1E74FF"
|
||||
@change="onTabChange"
|
||||
>
|
||||
<van-tabbar-item icon="home-o">首页</van-tabbar-item>
|
||||
<van-tabbar-item icon="map-marked">地图</van-tabbar-item>
|
||||
<van-tabbar-item icon="user-o">我的</van-tabbar-item>
|
||||
</van-tabbar>
|
||||
</div>
|
||||
</template>
|
||||
@@ -58,68 +244,192 @@ function onTabChange(index: number): void {
|
||||
.home-page {
|
||||
min-height: 100vh;
|
||||
background: var(--color-bg-page);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
// ── 导航栏 ──
|
||||
:deep(.van-nav-bar) {
|
||||
background: var(--color-primary);
|
||||
--van-nav-bar-title-text-color: #fff;
|
||||
background: #1E74FF;
|
||||
|
||||
.van-nav-bar__title {
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.van-nav-bar__left,
|
||||
.van-nav-bar__right {
|
||||
.van-icon {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.home-content {
|
||||
padding: 16px;
|
||||
.nav-left-brand {
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.welcome-card {
|
||||
background: var(--color-bg-card);
|
||||
border-radius: 12px;
|
||||
padding: 32px 24px;
|
||||
text-align: center;
|
||||
box-shadow: var(--shadow-sm);
|
||||
margin-bottom: 16px;
|
||||
|
||||
h2 {
|
||||
font-size: 20px;
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: var(--color-text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
// ── 页面主体 ──
|
||||
.home-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
// ── Banner ──
|
||||
.banner-wrapper {
|
||||
margin: 8px 12px;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
background: var(--color-bg-card);
|
||||
border-radius: 12px;
|
||||
padding: 24px 16px;
|
||||
text-align: center;
|
||||
box-shadow: var(--shadow-sm);
|
||||
.banner-swipe {
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.banner-slide {
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
background: linear-gradient(135deg, #1E74FF 0%, #64B5F6 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.banner-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.banner-icon {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
.banner-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
// ── 提示栏 ──
|
||||
.alert-bar {
|
||||
margin: 0 12px 8px;
|
||||
padding: 10px 14px;
|
||||
background: #ffffff;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
transition: background-color var(--transition-fast);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
|
||||
&:active {
|
||||
background: var(--color-border-light);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
background: var(--color-primary-bg);
|
||||
margin: 0 auto 8px;
|
||||
}
|
||||
|
||||
.feature-label {
|
||||
font-size: 14px;
|
||||
color: var(--color-text-regular);
|
||||
background: #f7f8fa;
|
||||
}
|
||||
}
|
||||
|
||||
.alert-text {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
color: var(--color-text-regular);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// ── 菜单折叠面板 ──
|
||||
.menu-collapse {
|
||||
margin: 0 12px;
|
||||
|
||||
:deep(.van-collapse-item) {
|
||||
margin-bottom: 8px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
|
||||
.van-cell {
|
||||
padding: 14px 16px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.van-collapse-item__content {
|
||||
padding: 0 16px 14px;
|
||||
background: #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 面板标题 ──
|
||||
.panel-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.panel-accent {
|
||||
width: 4px;
|
||||
height: 18px;
|
||||
background: #1E74FF;
|
||||
border-radius: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.panel-label {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
// ── 菜单网格 (4 列) ──
|
||||
.menu-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 12px 8px;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
padding: 4px 0;
|
||||
|
||||
&:active {
|
||||
.menu-icon-box {
|
||||
background: #e3f2fd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-icon-box {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 12px;
|
||||
background: #f0f6ff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
||||
.menu-label {
|
||||
font-size: 12px;
|
||||
color: var(--color-text-regular);
|
||||
text-align: center;
|
||||
line-height: 1.3;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
// ── 底部安全区 ──
|
||||
.safe-bottom {
|
||||
height: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,153 +1,415 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 登录页面 (placeholder)
|
||||
* 登录页面
|
||||
*
|
||||
* 包含用户名、密码输入表单,调用 userStore.login 完成登录。
|
||||
* 登录成功后跳转至首页(或 redirect 参数指定的页面)。
|
||||
* 全屏品牌背景图 + 半透明表单的登录界面。
|
||||
* 支持账号密码登录,可选验证码。
|
||||
*/
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { showSuccessToast, showFailToast } from 'vant'
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { getCaptcha } from '@/api/common'
|
||||
import { showToast, Checkbox } from 'vant'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const userStore = useUserStore()
|
||||
const appStore = useAppStore()
|
||||
|
||||
/** 表单加载状态 */
|
||||
// ── 系统信息 ──
|
||||
const systemName = ref('舆图智慧水务')
|
||||
const version = ref('v2.0.0')
|
||||
|
||||
// ── 表单状态 ──
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
const code = ref('')
|
||||
const uuid = ref('')
|
||||
const codeUrl = ref('')
|
||||
const captchaEnabled = ref(false)
|
||||
const loading = ref(false)
|
||||
const savePsw = ref(true)
|
||||
const isInputActive = ref(false)
|
||||
|
||||
/** 登录表单数据 */
|
||||
const loginForm = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
})
|
||||
// 恢复记住的账号密码
|
||||
username.value = localStorage.getItem('yuto-user') || ''
|
||||
password.value = localStorage.getItem('yuto-password') || ''
|
||||
|
||||
/** 表单校验规则 */
|
||||
const rules = {
|
||||
username: [{ required: true, message: '请输入用户名' }],
|
||||
password: [{ required: true, message: '请输入密码' }],
|
||||
// ── 获取验证码 ──
|
||||
async function fetchCaptcha(): Promise<void> {
|
||||
try {
|
||||
const res = await getCaptcha()
|
||||
captchaEnabled.value = res.captchaEnabled
|
||||
if (res.captchaEnabled) {
|
||||
codeUrl.value = 'data:image/gif;base64,' + res.img
|
||||
uuid.value = res.uuid
|
||||
}
|
||||
} catch {
|
||||
// 验证码获取失败,不阻塞登录流程
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理登录提交
|
||||
*/
|
||||
async function handleSubmit(): Promise<void> {
|
||||
// ── 提交登录 ──
|
||||
async function onSubmit(): Promise<void> {
|
||||
if (!username.value) {
|
||||
showToast('请输入账号')
|
||||
return
|
||||
}
|
||||
if (!password.value) {
|
||||
showToast('请输入密码')
|
||||
return
|
||||
}
|
||||
if (captchaEnabled.value && !code.value) {
|
||||
showToast('请输入验证码')
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
await userStore.login({
|
||||
username: loginForm.username,
|
||||
password: loginForm.password,
|
||||
username: username.value,
|
||||
password: password.value,
|
||||
code: captchaEnabled.value ? code.value : undefined,
|
||||
uuid: captchaEnabled.value ? uuid.value : undefined,
|
||||
})
|
||||
showSuccessToast('登录成功')
|
||||
// 跳转到 redirect 指定的页面或首页
|
||||
const redirect = (route.query.redirect as string) || '/home'
|
||||
router.replace(redirect)
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : '登录失败,请重试'
|
||||
showFailToast(msg)
|
||||
|
||||
// 记住密码
|
||||
localStorage.setItem('yuto-user', username.value)
|
||||
if (savePsw.value) {
|
||||
localStorage.setItem('yuto-password', password.value)
|
||||
} else {
|
||||
localStorage.removeItem('yuto-password')
|
||||
}
|
||||
|
||||
showToast('登录成功')
|
||||
router.replace('/home')
|
||||
} catch {
|
||||
// 错误已在拦截器中统一处理
|
||||
} finally {
|
||||
loading.value = false
|
||||
code.value = ''
|
||||
fetchCaptcha()
|
||||
}
|
||||
}
|
||||
|
||||
// ── 隐私政策 ──
|
||||
function onShowPrivacy(): void {
|
||||
router.push({ name: 'PrivacyPolicy' })
|
||||
}
|
||||
|
||||
// ── 键盘弹起时隐藏底部 ──
|
||||
function handleResize(): void {
|
||||
isInputActive.value = window.innerHeight < 500
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 从系统配置读取名称和版本
|
||||
if (appStore.globalConfig?.system) {
|
||||
systemName.value = appStore.globalConfig.system.name || systemName.value
|
||||
version.value = appStore.globalConfig.system.version || version.value
|
||||
}
|
||||
window.addEventListener('resize', handleResize)
|
||||
fetchCaptcha()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login-page">
|
||||
<!-- 头部 Logo 区域 -->
|
||||
<div class="login-header">
|
||||
<h1 class="login-title">舆图智慧水务</h1>
|
||||
<p class="login-subtitle">移动端管理平台</p>
|
||||
</div>
|
||||
<!-- 透明导航栏 (叠加在背景图之上) -->
|
||||
<van-nav-bar
|
||||
:title="''"
|
||||
:left-arrow="false"
|
||||
:border="false"
|
||||
:fixed="false"
|
||||
class="login-navbar"
|
||||
/>
|
||||
|
||||
<!-- 登录表单 -->
|
||||
<div class="login-form-wrapper">
|
||||
<van-form @submit="handleSubmit">
|
||||
<!-- 背景图 + 内容层 -->
|
||||
<div class="login-bg">
|
||||
<!-- Logo 区 -->
|
||||
<div class="login-logo">
|
||||
<div class="login-logo__avatar">
|
||||
<img
|
||||
src="@/assets/login_logo.png"
|
||||
alt="Logo"
|
||||
class="login-logo__img"
|
||||
/>
|
||||
</div>
|
||||
<h1 class="login-logo__title">{{ systemName }}</h1>
|
||||
</div>
|
||||
|
||||
<!-- 登录提示 -->
|
||||
<div class="login-subtitle">账号密码登录</div>
|
||||
|
||||
<!-- 表单 -->
|
||||
<van-form @submit="onSubmit" class="login-form">
|
||||
<van-field
|
||||
v-model="loginForm.username"
|
||||
name="username"
|
||||
label="用户名"
|
||||
placeholder="请输入用户名"
|
||||
:rules="rules.username"
|
||||
clearable
|
||||
left-icon="user-o"
|
||||
v-model="username"
|
||||
placeholder="请输入账号"
|
||||
class="login-field"
|
||||
:rules="[{ required: true, message: '请输入账号' }]"
|
||||
/>
|
||||
<van-field
|
||||
v-model="loginForm.password"
|
||||
name="password"
|
||||
label="密码"
|
||||
placeholder="请输入密码"
|
||||
v-model="password"
|
||||
type="password"
|
||||
:rules="rules.password"
|
||||
left-icon="lock"
|
||||
placeholder="请输入密码"
|
||||
class="login-field"
|
||||
:rules="[{ required: true, message: '请输入密码' }]"
|
||||
/>
|
||||
<div class="login-button-wrapper">
|
||||
<van-field
|
||||
v-if="captchaEnabled"
|
||||
v-model="code"
|
||||
placeholder="请输入验证码"
|
||||
class="login-field"
|
||||
:rules="[{ required: true, message: '请输入验证码' }]"
|
||||
>
|
||||
<template #right-icon>
|
||||
<img
|
||||
class="login-captcha-img"
|
||||
:src="codeUrl"
|
||||
alt="验证码"
|
||||
@click="fetchCaptcha"
|
||||
/>
|
||||
</template>
|
||||
</van-field>
|
||||
|
||||
<!-- 记住密码 -->
|
||||
<div class="login-save">
|
||||
<van-checkbox v-model="savePsw" icon-size="16px" checked-color="#1E74FF">
|
||||
记住密码
|
||||
</van-checkbox>
|
||||
</div>
|
||||
|
||||
<!-- 登录按钮 -->
|
||||
<div class="login-btn-wrapper">
|
||||
<van-button
|
||||
round
|
||||
v-if="!loading"
|
||||
block
|
||||
type="primary"
|
||||
round
|
||||
native-type="submit"
|
||||
:loading="loading"
|
||||
loading-text="登录中..."
|
||||
color="linear-gradient(135deg, $color-primary, $color-primary-light)"
|
||||
class="login-btn"
|
||||
>
|
||||
登 录
|
||||
</van-button>
|
||||
<van-button
|
||||
v-else
|
||||
block
|
||||
round
|
||||
loading
|
||||
loading-text="登录中..."
|
||||
color="linear-gradient(135deg, $color-primary, $color-primary-light)"
|
||||
class="login-btn"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 隐私政策 -->
|
||||
<div class="login-tips">
|
||||
登录即代表同意<span class="login-tips__link" @click="onShowPrivacy">《隐私政策》</span>
|
||||
</div>
|
||||
</van-form>
|
||||
</div>
|
||||
|
||||
<!-- 底部版权 -->
|
||||
<div class="login-footer">
|
||||
<span>v2.0.0</span>
|
||||
<!-- 底部技术支持 -->
|
||||
<div
|
||||
class="login-footer"
|
||||
:class="{ 'login-footer--hidden': isInputActive }"
|
||||
>
|
||||
<div class="login-footer__version">版本 {{ version }}</div>
|
||||
<div class="login-footer__support">技术支持:测绘股份 舆图科技</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// ══════════════════════════════════════════════
|
||||
// 登录页面 — 品牌背景 + 半透明表单
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
.login-page {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// ── NavBar 透明叠加在背景上方 ──
|
||||
.login-navbar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
|
||||
:deep(.van-nav-bar) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.van-nav-bar__content) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.van-nav-bar__title) {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
// 隐藏分割线
|
||||
:deep(.van-hairline--bottom::after) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 背景层 (全屏品牌背景图) ──
|
||||
.login-bg {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(180deg, #E8F0FE 0%, #F4F7F8 40%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
background: var(--color-bg-page);
|
||||
padding: 0 24px;
|
||||
padding-bottom: calc(20px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 8px;
|
||||
// ── Logo 区 ──
|
||||
.login-logo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 114px;
|
||||
|
||||
&__avatar {
|
||||
width: 78px;
|
||||
height: 78px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: #1A3973;
|
||||
margin-top: 12px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 登录子标题 ──
|
||||
.login-subtitle {
|
||||
font-size: 14px;
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
color: $color-text-regular;
|
||||
margin: 50px 0 12px 30px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.login-form-wrapper {
|
||||
// ── 表单 ──
|
||||
.login-form {
|
||||
width: 100%;
|
||||
background: var(--color-bg-card);
|
||||
border-radius: 12px;
|
||||
padding: 24px 16px;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.login-button-wrapper {
|
||||
margin-top: 24px;
|
||||
padding: 0 16px;
|
||||
// ── 输入框 (透明底 + 半透明白色背景) ──
|
||||
.login-field {
|
||||
width: calc(100% - 60px);
|
||||
margin: 0 30px 12px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
|
||||
:deep(.van-cell) {
|
||||
background: rgba(255, 255, 255, 0.9) !important;
|
||||
}
|
||||
|
||||
:deep(.van-field__control) {
|
||||
&::placeholder {
|
||||
color: $color-text-placeholder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login-footer {
|
||||
position: fixed;
|
||||
bottom: 32px;
|
||||
// ── 验证码图片 ──
|
||||
.login-captcha-img {
|
||||
width: 95px;
|
||||
height: 35px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
// ── 记住密码 ──
|
||||
.login-save {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin-right: 30px;
|
||||
font-size: 12px;
|
||||
color: var(--color-text-placeholder);
|
||||
color: $color-text-placeholder;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
// ── 登录按钮 ──
|
||||
.login-btn-wrapper {
|
||||
margin: 20px 30px;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
height: 44px;
|
||||
font-size: 16px;
|
||||
letter-spacing: 4px;
|
||||
|
||||
:deep(.van-button__text) {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 隐私政策提示 ──
|
||||
.login-tips {
|
||||
margin: 0 30px;
|
||||
font-size: 12px;
|
||||
color: $color-text-secondary;
|
||||
text-align: center;
|
||||
|
||||
&__link {
|
||||
color: $color-primary;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 底部 ──
|
||||
.login-footer {
|
||||
margin-top: auto;
|
||||
padding-top: 30px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
color: $color-text-secondary;
|
||||
transition: opacity 0.3s ease;
|
||||
line-height: 1.8;
|
||||
|
||||
&__version {
|
||||
font-size: 11px;
|
||||
color: $color-text-placeholder;
|
||||
}
|
||||
|
||||
&__support {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&--hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,37 +1,183 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 个人中心页面 (placeholder)
|
||||
* 个人中心页面 — 我的
|
||||
*
|
||||
* 展示用户基本信息,提供退出登录等功能入口。
|
||||
* 设计规格:
|
||||
* - NavBar: #1E74FF 背景, 透明标题区域, 右侧白色图标
|
||||
* - 顶部区域: 蓝色渐变背景 (264px), 用户头像 (56px 圆形) + 姓名/部门白色文字浮动其上
|
||||
* - 快捷入口: 3 列网格 (我的待办 / 我的已办 / 我的发起), 待办 Badge 角标
|
||||
* - 设置列表: van-cell-group, 图标 (edit / replay / orders-o), 版本信息
|
||||
* - 退出登录: 白色按钮, 红色文字, 圆角, 块级
|
||||
*/
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { showToast, showDialog } from 'vant'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
// ── 快捷入口 ──
|
||||
interface Shortcut {
|
||||
id: string
|
||||
label: string
|
||||
icon: string
|
||||
badge?: number
|
||||
route?: string
|
||||
}
|
||||
|
||||
const shortcuts: Shortcut[] = [
|
||||
{ id: 'pending', label: '我的待办', icon: 'todo-list-o', badge: 5, route: '/inspection' },
|
||||
{ id: 'done', label: '我的已办', icon: 'passed', route: '/inspectionRecords' },
|
||||
{ id: 'initiated', label: '我的发起', icon: 'add-square', route: '/inspectionProblem' },
|
||||
]
|
||||
|
||||
// ── 设置项 ──
|
||||
interface SettingItem {
|
||||
id: string
|
||||
label: string
|
||||
icon: string
|
||||
route?: string
|
||||
action?: () => void
|
||||
}
|
||||
|
||||
const settings: SettingItem[] = [
|
||||
{
|
||||
id: 'resetPwd',
|
||||
label: '修改密码',
|
||||
icon: 'edit',
|
||||
route: '/mine/resetPwd',
|
||||
},
|
||||
{
|
||||
id: 'clearCache',
|
||||
label: '清空缓存',
|
||||
icon: 'replay',
|
||||
action: onClearCache,
|
||||
},
|
||||
{
|
||||
id: 'privacyPolicy',
|
||||
label: '隐私政策',
|
||||
icon: 'orders-o',
|
||||
route: '/mine/privacyPolicy',
|
||||
},
|
||||
{
|
||||
id: 'version',
|
||||
label: '版本信息',
|
||||
icon: 'info-o',
|
||||
action: onShowVersion,
|
||||
},
|
||||
]
|
||||
|
||||
// ── 快捷入口点击 ──
|
||||
function onShortcutClick(item: Shortcut): void {
|
||||
if (item.route) {
|
||||
router.push(item.route)
|
||||
}
|
||||
}
|
||||
|
||||
// ── 设置项点击 ──
|
||||
function onSettingClick(item: SettingItem): void {
|
||||
if (item.route) {
|
||||
router.push(item.route)
|
||||
} else if (item.action) {
|
||||
item.action()
|
||||
}
|
||||
}
|
||||
|
||||
// ── 清空缓存 ──
|
||||
function onClearCache(): void {
|
||||
showDialog({
|
||||
title: '提示',
|
||||
message: '确定要清空缓存吗?',
|
||||
})
|
||||
.then(() => {
|
||||
showToast('缓存已清空')
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
// ── 版本信息 ──
|
||||
function onShowVersion(): void {
|
||||
showToast('版本 v2.0.0')
|
||||
}
|
||||
|
||||
// ── 退出登录 ──
|
||||
async function onLogout(): Promise<void> {
|
||||
try {
|
||||
await userStore.logout()
|
||||
router.replace('/login')
|
||||
} catch {
|
||||
router.replace('/login')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mine-page">
|
||||
<van-nav-bar title="我的" fixed placeholder />
|
||||
<!-- ═══════════ 顶部导航栏 ═══════════ -->
|
||||
<van-nav-bar title="我的" fixed placeholder>
|
||||
<template #right>
|
||||
<van-icon name="setting-o" size="20" @click="router.push('/menuEdit')" />
|
||||
<van-icon name="bell" size="20" style="margin-left: 14px" @click="router.push('/noticeList')" />
|
||||
</template>
|
||||
</van-nav-bar>
|
||||
|
||||
<div class="mine-content">
|
||||
<div class="user-card">
|
||||
<div class="avatar-placeholder"></div>
|
||||
<div class="user-info">
|
||||
<span class="user-name">未登录</span>
|
||||
<!-- ═══════════ 页面主体 ═══════════ -->
|
||||
<div class="mine-body">
|
||||
<!-- ── 顶部蓝色渐变区域 ── -->
|
||||
<div class="mine-header">
|
||||
<div class="header-bg"></div>
|
||||
<div class="header-user">
|
||||
<div class="user-avatar">
|
||||
<van-icon name="user-o" size="28" color="#ffffff" />
|
||||
</div>
|
||||
<div class="user-text">
|
||||
<span class="user-name">{{ userStore.userName || '未登录' }}</span>
|
||||
<span class="user-dept">{{ userStore.userInfo?.deptName || '' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<van-cell-group inset>
|
||||
<van-cell title="个人信息" is-link />
|
||||
<van-cell title="系统设置" is-link />
|
||||
<van-cell title="关于我们" is-link />
|
||||
<!-- ── 快捷入口 ── -->
|
||||
<div class="shortcuts">
|
||||
<div
|
||||
v-for="item in shortcuts"
|
||||
:key="item.id"
|
||||
class="shortcut-item"
|
||||
@click="onShortcutClick(item)"
|
||||
>
|
||||
<div class="shortcut-icon-box">
|
||||
<van-icon :name="item.icon" size="22" color="#1E74FF" />
|
||||
<span v-if="item.badge" class="shortcut-badge">{{ item.badge }}</span>
|
||||
</div>
|
||||
<span class="shortcut-label">{{ item.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── 设置列表 ── -->
|
||||
<van-cell-group inset class="setting-group">
|
||||
<van-cell
|
||||
v-for="item in settings"
|
||||
:key="item.id"
|
||||
:title="item.label"
|
||||
is-link
|
||||
@click="onSettingClick(item)"
|
||||
>
|
||||
<template #icon>
|
||||
<van-icon :name="item.icon" size="18" color="#1E74FF" class="cell-icon" />
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
|
||||
<!-- ── 退出登录 ── -->
|
||||
<div class="logout-wrapper">
|
||||
<van-button round block type="danger" @click="router.replace('/login')">
|
||||
<van-button round block class="logout-btn" @click="onLogout">
|
||||
退出登录
|
||||
</van-button>
|
||||
</div>
|
||||
|
||||
<!-- 底部安全区占位 -->
|
||||
<div class="safe-bottom"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -39,39 +185,184 @@ const router = useRouter()
|
||||
<style lang="scss" scoped>
|
||||
.mine-page {
|
||||
min-height: 100vh;
|
||||
background: var(--color-bg-page);
|
||||
background: var(--color-bg-page, #f4f7f8);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
// ── 导航栏 ──
|
||||
:deep(.van-nav-bar) {
|
||||
background: #1E74FF;
|
||||
|
||||
.van-nav-bar__title {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.van-nav-bar__left,
|
||||
.van-nav-bar__right {
|
||||
.van-icon {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mine-content {
|
||||
padding: 16px;
|
||||
// ── 页面主体 ──
|
||||
.mine-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.user-card {
|
||||
// ── 头部蓝色渐变区域 ──
|
||||
.mine-header {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 264px;
|
||||
overflow: hidden;
|
||||
|
||||
.header-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(180deg, #1E74FF 0%, #64B5F6 100%);
|
||||
}
|
||||
|
||||
.header-user {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 60px 24px 0;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
border: 2px solid rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.user-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.user-dept {
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 快捷入口 ──
|
||||
.shortcuts {
|
||||
display: flex;
|
||||
margin: -20px 12px 12px;
|
||||
padding: 18px 0;
|
||||
background: #ffffff;
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.shortcut-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.shortcut-icon-box {
|
||||
position: relative;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
border-radius: 50%;
|
||||
background: #f0f6ff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
background: var(--color-bg-card);
|
||||
border-radius: 12px;
|
||||
padding: 24px 16px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.avatar-placeholder {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-primary-bg);
|
||||
.shortcut-badge {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: -4px;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 9px;
|
||||
background: #ee0a24;
|
||||
color: #ffffff;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-primary);
|
||||
.shortcut-label {
|
||||
font-size: 13px;
|
||||
color: var(--color-text-regular, #666);
|
||||
}
|
||||
|
||||
// ── 设置列表 ──
|
||||
.setting-group {
|
||||
margin: 0 12px 12px;
|
||||
|
||||
:deep(.van-cell-group) {
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.van-cell) {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cell-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 退出登录 ──
|
||||
.logout-wrapper {
|
||||
margin-top: 24px;
|
||||
padding: 0 16px;
|
||||
margin: 24px 16px 0;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
:deep(.van-button__text) {
|
||||
color: #ee0a24;
|
||||
}
|
||||
|
||||
:deep(.van-button) {
|
||||
background: #ffffff;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 底部安全区 ──
|
||||
.safe-bottom {
|
||||
height: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user