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 Component Base ─────────────────────────────────────
|
||||||
.card {
|
.card {
|
||||||
background: var(--color-bg-card);
|
background: var(--color-bg-card);
|
||||||
border-radius: var(--radius-md);
|
border-radius: 10px;
|
||||||
box-shadow: var(--shadow-sm);
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
||||||
padding: $spacing-md;
|
padding: 20px 16px;
|
||||||
margin: $spacing-sm $spacing-md;
|
margin: $spacing-sm $spacing-md;
|
||||||
|
|
||||||
&--elevated {
|
&--elevated {
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// ── Brand Colors ────────────────────────────────────────────
|
// ── Brand Colors ────────────────────────────────────────────
|
||||||
$color-primary: #2196F3;
|
$color-primary: #1E74FF;
|
||||||
$color-primary-light: #64B5F6;
|
$color-primary-light: #64B5F6;
|
||||||
$color-primary-dark: #1976D2;
|
$color-primary-dark: #1A3973;
|
||||||
$color-primary-bg: #E3F2FD;
|
$color-primary-bg: #E3F2FD;
|
||||||
|
|
||||||
$color-success: #07C160;
|
$color-success: #07C160;
|
||||||
@@ -45,7 +45,7 @@ $color-text-placeholder:$color-gray-5;
|
|||||||
$color-text-inverse: $color-white;
|
$color-text-inverse: $color-white;
|
||||||
|
|
||||||
// ── Background Colors ───────────────────────────────────────
|
// ── Background Colors ───────────────────────────────────────
|
||||||
$color-bg-page: $color-gray-1;
|
$color-bg-page: #F4F7F8;
|
||||||
$color-bg-card: $color-white;
|
$color-bg-card: $color-white;
|
||||||
$color-bg-elevated: $color-white;
|
$color-bg-elevated: $color-white;
|
||||||
$color-bg-mask: rgba(0, 0, 0, 0.6);
|
$color-bg-mask: rgba(0, 0, 0, 0.6);
|
||||||
|
|||||||
@@ -1,55 +1,241 @@
|
|||||||
<script setup lang="ts">
|
<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 { useRouter } from 'vue-router'
|
||||||
|
import { showToast } from 'vant'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
/** 当前激活的 Tab */
|
// ── Tabbar ──
|
||||||
const active = ref(0)
|
const activeTab = ref(0)
|
||||||
|
|
||||||
/**
|
|
||||||
* Tab 切换处理
|
|
||||||
*/
|
|
||||||
function onTabChange(index: number): void {
|
function onTabChange(index: number): void {
|
||||||
active.value = index
|
activeTab.value = index
|
||||||
if (index === 0) {
|
if (index === 0) router.replace('/home')
|
||||||
router.replace('/home')
|
else if (index === 1) router.replace('/map')
|
||||||
} else if (index === 1) {
|
else if (index === 2) router.replace('/mine')
|
||||||
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="home-page">
|
<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="home-body">
|
||||||
<div class="welcome-card">
|
<!-- ── Banner 轮播 ── -->
|
||||||
<h2>欢迎使用智慧水务</h2>
|
<div class="banner-wrapper">
|
||||||
<p>移动端管理平台</p>
|
<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>
|
||||||
|
|
||||||
<div class="feature-grid">
|
<!-- ── 提示栏 ── -->
|
||||||
<div class="feature-item" v-for="i in 4" :key="i">
|
<div class="alert-bar" @click="router.push('/instructionList')">
|
||||||
<div class="feature-icon"></div>
|
<van-icon name="warning-o" color="#FF976A" size="18" />
|
||||||
<span class="feature-label">功能模块 {{ i }}</span>
|
<span class="alert-text">{{ alertMessage }}</span>
|
||||||
</div>
|
<van-icon name="arrow" color="#C8C9CC" size="14" />
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 底部导航栏 -->
|
<!-- ── 菜单面板 (van-collapse) ── -->
|
||||||
<van-tabbar v-model="active" :fixed="true" :placeholder="true" @change="onTabChange">
|
<van-collapse
|
||||||
<van-tabbar-item icon="home-o" name="首页">首页</van-tabbar-item>
|
v-model="activePanels"
|
||||||
<van-tabbar-item icon="user-o" name="我的">我的</van-tabbar-item>
|
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="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>
|
</van-tabbar>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -58,68 +244,192 @@ function onTabChange(index: number): void {
|
|||||||
.home-page {
|
.home-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: var(--color-bg-page);
|
background: var(--color-bg-page);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
// ── 导航栏 ──
|
||||||
:deep(.van-nav-bar) {
|
:deep(.van-nav-bar) {
|
||||||
background: var(--color-primary);
|
background: #1E74FF;
|
||||||
--van-nav-bar-title-text-color: #fff;
|
|
||||||
|
.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 {
|
.nav-left-brand {
|
||||||
padding: 16px;
|
font-size: 18px;
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.welcome-card {
|
// ── 页面主体 ──
|
||||||
background: var(--color-bg-card);
|
.home-body {
|
||||||
border-radius: 12px;
|
flex: 1;
|
||||||
padding: 32px 24px;
|
overflow-y: auto;
|
||||||
text-align: center;
|
-webkit-overflow-scrolling: touch;
|
||||||
box-shadow: var(--shadow-sm);
|
|
||||||
margin-bottom: 16px;
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 20px;
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
// ── Banner ──
|
||||||
font-size: 14px;
|
.banner-wrapper {
|
||||||
color: var(--color-text-secondary);
|
margin: 8px 12px;
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.feature-grid {
|
.banner-swipe {
|
||||||
display: grid;
|
border-radius: 10px;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
overflow: hidden;
|
||||||
gap: 12px;
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.feature-item {
|
.banner-slide {
|
||||||
background: var(--color-bg-card);
|
width: 100%;
|
||||||
border-radius: 12px;
|
height: 180px;
|
||||||
padding: 24px 16px;
|
background: linear-gradient(135deg, #1E74FF 0%, #64B5F6 100%);
|
||||||
text-align: center;
|
display: flex;
|
||||||
box-shadow: var(--shadow-sm);
|
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;
|
cursor: pointer;
|
||||||
transition: background-color var(--transition-fast);
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background: var(--color-border-light);
|
background: #f7f8fa;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.feature-icon {
|
.alert-text {
|
||||||
width: 48px;
|
flex: 1;
|
||||||
height: 48px;
|
font-size: 13px;
|
||||||
border-radius: 12px;
|
|
||||||
background: var(--color-primary-bg);
|
|
||||||
margin: 0 auto 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-label {
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--color-text-regular);
|
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>
|
</style>
|
||||||
|
|||||||
@@ -1,153 +1,415 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
/**
|
/**
|
||||||
* 登录页面 (placeholder)
|
* 登录页面
|
||||||
*
|
*
|
||||||
* 包含用户名、密码输入表单,调用 userStore.login 完成登录。
|
* 全屏品牌背景图 + 半透明表单的登录界面。
|
||||||
* 登录成功后跳转至首页(或 redirect 参数指定的页面)。
|
* 支持账号密码登录,可选验证码。
|
||||||
*/
|
*/
|
||||||
import { ref, reactive } from 'vue'
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { showSuccessToast, showFailToast } from 'vant'
|
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
|
import { useAppStore } from '@/stores/app'
|
||||||
|
import { getCaptcha } from '@/api/common'
|
||||||
|
import { showToast, Checkbox } from 'vant'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
|
||||||
const userStore = useUserStore()
|
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 loading = ref(false)
|
||||||
|
const savePsw = ref(true)
|
||||||
|
const isInputActive = ref(false)
|
||||||
|
|
||||||
/** 登录表单数据 */
|
// 恢复记住的账号密码
|
||||||
const loginForm = reactive({
|
username.value = localStorage.getItem('yuto-user') || ''
|
||||||
username: '',
|
password.value = localStorage.getItem('yuto-password') || ''
|
||||||
password: '',
|
|
||||||
})
|
|
||||||
|
|
||||||
/** 表单校验规则 */
|
// ── 获取验证码 ──
|
||||||
const rules = {
|
async function fetchCaptcha(): Promise<void> {
|
||||||
username: [{ required: true, message: '请输入用户名' }],
|
try {
|
||||||
password: [{ required: true, message: '请输入密码' }],
|
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 onSubmit(): Promise<void> {
|
||||||
|
if (!username.value) {
|
||||||
|
showToast('请输入账号')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!password.value) {
|
||||||
|
showToast('请输入密码')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (captchaEnabled.value && !code.value) {
|
||||||
|
showToast('请输入验证码')
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理登录提交
|
|
||||||
*/
|
|
||||||
async function handleSubmit(): Promise<void> {
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
await userStore.login({
|
await userStore.login({
|
||||||
username: loginForm.username,
|
username: username.value,
|
||||||
password: loginForm.password,
|
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'
|
localStorage.setItem('yuto-user', username.value)
|
||||||
router.replace(redirect)
|
if (savePsw.value) {
|
||||||
} catch (err) {
|
localStorage.setItem('yuto-password', password.value)
|
||||||
const msg = err instanceof Error ? err.message : '登录失败,请重试'
|
} else {
|
||||||
showFailToast(msg)
|
localStorage.removeItem('yuto-password')
|
||||||
|
}
|
||||||
|
|
||||||
|
showToast('登录成功')
|
||||||
|
router.replace('/home')
|
||||||
|
} catch {
|
||||||
|
// 错误已在拦截器中统一处理
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="login-page">
|
<div class="login-page">
|
||||||
<!-- 头部 Logo 区域 -->
|
<!-- 透明导航栏 (叠加在背景图之上) -->
|
||||||
<div class="login-header">
|
<van-nav-bar
|
||||||
<h1 class="login-title">舆图智慧水务</h1>
|
:title="''"
|
||||||
<p class="login-subtitle">移动端管理平台</p>
|
:left-arrow="false"
|
||||||
|
:border="false"
|
||||||
|
:fixed="false"
|
||||||
|
class="login-navbar"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 背景图 + 内容层 -->
|
||||||
|
<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>
|
||||||
|
|
||||||
<!-- 登录表单 -->
|
<!-- 登录提示 -->
|
||||||
<div class="login-form-wrapper">
|
<div class="login-subtitle">账号密码登录</div>
|
||||||
<van-form @submit="handleSubmit">
|
|
||||||
|
<!-- 表单 -->
|
||||||
|
<van-form @submit="onSubmit" class="login-form">
|
||||||
<van-field
|
<van-field
|
||||||
v-model="loginForm.username"
|
v-model="username"
|
||||||
name="username"
|
placeholder="请输入账号"
|
||||||
label="用户名"
|
class="login-field"
|
||||||
placeholder="请输入用户名"
|
:rules="[{ required: true, message: '请输入账号' }]"
|
||||||
:rules="rules.username"
|
|
||||||
clearable
|
|
||||||
left-icon="user-o"
|
|
||||||
/>
|
/>
|
||||||
<van-field
|
<van-field
|
||||||
v-model="loginForm.password"
|
v-model="password"
|
||||||
name="password"
|
|
||||||
label="密码"
|
|
||||||
placeholder="请输入密码"
|
|
||||||
type="password"
|
type="password"
|
||||||
:rules="rules.password"
|
placeholder="请输入密码"
|
||||||
left-icon="lock"
|
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
|
<van-button
|
||||||
round
|
v-if="!loading"
|
||||||
block
|
block
|
||||||
type="primary"
|
round
|
||||||
native-type="submit"
|
native-type="submit"
|
||||||
:loading="loading"
|
color="linear-gradient(135deg, $color-primary, $color-primary-light)"
|
||||||
loading-text="登录中..."
|
class="login-btn"
|
||||||
>
|
>
|
||||||
登 录
|
登 录
|
||||||
</van-button>
|
</van-button>
|
||||||
</div>
|
<van-button
|
||||||
</van-form>
|
v-else
|
||||||
|
block
|
||||||
|
round
|
||||||
|
loading
|
||||||
|
loading-text="登录中..."
|
||||||
|
color="linear-gradient(135deg, $color-primary, $color-primary-light)"
|
||||||
|
class="login-btn"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 底部版权 -->
|
<!-- 隐私政策 -->
|
||||||
<div class="login-footer">
|
<div class="login-tips">
|
||||||
<span>v2.0.0</span>
|
登录即代表同意<span class="login-tips__link" @click="onShowPrivacy">《隐私政策》</span>
|
||||||
|
</div>
|
||||||
|
</van-form>
|
||||||
|
|
||||||
|
<!-- 底部技术支持 -->
|
||||||
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
// ══════════════════════════════════════════════
|
||||||
|
// 登录页面 — 品牌背景 + 半透明表单
|
||||||
|
// ══════════════════════════════════════════════
|
||||||
|
|
||||||
.login-page {
|
.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;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
padding-bottom: calc(20px + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 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;
|
justify-content: center;
|
||||||
min-height: 100vh;
|
|
||||||
background: var(--color-bg-page);
|
|
||||||
padding: 0 24px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-header {
|
&__img {
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-title {
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-primary);
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-subtitle {
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form-wrapper {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: var(--color-bg-card);
|
height: 100%;
|
||||||
border-radius: 12px;
|
object-fit: cover;
|
||||||
padding: 24px 16px;
|
|
||||||
box-shadow: var(--shadow-md);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-button-wrapper {
|
&__title {
|
||||||
margin-top: 24px;
|
font-size: 22px;
|
||||||
padding: 0 16px;
|
font-weight: 700;
|
||||||
|
color: #1A3973;
|
||||||
|
margin-top: 12px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-footer {
|
// ── 登录子标题 ──
|
||||||
position: fixed;
|
.login-subtitle {
|
||||||
bottom: 32px;
|
font-weight: 500;
|
||||||
|
font-size: 16px;
|
||||||
|
color: $color-text-regular;
|
||||||
|
margin: 50px 0 12px 30px;
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 表单 ──
|
||||||
|
.login-form {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 输入框 (透明底 + 半透明白色背景) ──
|
||||||
|
.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-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;
|
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>
|
</style>
|
||||||
|
|||||||
@@ -1,37 +1,183 @@
|
|||||||
<script setup lang="ts">
|
<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 { useRouter } from 'vue-router'
|
||||||
|
import { showToast, showDialog } from 'vant'
|
||||||
|
import { useUserStore } from '@/stores/user'
|
||||||
|
|
||||||
const router = useRouter()
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="mine-page">
|
<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="mine-body">
|
||||||
<div class="avatar-placeholder"></div>
|
<!-- ── 顶部蓝色渐变区域 ── -->
|
||||||
<div class="user-info">
|
<div class="mine-header">
|
||||||
<span class="user-name">未登录</span>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<van-cell-group inset>
|
<!-- ── 快捷入口 ── -->
|
||||||
<van-cell title="个人信息" is-link />
|
<div class="shortcuts">
|
||||||
<van-cell title="系统设置" is-link />
|
<div
|
||||||
<van-cell title="关于我们" is-link />
|
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>
|
</van-cell-group>
|
||||||
|
|
||||||
|
<!-- ── 退出登录 ── -->
|
||||||
<div class="logout-wrapper">
|
<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>
|
</van-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 底部安全区占位 -->
|
||||||
|
<div class="safe-bottom"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -39,39 +185,184 @@ const router = useRouter()
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.mine-page {
|
.mine-page {
|
||||||
min-height: 100vh;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mine-content {
|
.van-nav-bar__left,
|
||||||
padding: 16px;
|
.van-nav-bar__right {
|
||||||
|
.van-icon {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-card {
|
// ── 页面主体 ──
|
||||||
|
.mine-body {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 头部蓝色渐变区域 ──
|
||||||
|
.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;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 14px;
|
||||||
background: var(--color-bg-card);
|
padding: 60px 24px 0;
|
||||||
border-radius: 12px;
|
|
||||||
padding: 24px 16px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
box-shadow: var(--shadow-sm);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-placeholder {
|
.user-avatar {
|
||||||
width: 56px;
|
width: 56px;
|
||||||
height: 56px;
|
height: 56px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: var(--color-primary-bg);
|
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 {
|
.user-name {
|
||||||
font-size: 16px;
|
font-size: 18px;
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
color: var(--color-text-primary);
|
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;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 {
|
.logout-wrapper {
|
||||||
margin-top: 24px;
|
margin: 24px 16px 0;
|
||||||
padding: 0 16px;
|
}
|
||||||
|
|
||||||
|
.logout-btn {
|
||||||
|
:deep(.van-button__text) {
|
||||||
|
color: #ee0a24;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.van-button) {
|
||||||
|
background: #ffffff;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 底部安全区 ──
|
||||||
|
.safe-bottom {
|
||||||
|
height: 16px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user