feat: adapted mobile screen

This commit is contained in:
chansee97 2025-08-02 01:06:47 +08:00
parent 1b4639d5d8
commit 3144acbc39
10 changed files with 417 additions and 315 deletions

6
pnpm-workspace.yaml Normal file
View File

@ -0,0 +1,6 @@
ignoredBuiltDependencies:
- '@parcel/watcher'
- esbuild
- simple-git-hooks
- vue-demi
- unrs-resolver

View File

@ -0,0 +1,38 @@
<script setup lang="ts">
import Search from '../header/Search.vue'
import Notices from '../header/Notices.vue'
import UserCenter from '../header/UserCenter.vue'
import Setting from './Setting.vue'
const showDrawer = defineModel<boolean>('show', { default: false })
</script>
<template>
<n-drawer
v-model:show="showDrawer"
:width="280"
placement="right"
:mask-closable="true"
:close-on-esc="true"
>
<n-drawer-content :native-scrollbar="false" :body-content-style="{ padding: '0' }">
<template #header>
<div class="flex">
<UserCenter />
<div class="ml-auto" />
<Search />
<Notices />
</div>
</template>
<slot />
<template #footer>
<DarkModeSwitch />
<LangsSwitch />
<div class="ml-auto" />
<Setting />
</template>
</n-drawer-content>
</n-drawer>
</template>

View File

@ -1,7 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { useBoolean } from '@/hooks' import { useBoolean } from '@/hooks'
import { useRouteStore } from '@/store' import { useAppStore, useRouteStore } from '@/store'
const appStore = useAppStore()
const routeStore = useRouteStore() const routeStore = useRouteStore()
// //
@ -143,13 +144,14 @@ function handleMouseEnter(index: number) {
<template> <template>
<CommonWrapper @click="openModal"> <CommonWrapper @click="openModal">
<icon-park-outline-search /><n-tag round size="small" class="font-mono cursor-pointer"> <icon-park-outline-search />
<n-tag v-if="!appStore.isMobile" round size="small" class="font-mono cursor-pointer">
CtrlK CtrlK
</n-tag> </n-tag>
</CommonWrapper> </CommonWrapper>
<n-modal <n-modal
v-model:show="showModal" v-model:show="showModal"
class="w-560px fixed top-60px inset-x-0" class="w-560px fixed top-60px inset-x-0 max-w-full"
size="small" size="small"
preset="card" preset="card"
:segmented="{ :segmented="{

View File

@ -2,6 +2,7 @@ import BackTop from './common/BackTop.vue'
import Setting from './common/Setting.vue' import Setting from './common/Setting.vue'
import SettingDrawer from './common/SettingDrawer.vue' import SettingDrawer from './common/SettingDrawer.vue'
import Logo from './common/Logo.vue' import Logo from './common/Logo.vue'
import MobileDrawer from './common/MobileDrawer.vue'
import Breadcrumb from './header/Breadcrumb.vue' import Breadcrumb from './header/Breadcrumb.vue'
import CollapaseButton from './header/CollapaseButton.vue' import CollapaseButton from './header/CollapaseButton.vue'
@ -18,6 +19,7 @@ export {
CollapaseButton, CollapaseButton,
FullScreen, FullScreen,
Logo, Logo,
MobileDrawer,
Notices, Notices,
Search, Search,
Setting, Setting,

View File

@ -20,7 +20,7 @@ function exitFullContent() {
</script> </script>
<template> <template>
<n-tooltip placement="bottom" trigger="hover"> <n-tooltip v-if="!appStore.isMobile" placement="bottom" trigger="hover">
<template #trigger> <template #trigger>
<CommonWrapper @click="enterFullContent"> <CommonWrapper @click="enterFullContent">
<icon-park-outline-full-screen-one /> <icon-park-outline-full-screen-one />

View File

@ -6,6 +6,7 @@ import {
CollapaseButton, CollapaseButton,
FullScreen, FullScreen,
Logo, Logo,
MobileDrawer,
Notices, Notices,
Search, Search,
Setting, Setting,
@ -14,12 +15,12 @@ import {
UserCenter, UserCenter,
} from './components' } from './components'
import Content from './Content.vue' import Content from './Content.vue'
import { ProLayout, useLayoutMenu } from 'pro-naive-ui' import { ProLayout, useLayoutMenu } from 'pro-naive-ui'
const route = useRoute() const route = useRoute()
const appStore = useAppStore() const appStore = useAppStore()
const routeStore = useRouteStore() const routeStore = useRouteStore()
const { layoutMode } = storeToRefs(useAppStore()) const { layoutMode } = storeToRefs(useAppStore())
const { const {
@ -31,16 +32,19 @@ const {
menus: routeStore.menus, menus: routeStore.menus,
}) })
watch(() => route.path, (value) => { watch(() => route.path, (value: string) => {
activeKey.value = value activeKey.value = value
}, { immediate: true }) }, { immediate: true })
//
const showMobileDrawer = ref(false)
const sidebarWidth = ref(240) const sidebarWidth = ref(240)
const sidebarCollapsedWidth = ref(64) const sidebarCollapsedWidth = ref(64)
const hasHorizontalMenu = computed(() => ['horizontal', 'mixed-two-column', 'mixed-sidebar'].includes(layoutMode.value)) const hasHorizontalMenu = computed(() => ['horizontal', 'mixed-two-column', 'mixed-sidebar'].includes(layoutMode.value))
const hidenCollapaseButton = computed(() => ['horizontal'].includes(layoutMode.value)) const hidenCollapaseButton = computed(() => ['horizontal'].includes(layoutMode.value) || appStore.isMobile)
</script> </script>
<template> <template>
@ -48,7 +52,8 @@ const hidenCollapaseButton = computed(() => ['horizontal'].includes(layoutMode.v
<ProLayout <ProLayout
v-model:collapsed="appStore.collapsed" v-model:collapsed="appStore.collapsed"
:mode="layoutMode" :mode="layoutMode"
:show-logo="appStore.showLogo" :is-mobile="appStore.isMobile"
:show-logo="appStore.showLogo && !appStore.isMobile"
:show-footer="appStore.showFooter" :show-footer="appStore.showFooter"
:show-tabbar="appStore.showTabs" :show-tabbar="appStore.showTabs"
nav-fixed nav-fixed
@ -79,13 +84,30 @@ const hidenCollapaseButton = computed(() => ['horizontal'].includes(layoutMode.v
<template #nav-right> <template #nav-right>
<div class="h-full flex-y-center gap-1 p-x-xl"> <div class="h-full flex-y-center gap-1 p-x-xl">
<Search /> <!-- 移动端只显示菜单按钮 -->
<Notices /> <template v-if="appStore.isMobile">
<FullScreen /> <n-button
<DarkModeSwitch /> quaternary
<LangsSwitch /> @click="showMobileDrawer = true"
<Setting /> >
<UserCenter /> <template #icon>
<n-icon size="18">
<icon-park-outline-hamburger-button />
</n-icon>
</template>
</n-button>
</template>
<!-- 桌面端显示完整功能组件 -->
<template v-else>
<Search />
<Notices />
<FullScreen />
<DarkModeSwitch />
<LangsSwitch />
<Setting />
<UserCenter />
</template>
</div> </div>
</template> </template>
@ -111,5 +133,10 @@ const hidenCollapaseButton = computed(() => ['horizontal'].includes(layoutMode.v
<Content /> <Content />
<BackTop /> <BackTop />
<SettingDrawer /> <SettingDrawer />
<!-- 移动端功能抽屉 -->
<MobileDrawer v-model:show="showMobileDrawer">
<n-menu v-bind="layout.verticalMenuProps" />
</MobileDrawer>
</ProLayout> </ProLayout>
</template> </template>

View File

@ -17,6 +17,8 @@ const { system, store } = useColorMode({
emitAuto: true, emitAuto: true,
}) })
const isMobile = useMediaQuery('(max-width: 700px)')
export const useAppStore = defineStore('app-store', { export const useAppStore = defineStore('app-store', {
state: () => { state: () => {
return { return {
@ -50,6 +52,9 @@ export const useAppStore = defineStore('app-store', {
fullScreen() { fullScreen() {
return isFullscreen.value return isFullscreen.value
}, },
isMobile() {
return isMobile.value
},
}, },
actions: { actions: {
// 重置所有设置 // 重置所有设置

View File

@ -1,8 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { useAppStore } from '@/store'
import Chart from './components/chart.vue' import Chart from './components/chart.vue'
import Chart2 from './components/chart2.vue' import Chart2 from './components/chart2.vue'
import Chart3 from './components/chart3.vue' import Chart3 from './components/chart3.vue'
const appStore = useAppStore()
const tableData = [ const tableData = [
{ {
id: 0, id: 0,
@ -32,218 +35,224 @@ const tableData = [
</script> </script>
<template> <template>
<div> <n-grid
<n-grid :x-gap="16"
:x-gap="16" :y-gap="16"
:y-gap="16" :cols="12"
> item-responsive
<n-gi :span="6"> responsive="screen"
<n-card> >
<n-space <!-- 统计卡片 - 移动端每行2个桌面端每行4个 -->
justify="space-between" <n-gi span="6 m:3">
align="center" <n-card>
> <n-space
<n-statistic label="访问量"> justify="space-between"
<n-number-animation align="center"
:from="0"
:to="12039"
show-separator
/>
</n-statistic>
<n-icon
color="#de4307"
size="42"
>
<icon-park-outline-chart-histogram />
</n-icon>
</n-space>
<template #footer>
<n-space justify="space-between">
<span>累计访问数</span>
<span><n-number-animation
:from="0"
:to="322039"
show-separator
/></span>
</n-space>
</template>
</n-card>
</n-gi>
<n-gi :span="6">
<n-card>
<n-space
justify="space-between"
align="center"
>
<n-statistic label="下载量">
<n-number-animation
:from="0"
:to="12039"
show-separator
/>
</n-statistic>
<n-icon
color="#ffb549"
size="42"
>
<icon-park-outline-chart-graph />
</n-icon>
</n-space>
<template #footer>
<n-space justify="space-between">
<span>累计下载量</span>
<span><n-number-animation
:from="0"
:to="322039"
show-separator
/></span>
</n-space>
</template>
</n-card>
</n-gi>
<n-gi :span="6">
<n-card>
<n-space
justify="space-between"
align="center"
>
<n-statistic label="浏览量">
<n-number-animation
:from="0"
:to="12039"
show-separator
/>
</n-statistic>
<n-icon
color="#1687a7"
size="42"
>
<icon-park-outline-average />
</n-icon>
</n-space>
<template #footer>
<n-space justify="space-between">
<span>累计浏览量</span>
<span><n-number-animation
:from="0"
:to="322039"
show-separator
/></span>
</n-space>
</template>
</n-card>
</n-gi>
<n-gi :span="6">
<n-card>
<n-space
justify="space-between"
align="center"
>
<n-statistic label="注册量">
<n-number-animation
:from="0"
:to="12039"
show-separator
/>
</n-statistic>
<n-icon
color="#42218E"
size="42"
>
<icon-park-outline-chart-pie />
</n-icon>
</n-space>
<template #footer>
<n-space justify="space-between">
<span>累计注册量</span>
<span><n-number-animation
:from="0"
:to="322039"
show-separator
/></span>
</n-space>
</template>
</n-card>
</n-gi>
<n-gi :span="24">
<n-card content-style="padding: 0;">
<n-tabs
type="line"
size="large"
:tabs-padding="20"
pane-style="padding: 20px;"
>
<n-tab-pane name="流量趋势">
<Chart />
</n-tab-pane>
<n-tab-pane name="访问量趋势">
<Chart2 />
</n-tab-pane>
</n-tabs>
</n-card>
</n-gi>
<n-gi :span="8">
<n-card
title="访问来源"
:segmented="{
content: true,
}"
> >
<Chart3 /> <n-statistic label="访问量">
</n-card> <n-number-animation
</n-gi> :from="0"
<n-gi :span="16"> :to="12039"
<n-card show-separator
title="成交记录" />
:segmented="{ </n-statistic>
content: true, <n-icon
}" color="#de4307"
> size="42"
<template #header-extra>
<n-button
type="primary"
quaternary
>
更多
</n-button>
</template>
<n-table
:bordered="false"
:single-line="false"
> >
<thead> <icon-park-outline-chart-histogram />
<tr> </n-icon>
<th>交易名称</th> </n-space>
<th>开始时间</th> <template #footer>
<th>结束时间</th> <n-space justify="space-between">
<th>进度</th> <span>累计访问数</span>
<th>状态</th> <span><n-number-animation
</tr> :from="0"
</thead> :to="322039"
<tbody> show-separator
<tr /></span>
v-for="item in tableData" </n-space>
:key="item.id" </template>
> </n-card>
<td>{{ item.name }}</td> </n-gi>
<td>{{ item.start }}</td> <n-gi span="6 m:3">
<td>{{ item.end }}</td> <n-card>
<td>{{ item.prograss }}%</td> <n-space
<td> justify="space-between"
<n-tag align="center"
:bordered="false" >
type="info" <n-statistic label="下载量">
> <n-number-animation
{{ item.status }} :from="0"
</n-tag> :to="12039"
</td> show-separator
</tr> />
</tbody> </n-statistic>
</n-table> <n-icon
</n-card> color="#ffb549"
</n-gi> size="42"
</n-grid> >
</div> <icon-park-outline-chart-graph />
</template> </n-icon>
</n-space>
<template #footer>
<n-space justify="space-between">
<span>累计下载量</span>
<span><n-number-animation
:from="0"
:to="322039"
show-separator
/></span>
</n-space>
</template>
</n-card>
</n-gi>
<n-gi span="6 m:3">
<n-card>
<n-space
justify="space-between"
align="center"
>
<n-statistic label="浏览量">
<n-number-animation
:from="0"
:to="12039"
show-separator
/>
</n-statistic>
<n-icon
color="#1687a7"
size="42"
>
<icon-park-outline-average />
</n-icon>
</n-space>
<template #footer>
<n-space justify="space-between">
<span>累计浏览量</span>
<span><n-number-animation
:from="0"
:to="322039"
show-separator
/></span>
</n-space>
</template>
</n-card>
</n-gi>
<n-gi span="6 m:3">
<n-card>
<n-space
justify="space-between"
align="center"
>
<n-statistic label="注册量">
<n-number-animation
:from="0"
:to="12039"
show-separator
/>
</n-statistic>
<n-icon
color="#42218E"
size="42"
>
<icon-park-outline-chart-pie />
</n-icon>
</n-space>
<template #footer>
<n-space justify="space-between">
<span>累计注册量</span>
<span><n-number-animation
:from="0"
:to="322039"
show-separator
/></span>
</n-space>
</template>
</n-card>
</n-gi>
<!-- 图表区域 - 全宽显示 -->
<n-gi :span="12">
<n-card content-style="padding: 0;">
<n-tabs
type="line"
size="large"
:tabs-padding="20"
pane-style="padding: 20px;"
>
<n-tab-pane name="流量趋势">
<Chart />
</n-tab-pane>
<n-tab-pane name="访问量趋势">
<Chart2 />
</n-tab-pane>
</n-tabs>
</n-card>
</n-gi>
<style scoped></style> <!-- 访问来源 - 移动端全宽桌面端1/3 -->
<n-gi span="12 m:4">
<n-card
title="访问来源"
:segmented="{
content: true,
}"
>
<Chart3 />
</n-card>
</n-gi>
<!-- 成交记录 - 移动端全宽桌面端2/3 -->
<n-gi span="12 m:8">
<n-card
title="成交记录"
:segmented="{
content: true,
}"
>
<template #header-extra>
<n-button
type="primary"
quaternary
>
更多
</n-button>
</template>
<n-table
:bordered="false"
:single-line="false"
:scroll-x="appStore.isMobile ? 600 : undefined"
>
<thead>
<tr>
<th>交易名称</th>
<th>开始时间</th>
<th>结束时间</th>
<th>进度</th>
<th>状态</th>
</tr>
</thead>
<tbody>
<tr
v-for="item in tableData"
:key="item.id"
>
<td>{{ item.name }}</td>
<td>{{ item.start }}</td>
<td>{{ item.end }}</td>
<td>{{ item.prograss }}%</td>
<td>
<n-tag
:bordered="false"
type="info"
>
{{ item.status }}
</n-tag>
</td>
</tr>
</tbody>
</n-table>
</n-card>
</n-gi>
</n-grid>
</template>

View File

@ -9,94 +9,102 @@ const { userInfo } = useAuthStore()
<n-grid <n-grid
:x-gap="16" :x-gap="16"
:y-gap="16" :y-gap="16"
:cols="3"
item-responsive
responsive="screen"
> >
<n-gi :span="16"> <!-- 左侧主要内容区 - 移动端全宽桌面端2/3 -->
<n-gi span="3 m:2">
<n-space <n-space
vertical vertical
:size="16" :size="16"
> >
<!-- 图表区域 -->
<n-card style="--n-padding-left: 0;"> <n-card style="--n-padding-left: 0;">
<Chart /> <Chart />
</n-card> </n-card>
<n-card>
<n-grid <!-- 统计卡片区域 -->
:x-gap="8" <n-grid
:y-gap="8" :x-gap="16"
> :y-gap="16"
<n-gi :span="6"> :cols="4"
<n-card> item-responsive
<n-thing> responsive="screen"
<template #avatar> >
<n-el> <n-gi span="2 l:1">
<n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999"> <n-card>
<nova-icon :size="26" icon="icon-park-outline:user" /> <n-thing>
</n-icon-wrapper> <template #avatar>
</n-el> <n-el>
</template> <n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999">
<template #header> <nova-icon :size="26" icon="icon-park-outline:user" />
<n-statistic label="活跃用户"> </n-icon-wrapper>
<n-number-animation show-separator :from="0" :to="12039" /> </n-el>
</n-statistic> </template>
</template> <template #header>
</n-thing> <n-statistic label="活跃用户">
</n-card> <n-number-animation show-separator :from="0" :to="12039" />
</n-gi> </n-statistic>
<n-gi :span="6"> </template>
<n-card> </n-thing>
<n-thing> </n-card>
<template #avatar> </n-gi>
<n-el> <n-gi span="2 l:1">
<n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999"> <n-card>
<nova-icon :size="26" icon="icon-park-outline:every-user" /> <n-thing>
</n-icon-wrapper> <template #avatar>
</n-el> <n-el>
</template> <n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999">
<template #header> <nova-icon :size="26" icon="icon-park-outline:every-user" />
<n-statistic label="用户"> </n-icon-wrapper>
<n-number-animation show-separator :from="0" :to="44039" /> </n-el>
</n-statistic> </template>
</template> <template #header>
</n-thing> <n-statistic label="用户">
</n-card> <n-number-animation show-separator :from="0" :to="44039" />
</n-gi> </n-statistic>
<n-gi :span="6"> </template>
<n-card> </n-thing>
<n-thing> </n-card>
<template #avatar> </n-gi>
<n-el> <n-gi span="2 l:1">
<n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999"> <n-card>
<nova-icon :size="26" icon="icon-park-outline:preview-open" /> <n-thing>
</n-icon-wrapper> <template #avatar>
</n-el> <n-el>
</template> <n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999">
<template #header> <nova-icon :size="26" icon="icon-park-outline:preview-open" />
<n-statistic label="浏览量"> </n-icon-wrapper>
<n-number-animation show-separator :from="0" :to="551039" /> </n-el>
</n-statistic> </template>
</template> <template #header>
</n-thing> <n-statistic label="浏览量">
</n-card> <n-number-animation show-separator :from="0" :to="551039" />
</n-gi> </n-statistic>
<n-gi :span="6"> </template>
<n-card> </n-thing>
<n-thing> </n-card>
<template #avatar> </n-gi>
<n-el> <n-gi span="2 l:1">
<n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999"> <n-card>
<nova-icon :size="26" icon="icon-park-outline:star" /> <n-thing>
</n-icon-wrapper> <template #avatar>
</n-el> <n-el>
</template> <n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999">
<template #header> <nova-icon :size="26" icon="icon-park-outline:star" />
<n-statistic label="收藏数"> </n-icon-wrapper>
<n-number-animation show-separator :from="0" :to="7739" /> </n-el>
</n-statistic> </template>
</template> <template #header>
</n-thing> <n-statistic label="收藏数">
</n-card> <n-number-animation show-separator :from="0" :to="7739" />
</n-gi> </n-statistic>
</n-grid> </template>
</n-card> </n-thing>
</n-card>
</n-gi>
</n-grid>
<n-card title="动态"> <n-card title="动态">
<template #header-extra> <template #header-extra>
<n-button <n-button
@ -167,7 +175,9 @@ const { userInfo } = useAuthStore()
</n-card> </n-card>
</n-space> </n-space>
</n-gi> </n-gi>
<n-gi :span="8">
<!-- 右侧边栏 - 移动端全宽桌面端1/3 -->
<n-gi span="3 m:1">
<n-space <n-space
vertical vertical
:size="16" :size="16"
@ -226,11 +236,14 @@ const { userInfo } = useAuthStore()
</n-list-item> </n-list-item>
</n-list> </n-list>
</n-card> </n-card>
<!-- 订单和待办统计 -->
<n-grid <n-grid
:x-gap="8" :x-gap="16"
:y-gap="8" :y-gap="16"
:cols="2"
> >
<n-gi :span="12"> <!-- 移动端和桌面端都是每行2个 -->
<n-gi :span="1">
<n-card> <n-card>
<n-flex vertical align="center"> <n-flex vertical align="center">
<n-text depth="3"> <n-text depth="3">
@ -245,7 +258,7 @@ const { userInfo } = useAuthStore()
</n-flex> </n-flex>
</n-card> </n-card>
</n-gi> </n-gi>
<n-gi :span="12"> <n-gi :span="1">
<n-card> <n-card>
<n-flex vertical align="center"> <n-flex vertical align="center">
<n-text depth="3"> <n-text depth="3">

View File

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { Login, Register, ResetPwd } from './components' import { Login, Register, ResetPwd } from './components'
type IformType = 'login' | 'register' | 'resetPwd' type IformType = 'login' | 'register' | 'resetPwd'
const formType: Ref<IformType> = ref('login') const formType: Ref<IformType> = ref('login')
const formComponets = { const formComponets = {
login: Login, login: Login,
@ -18,7 +18,7 @@ const appName = import.meta.env.VITE_APP_NAME
<DarkModeSwitch /> <DarkModeSwitch />
<LangsSwitch /> <LangsSwitch />
</div> </div>
<n-el <div
class="p-4xl h-full w-full sm:w-450px sm:h-unset" class="p-4xl h-full w-full sm:w-450px sm:h-unset"
style="background: var(--card-color);box-shadow: var(--box-shadow-1);" style="background: var(--card-color);box-shadow: var(--box-shadow-1);"
> >
@ -36,7 +36,7 @@ const appName = import.meta.env.VITE_APP_NAME
/> />
</transition> </transition>
</div> </div>
</n-el> </div>
<div /> <div />
</n-el> </n-el>