保留页面布局
This commit is contained in:
@@ -1,7 +1,16 @@
|
||||
<template>
|
||||
<view class="layout-root">
|
||||
<!-- 移动端遮罩层 -->
|
||||
<view
|
||||
v-if="isMobile && isMobileMenuOpen"
|
||||
class="mobile-mask"
|
||||
@click="isMobileMenuOpen = false"
|
||||
></view>
|
||||
|
||||
<!-- 主侧边栏 (CRMEB风格) -->
|
||||
<AdminAside
|
||||
class="admin-sidebar"
|
||||
:class="{ 'mobile-aside-open': isMobileMenuOpen }"
|
||||
:collapsed="isMainAsideCollapsed"
|
||||
:topMenus="topMenus"
|
||||
:activeTopMenuId="activeTopMenuId"
|
||||
@@ -12,7 +21,7 @@
|
||||
|
||||
<!-- 二级侧边栏 (CRMEB风格 - 内容区左侧) -->
|
||||
<AdminSubSider
|
||||
v-if="showSubSider"
|
||||
v-if="showSubSider && !isMobile"
|
||||
:topMenuTitle="activeTopMenuTitle"
|
||||
:groups="activeGroups"
|
||||
:routes="activeRoutes"
|
||||
@@ -25,19 +34,22 @@
|
||||
<!-- 右侧内容区 -->
|
||||
<view
|
||||
class="main"
|
||||
:style="{ marginLeft: mainLeft }"
|
||||
:style="{ marginLeft: isMobile ? '0' : mainLeft }"
|
||||
>
|
||||
<!-- 顶部导航栏 -->
|
||||
<AdminHeader
|
||||
:breadcrumb="breadcrumb"
|
||||
:hasNotification="hasNotification"
|
||||
:isMobile="isMobile"
|
||||
@toggle-mobile-menu="isMobileMenuOpen = !isMobileMenuOpen"
|
||||
@search="onSearch"
|
||||
@refresh="onRefresh"
|
||||
@notify="onNotify"
|
||||
/>
|
||||
|
||||
<!-- 标签页 (CRMEB风格) -->
|
||||
<!-- 标签页 (CRMEB风格) - 移动端可以隐藏或滚动 -->
|
||||
<AdminTagsView
|
||||
v-if="!isMobile"
|
||||
:tabs="tabs"
|
||||
:activeTabId="activeRouteId"
|
||||
@tab-click="onTabClick"
|
||||
@@ -48,7 +60,7 @@
|
||||
|
||||
<!-- 内容展示区 (内部路由渲染) -->
|
||||
<view class="content-scroll">
|
||||
<view class="content-inner">
|
||||
<view class="content-inner" :style="{ padding: isMobile ? '12px' : '16px' }">
|
||||
<component :is="currentComponent" />
|
||||
</view>
|
||||
<AdminFooter />
|
||||
@@ -80,6 +92,9 @@ import {
|
||||
tabs,
|
||||
isMainAsideCollapsed,
|
||||
showSubSider,
|
||||
windowWidth,
|
||||
isMobile,
|
||||
isMobileMenuOpen,
|
||||
openRoute,
|
||||
closeTab,
|
||||
closeOtherTabs,
|
||||
@@ -194,6 +209,18 @@ function onNotify(): void {
|
||||
|
||||
onMounted(() => {
|
||||
initNavState()
|
||||
|
||||
// 初始化窗口宽度
|
||||
windowWidth.value = uni.getWindowInfo().windowWidth
|
||||
|
||||
// 监听窗口变化
|
||||
uni.onWindowResize((res) => {
|
||||
windowWidth.value = res.size.windowWidth
|
||||
// 窗口变大时自动关闭移动端菜单
|
||||
if (windowWidth.value >= 768) {
|
||||
isMobileMenuOpen.value = false
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -204,6 +231,32 @@ onMounted(() => {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
background: #f0f2f5;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 移动端侧边栏样式 */
|
||||
.mobile-aside {
|
||||
position: absolute;
|
||||
left: -100px; /* 隐藏在左侧 */
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 1001;
|
||||
transition: transform 0.3s ease;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.mobile-aside-open {
|
||||
transform: translateX(100px); /* 移入视图 */
|
||||
}
|
||||
|
||||
.mobile-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.main {
|
||||
@@ -213,11 +266,35 @@ onMounted(() => {
|
||||
min-height: 100vh;
|
||||
transition: margin-left 0.3s ease;
|
||||
background: #f0f2f5;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 响应式强制覆盖 */
|
||||
@media screen and (max-width: 768px) {
|
||||
.main {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
/* 强行改变侧边栏布局模式 */
|
||||
.admin-sidebar {
|
||||
position: absolute !important;
|
||||
left: -100px !important; /* 隐藏在左侧,假设 ASIDE_W 是 96 */
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 1001;
|
||||
transition: transform 0.3s ease !important;
|
||||
}
|
||||
|
||||
/* 展开时的状态 */
|
||||
.mobile-aside-open {
|
||||
transform: translateX(100px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.content-scroll {
|
||||
flex: 1;
|
||||
overflow-y: scroll;
|
||||
overflow-x: auto; /* 允许横向滚动,兼容极端窄屏 */
|
||||
background: #f0f2f5;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
<template>
|
||||
<view class="header">
|
||||
<view class="header-left">
|
||||
<text class="crumb" v-for="(item, index) in breadcrumb" :key="item.id">
|
||||
{{ item.title }}
|
||||
<text v-if="index < breadcrumb.length - 1" class="separator"> / </text>
|
||||
</text>
|
||||
<!-- 移动端菜单切换按钮 (CSS 控制显隐) -->
|
||||
<view class="menu-toggle mobile-only" @click="$emit('toggle-mobile-menu')">
|
||||
<text class="menu-icon">☰</text>
|
||||
</view>
|
||||
|
||||
<view class="breadcrumb-container desktop-only">
|
||||
<text class="crumb" v-for="(item, index) in breadcrumb" :key="item.id">
|
||||
{{ item.title }}
|
||||
<text v-if="index < breadcrumb.length - 1" class="separator"> / </text>
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 移动端简单标题 (CSS 控制显隐) -->
|
||||
<text class="mobile-title mobile-only">{{ currentTitle }}</text>
|
||||
</view>
|
||||
|
||||
<view class="header-right">
|
||||
<view class="hbtn" @click="$emit('search')"><text>🔍</text></view>
|
||||
<view class="hbtn" @click="$emit('refresh')"><text>⟳</text></view>
|
||||
<view v-if="!isMobile" class="hbtn" @click="$emit('refresh')"><text>⟳</text></view>
|
||||
<view class="hbtn" @click="$emit('notify')">
|
||||
<text>🔔</text>
|
||||
<view class="dot" v-if="hasNotification"></view>
|
||||
@@ -19,16 +29,27 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
defineProps<{
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
breadcrumb: Array<{id: string, title: string}>
|
||||
hasNotification: boolean
|
||||
isMobile: boolean
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
(e:'search'): void
|
||||
(e:'refresh'): void
|
||||
(e:'notify'): void
|
||||
(e:'toggle-mobile-menu'): void
|
||||
}>()
|
||||
|
||||
const currentTitle = computed((): string => {
|
||||
if (props.breadcrumb.length > 0) {
|
||||
return props.breadcrumb[props.breadcrumb.length - 1].title
|
||||
}
|
||||
return '管理后台'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -49,11 +70,54 @@ defineEmits<{
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
margin-right: 12px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
font-size: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.mobile-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.breadcrumb-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.crumb {
|
||||
color: #374151;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 响应式控制 */
|
||||
.mobile-only {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.desktop-only {
|
||||
display: none !important;
|
||||
}
|
||||
.mobile-only {
|
||||
display: flex !important;
|
||||
}
|
||||
.header-right {
|
||||
gap: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.separator {
|
||||
color: #d1d5db;
|
||||
margin: 0 8px;
|
||||
|
||||
@@ -1,74 +1,54 @@
|
||||
<template>
|
||||
<view class="home-page">
|
||||
<!-- 数据统计卡片行 -->
|
||||
<view class="stats-row">
|
||||
<!-- 销售额卡片 -->
|
||||
<view class="stat-card">
|
||||
<view class="card-header">
|
||||
<text class="card-title">销售额</text>
|
||||
<view class="tag today">今日</view>
|
||||
</view>
|
||||
<view class="card-value">91.1</view>
|
||||
<view class="card-meta">
|
||||
<text class="meta-text">昨日 2740</text>
|
||||
<text class="meta-trend down">日环比 -96.67% ▼</text>
|
||||
</view>
|
||||
<view class="card-footer">
|
||||
<text class="footer-label">本月销售额</text>
|
||||
<text class="footer-value">2831.1元</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 数据统计卡片行 (使用统一响应式网格) -->
|
||||
<view class="kpi-grid">
|
||||
<KpiMiniCard
|
||||
class="stat-card"
|
||||
title="销售额"
|
||||
tagText="今日"
|
||||
:valueText="statsData.sales.today.toString()"
|
||||
:metaLeft="'昨日 ' + statsData.sales.yesterday"
|
||||
:metaRight="'日环比 ' + statsData.sales.trend + '%'"
|
||||
:trend="statsData.sales.trend > 0 ? 'up' : 'down'"
|
||||
footerLeftText="本月销售额"
|
||||
:footerRightText="statsData.sales.monthTotal + '元'"
|
||||
/>
|
||||
|
||||
<!-- 用户访问量卡片 -->
|
||||
<view class="stat-card">
|
||||
<view class="card-header">
|
||||
<text class="card-title">用户访问量</text>
|
||||
<view class="tag today">今日</view>
|
||||
</view>
|
||||
<view class="card-value">224</view>
|
||||
<view class="card-meta">
|
||||
<text class="meta-text">昨日 136</text>
|
||||
<text class="meta-trend up">日环比 64.7% ▲</text>
|
||||
</view>
|
||||
<view class="card-footer">
|
||||
<text class="footer-label">本月访问量</text>
|
||||
<text class="footer-value">360Pv</text>
|
||||
</view>
|
||||
</view>
|
||||
<KpiMiniCard
|
||||
class="stat-card"
|
||||
title="用户访问量"
|
||||
tagText="今日"
|
||||
:valueText="statsData.visits.today.toString()"
|
||||
:metaLeft="'昨日 ' + statsData.visits.yesterday"
|
||||
:metaRight="'日环比 ' + statsData.visits.trend + '%'"
|
||||
:trend="statsData.visits.trend > 0 ? 'up' : 'down'"
|
||||
footerLeftText="本月访问量"
|
||||
:footerRightText="statsData.visits.monthTotal + 'Pv'"
|
||||
/>
|
||||
|
||||
<!-- 订单量卡片 -->
|
||||
<view class="stat-card">
|
||||
<view class="card-header">
|
||||
<text class="card-title">订单量</text>
|
||||
<view class="tag today">今日</view>
|
||||
</view>
|
||||
<view class="card-value">4</view>
|
||||
<view class="card-meta">
|
||||
<text class="meta-text">昨日 8</text>
|
||||
<text class="meta-trend down">日环比 -50% ▼</text>
|
||||
</view>
|
||||
<view class="card-footer">
|
||||
<text class="footer-label">本月订单量</text>
|
||||
<text class="footer-value">12单</text>
|
||||
</view>
|
||||
</view>
|
||||
<KpiMiniCard
|
||||
class="stat-card"
|
||||
title="订单量"
|
||||
tagText="今日"
|
||||
:valueText="statsData.orders.today.toString()"
|
||||
:metaLeft="'昨日 ' + statsData.orders.yesterday"
|
||||
:metaRight="'日环比 ' + statsData.orders.trend + '%'"
|
||||
:trend="statsData.orders.trend > 0 ? 'up' : 'down'"
|
||||
footerLeftText="本月订单量"
|
||||
:footerRightText="statsData.orders.monthTotal + '单'"
|
||||
/>
|
||||
|
||||
<!-- 新增用户卡片 -->
|
||||
<view class="stat-card">
|
||||
<view class="card-header">
|
||||
<text class="card-title">新增用户</text>
|
||||
<view class="tag today">今日</view>
|
||||
</view>
|
||||
<view class="card-value">21</view>
|
||||
<view class="card-meta">
|
||||
<text class="meta-text">昨日 6</text>
|
||||
<text class="meta-trend up">日环比 250% ▲</text>
|
||||
</view>
|
||||
<view class="card-footer">
|
||||
<text class="footer-label">本月新增用户</text>
|
||||
<text class="footer-value">27人</text>
|
||||
</view>
|
||||
</view>
|
||||
<KpiMiniCard
|
||||
class="stat-card"
|
||||
title="新增用户"
|
||||
tagText="今日"
|
||||
:valueText="statsData.users.today.toString()"
|
||||
:metaLeft="'昨日 ' + statsData.users.yesterday"
|
||||
:metaRight="'日环比 ' + statsData.users.trend + '%'"
|
||||
:trend="statsData.users.trend > 0 ? 'up' : 'down'"
|
||||
footerLeftText="本月新增用户"
|
||||
:footerRightText="statsData.users.monthTotal + '人'"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 订单趋势图表区 -->
|
||||
@@ -156,6 +136,7 @@ import { ref, computed, onMounted } from 'vue'
|
||||
import AnalyticsComboChart from '@/components/analytics/AnalyticsComboChart.uvue'
|
||||
import AnalyticsAreaChart from '@/components/analytics/AnalyticsAreaChart.uvue'
|
||||
import AnalyticsPieChart from '@/components/analytics/AnalyticsPieChart.uvue'
|
||||
import KpiMiniCard from '@/pages/mall/admin/homePage/components/KpiMiniCard.uvue'
|
||||
|
||||
// Filter periods
|
||||
const periods = [
|
||||
@@ -249,90 +230,9 @@ const statsData = ref({
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.stats-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* 统计卡片 */
|
||||
/* 兼容旧布局标识,样式逻辑已由 .kpi-grid 接管 */
|
||||
.stat-card {
|
||||
flex: 1;
|
||||
min-width: 280px;
|
||||
background-color: #ffffff;
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||||
transition: box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
/* 卡片头部 */
|
||||
.card-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 14px;
|
||||
color: #666666;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: 2px 8px;
|
||||
border-radius: 2px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.tag.today {
|
||||
background-color: #e8f4ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
/* 卡片主值 */
|
||||
.card-value {
|
||||
font-size: 32px;
|
||||
font-weight: 500;
|
||||
color: #262626;
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 卡片元数据 */
|
||||
.card-meta {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.meta-text {
|
||||
font-size: 13px;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
|
||||
.meta-trend {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.meta-trend.up {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.meta-trend.down {
|
||||
color: #52c41a;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
/* 图表区样式 */
|
||||
@@ -453,14 +353,11 @@ const statsData = ref({
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media screen and (max-width: 1400px) {
|
||||
.stat-card {
|
||||
min-width: calc(50% - 8px);
|
||||
@media screen and (max-width: 768px) {
|
||||
.home-page {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
|
||||
.bottom-charts {
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -468,24 +365,25 @@ const statsData = ref({
|
||||
.half-width {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.home-page {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.stats-row {
|
||||
/* 调整图表头部在移动端的展示 */
|
||||
.chart-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
min-width: 100%;
|
||||
padding: 16px;
|
||||
.header-right {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-value {
|
||||
font-size: 28px;
|
||||
.period-tabs {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.period-tab {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -38,8 +38,18 @@ export const tabs = ref<TabItem[]>([])
|
||||
/** 是否折叠主侧边栏 */
|
||||
export const isMainAsideCollapsed = ref<boolean>(false)
|
||||
|
||||
/** 屏幕宽度 */
|
||||
export const windowWidth = ref<number>(1024)
|
||||
|
||||
/** 是否为移动端布局 (width < 768) */
|
||||
export const isMobile = computed<boolean>(() => windowWidth.value < 768)
|
||||
|
||||
/** 移动端菜单是否展开 */
|
||||
export const isMobileMenuOpen = ref<boolean>(false)
|
||||
|
||||
/** 是否显示二级侧边栏 */
|
||||
export const showSubSider = computed<boolean>(() => {
|
||||
if (isMobile.value) return false // 移动端不显式二级侧边栏在主体区域
|
||||
const topMenus = getTopMenus()
|
||||
const activeMenu = topMenus.find(m => m.id === activeTopMenuId.value)
|
||||
return activeMenu ? activeMenu.groups.length > 0 : false
|
||||
|
||||
36
layouts/admin/styles/admin-responsive.css
Normal file
36
layouts/admin/styles/admin-responsive.css
Normal file
@@ -0,0 +1,36 @@
|
||||
/* 统一 KPI 统计网格响应式规范 */
|
||||
|
||||
.kpi-grid {
|
||||
display: grid !important;
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 规则 1:屏幕宽度 >= 1200px -> 固定 4 列 */
|
||||
@media (min-width: 1200px) {
|
||||
.kpi-grid {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 规则 2:768px <= 屏幕宽度 < 1200px -> 固定 2 列 */
|
||||
@media (min-width: 768px) and (max-width: 1199.98px) {
|
||||
.kpi-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 规则 3:屏幕宽度 < 768px -> 固定 1 列 */
|
||||
@media (max-width: 767.98px) {
|
||||
.kpi-grid {
|
||||
grid-template-columns: repeat(1, minmax(0, 1fr)) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 强制子项允许收缩,防止内部长文本撑爆网格 */
|
||||
.kpi-grid > * {
|
||||
min-width: 0 !important;
|
||||
flex: none !important; /* 覆盖旧的 flex 逻辑 */
|
||||
width: auto !important; /* 让 grid 控制宽度 */
|
||||
}
|
||||
Reference in New Issue
Block a user