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">
import { useBoolean } from '@/hooks'
import { useRouteStore } from '@/store'
import { useAppStore, useRouteStore } from '@/store'
const appStore = useAppStore()
const routeStore = useRouteStore()
//
@ -143,13 +144,14 @@ function handleMouseEnter(index: number) {
<template>
<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
</n-tag>
</CommonWrapper>
<n-modal
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"
preset="card"
:segmented="{

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,11 @@
<script setup lang="ts">
import { useAppStore } from '@/store'
import Chart from './components/chart.vue'
import Chart2 from './components/chart2.vue'
import Chart3 from './components/chart3.vue'
const appStore = useAppStore()
const tableData = [
{
id: 0,
@ -32,218 +35,224 @@ const tableData = [
</script>
<template>
<div>
<n-grid
:x-gap="16"
:y-gap="16"
>
<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="#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,
}"
<n-grid
:x-gap="16"
:y-gap="16"
:cols="12"
item-responsive
responsive="screen"
>
<!-- 统计卡片 - 移动端每行2个桌面端每行4个 -->
<n-gi span="6 m:3">
<n-card>
<n-space
justify="space-between"
align="center"
>
<Chart3 />
</n-card>
</n-gi>
<n-gi :span="16">
<n-card
title="成交记录"
:segmented="{
content: true,
}"
>
<template #header-extra>
<n-button
type="primary"
quaternary
>
更多
</n-button>
</template>
<n-table
:bordered="false"
:single-line="false"
<n-statistic label="访问量">
<n-number-animation
:from="0"
:to="12039"
show-separator
/>
</n-statistic>
<n-icon
color="#de4307"
size="42"
>
<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>
</div>
</template>
<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 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="#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 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
:x-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
vertical
:size="16"
>
<!-- 图表区域 -->
<n-card style="--n-padding-left: 0;">
<Chart />
</n-card>
<n-card>
<n-grid
:x-gap="8"
:y-gap="8"
>
<n-gi :span="6">
<n-card>
<n-thing>
<template #avatar>
<n-el>
<n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999">
<nova-icon :size="26" icon="icon-park-outline:user" />
</n-icon-wrapper>
</n-el>
</template>
<template #header>
<n-statistic label="活跃用户">
<n-number-animation show-separator :from="0" :to="12039" />
</n-statistic>
</template>
</n-thing>
</n-card>
</n-gi>
<n-gi :span="6">
<n-card>
<n-thing>
<template #avatar>
<n-el>
<n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999">
<nova-icon :size="26" icon="icon-park-outline:every-user" />
</n-icon-wrapper>
</n-el>
</template>
<template #header>
<n-statistic label="用户">
<n-number-animation show-separator :from="0" :to="44039" />
</n-statistic>
</template>
</n-thing>
</n-card>
</n-gi>
<n-gi :span="6">
<n-card>
<n-thing>
<template #avatar>
<n-el>
<n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999">
<nova-icon :size="26" icon="icon-park-outline:preview-open" />
</n-icon-wrapper>
</n-el>
</template>
<template #header>
<n-statistic label="浏览量">
<n-number-animation show-separator :from="0" :to="551039" />
</n-statistic>
</template>
</n-thing>
</n-card>
</n-gi>
<n-gi :span="6">
<n-card>
<n-thing>
<template #avatar>
<n-el>
<n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999">
<nova-icon :size="26" icon="icon-park-outline:star" />
</n-icon-wrapper>
</n-el>
</template>
<template #header>
<n-statistic label="收藏数">
<n-number-animation show-separator :from="0" :to="7739" />
</n-statistic>
</template>
</n-thing>
</n-card>
</n-gi>
</n-grid>
</n-card>
<!-- 统计卡片区域 -->
<n-grid
:x-gap="16"
:y-gap="16"
:cols="4"
item-responsive
responsive="screen"
>
<n-gi span="2 l:1">
<n-card>
<n-thing>
<template #avatar>
<n-el>
<n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999">
<nova-icon :size="26" icon="icon-park-outline:user" />
</n-icon-wrapper>
</n-el>
</template>
<template #header>
<n-statistic label="活跃用户">
<n-number-animation show-separator :from="0" :to="12039" />
</n-statistic>
</template>
</n-thing>
</n-card>
</n-gi>
<n-gi span="2 l:1">
<n-card>
<n-thing>
<template #avatar>
<n-el>
<n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999">
<nova-icon :size="26" icon="icon-park-outline:every-user" />
</n-icon-wrapper>
</n-el>
</template>
<template #header>
<n-statistic label="用户">
<n-number-animation show-separator :from="0" :to="44039" />
</n-statistic>
</template>
</n-thing>
</n-card>
</n-gi>
<n-gi span="2 l:1">
<n-card>
<n-thing>
<template #avatar>
<n-el>
<n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999">
<nova-icon :size="26" icon="icon-park-outline:preview-open" />
</n-icon-wrapper>
</n-el>
</template>
<template #header>
<n-statistic label="浏览量">
<n-number-animation show-separator :from="0" :to="551039" />
</n-statistic>
</template>
</n-thing>
</n-card>
</n-gi>
<n-gi span="2 l:1">
<n-card>
<n-thing>
<template #avatar>
<n-el>
<n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999">
<nova-icon :size="26" icon="icon-park-outline:star" />
</n-icon-wrapper>
</n-el>
</template>
<template #header>
<n-statistic label="收藏数">
<n-number-animation show-separator :from="0" :to="7739" />
</n-statistic>
</template>
</n-thing>
</n-card>
</n-gi>
</n-grid>
<n-card title="动态">
<template #header-extra>
<n-button
@ -167,7 +175,9 @@ const { userInfo } = useAuthStore()
</n-card>
</n-space>
</n-gi>
<n-gi :span="8">
<!-- 右侧边栏 - 移动端全宽桌面端1/3 -->
<n-gi span="3 m:1">
<n-space
vertical
:size="16"
@ -226,11 +236,14 @@ const { userInfo } = useAuthStore()
</n-list-item>
</n-list>
</n-card>
<!-- 订单和待办统计 -->
<n-grid
:x-gap="8"
:y-gap="8"
:x-gap="16"
:y-gap="16"
:cols="2"
>
<n-gi :span="12">
<!-- 移动端和桌面端都是每行2个 -->
<n-gi :span="1">
<n-card>
<n-flex vertical align="center">
<n-text depth="3">
@ -245,7 +258,7 @@ const { userInfo } = useAuthStore()
</n-flex>
</n-card>
</n-gi>
<n-gi :span="12">
<n-gi :span="1">
<n-card>
<n-flex vertical align="center">
<n-text depth="3">

View File

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