1
This commit is contained in:
@ -9,6 +9,7 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@element-plus/icons-vue": "^2.3.2",
|
||||||
"@vueup/vue-quill": "^1.2.0",
|
"@vueup/vue-quill": "^1.2.0",
|
||||||
"@vueuse/core": "^13.8.0",
|
"@vueuse/core": "^13.8.0",
|
||||||
"axios": "^1.12.2",
|
"axios": "^1.12.2",
|
||||||
@ -21,6 +22,7 @@
|
|||||||
"quill": "^2.0.3",
|
"quill": "^2.0.3",
|
||||||
"terser": "^5.43.1",
|
"terser": "^5.43.1",
|
||||||
"v-viewer": "^3.0.11",
|
"v-viewer": "^3.0.11",
|
||||||
|
"vant": "^4.10.0",
|
||||||
"vue": "^3.5.18",
|
"vue": "^3.5.18",
|
||||||
"vue-router": "^4.5.1"
|
"vue-router": "^4.5.1"
|
||||||
},
|
},
|
||||||
|
|||||||
25
src/home.vue
25
src/home.vue
@ -1,10 +1,18 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import {storeToRefs} from 'pinia'
|
||||||
|
import {useNavStore} from '@/store/nav'
|
||||||
|
import {isMobileDevice} from '@/utils/rem'
|
||||||
|
import {onMounted, ref} from 'vue'
|
||||||
import Header from './views/layout/pc/header/index.vue'
|
import Header from './views/layout/pc/header/index.vue'
|
||||||
import Footer from './views/layout/pc/footer/index.vue'
|
import Footer from './views/layout/pc/footer/index.vue'
|
||||||
import {onMounted, ref, watch} from 'vue'
|
import MobileLayout from './views/layout/mobile/index.vue'
|
||||||
import {visit} from '@/api/index'
|
import {visit} from '@/api/index'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
|
// 移动端判断
|
||||||
|
const navStore = useNavStore()
|
||||||
|
const {isMobile} = storeToRefs(navStore)
|
||||||
|
|
||||||
const referrer = ref(document.referrer || '')
|
const referrer = ref(document.referrer || '')
|
||||||
const ipUrl = ref('')
|
const ipUrl = ref('')
|
||||||
|
|
||||||
@ -12,6 +20,14 @@ const CACHE_KEY = 'ip'
|
|||||||
const TIME_KEY = 'ipFetchTime'
|
const TIME_KEY = 'ipFetchTime'
|
||||||
const CACHE_MIN = 60 * 60 * 1000 // 1h
|
const CACHE_MIN = 60 * 60 * 1000 // 1h
|
||||||
|
|
||||||
|
// 初始化移动端状态
|
||||||
|
navStore.setIsMobile(isMobileDevice())
|
||||||
|
|
||||||
|
// 监听窗口 resize
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
navStore.setIsMobile(isMobileDevice())
|
||||||
|
})
|
||||||
|
|
||||||
async function getUrl() {
|
async function getUrl() {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const cachedIp = sessionStorage.getItem(CACHE_KEY)
|
const cachedIp = sessionStorage.getItem(CACHE_KEY)
|
||||||
@ -40,7 +56,6 @@ function postVisit() {
|
|||||||
referrer: referrer.value,
|
referrer: referrer.value,
|
||||||
real_ip: ipUrl.value,
|
real_ip: ipUrl.value,
|
||||||
}
|
}
|
||||||
|
|
||||||
visit(params).then((res) => {})
|
visit(params).then((res) => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +65,8 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<!-- PC端布局 -->
|
||||||
|
<div v-if="!isMobile" id="app">
|
||||||
<Header id="header" />
|
<Header id="header" />
|
||||||
<router-view id="main" v-slot="{Component, title}">
|
<router-view id="main" v-slot="{Component, title}">
|
||||||
<keep-alive include="New">
|
<keep-alive include="New">
|
||||||
@ -59,6 +75,9 @@ onMounted(async () => {
|
|||||||
</router-view>
|
</router-view>
|
||||||
<Footer id="footer" />
|
<Footer id="footer" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 移动端布局 -->
|
||||||
|
<MobileLayout v-else />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@ -105,12 +105,21 @@ const router = createRouter({
|
|||||||
routes,
|
routes,
|
||||||
})
|
})
|
||||||
|
|
||||||
// router.beforeEach(async (to, from, next) => {
|
// 移动端/PC端断点
|
||||||
// const {useNavStore} = await import('@/store/nav.js')
|
const MOBILE_BREAKPOINT = 1000
|
||||||
// const navStore = useNavStore()
|
|
||||||
// navStore.setLoad(true)
|
// 判断是否为移动端
|
||||||
// next()
|
function checkIsMobile() {
|
||||||
// })
|
return window.innerWidth < MOBILE_BREAKPOINT
|
||||||
|
}
|
||||||
|
|
||||||
|
router.beforeEach(async (to, from, next) => {
|
||||||
|
const {useNavStore} = await import('@/store/nav.js')
|
||||||
|
const navStore = useNavStore()
|
||||||
|
// 更新移动端状态
|
||||||
|
navStore.setIsMobile(checkIsMobile())
|
||||||
|
next()
|
||||||
|
})
|
||||||
|
|
||||||
router.afterEach((to, from) => {
|
router.afterEach((to, from) => {
|
||||||
// 只有路径变化时才滚动到顶部
|
// 只有路径变化时才滚动到顶部
|
||||||
|
|||||||
@ -6,6 +6,7 @@ export const useNavStore = defineStore(
|
|||||||
() => {
|
() => {
|
||||||
const navIndex = ref('/')
|
const navIndex = ref('/')
|
||||||
const showLoad = ref(true)
|
const showLoad = ref(true)
|
||||||
|
const isMobile = ref(window.innerWidth < 1000)
|
||||||
|
|
||||||
const changeNavIndex = (id) => {
|
const changeNavIndex = (id) => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
@ -20,11 +21,17 @@ export const useNavStore = defineStore(
|
|||||||
showLoad.value = status
|
showLoad.value = status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setIsMobile = (val) => {
|
||||||
|
isMobile.value = val
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
navIndex,
|
navIndex,
|
||||||
showLoad,
|
showLoad,
|
||||||
|
isMobile,
|
||||||
changeNavIndex,
|
changeNavIndex,
|
||||||
setLoad,
|
setLoad,
|
||||||
|
setIsMobile,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,13 +1,28 @@
|
|||||||
// rem等比适配配置
|
// rem等比适配配置
|
||||||
// 基准大小
|
// 基准大小
|
||||||
const baseSize = 16
|
const baseSize = 16
|
||||||
// 设计稿宽度
|
// 设计稿宽度(PC端)
|
||||||
const designWidth = 1920
|
const pcDesignWidth = 1920
|
||||||
|
// 设计稿宽度(移动端)
|
||||||
|
const mobileDesignWidth = 375
|
||||||
|
// 移动端/PC端断点
|
||||||
|
const mobileBreakpoint = 1000
|
||||||
// 最大缩放比例
|
// 最大缩放比例
|
||||||
const maxScale = 2
|
const maxScale = 2
|
||||||
|
|
||||||
|
// 检测是否为移动端
|
||||||
|
export function isMobileDevice() {
|
||||||
|
return window.innerWidth < mobileBreakpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前设计稿宽度
|
||||||
|
function getDesignWidth() {
|
||||||
|
return isMobileDevice() ? mobileDesignWidth : pcDesignWidth
|
||||||
|
}
|
||||||
|
|
||||||
// 设置 rem 函数
|
// 设置 rem 函数
|
||||||
function setRem() {
|
function setRem() {
|
||||||
|
const designWidth = getDesignWidth()
|
||||||
// 当前页面宽度相对于设计稿宽度的缩放比例
|
// 当前页面宽度相对于设计稿宽度的缩放比例
|
||||||
const scale = document.documentElement.clientWidth / designWidth
|
const scale = document.documentElement.clientWidth / designWidth
|
||||||
// 设置页面根节点字体大小(最高放大比例为maxScale)
|
// 设置页面根节点字体大小(最高放大比例为maxScale)
|
||||||
@ -18,6 +33,7 @@ function setRem() {
|
|||||||
export function fontSize(res) {
|
export function fontSize(res) {
|
||||||
const clientWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
|
const clientWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
|
||||||
if (!clientWidth) return res
|
if (!clientWidth) return res
|
||||||
|
const designWidth = getDesignWidth()
|
||||||
const scale = clientWidth / designWidth
|
const scale = clientWidth / designWidth
|
||||||
return res * Math.min(scale, maxScale)
|
return res * Math.min(scale, maxScale)
|
||||||
}
|
}
|
||||||
|
|||||||
316
src/views/layout/mobile/index.vue
Normal file
316
src/views/layout/mobile/index.vue
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
<script setup>
|
||||||
|
import {ref, onMounted, watch} from 'vue'
|
||||||
|
import {useRouter} from 'vue-router'
|
||||||
|
import {useNavStore} from '@/store/nav'
|
||||||
|
import {storeToRefs} from 'pinia'
|
||||||
|
import {Menu, ArrowDown, ArrowRight} from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
const navStore = useNavStore()
|
||||||
|
const {navIndex} = storeToRefs(navStore)
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const sidebarVisible = ref(false)
|
||||||
|
const navList = ref([])
|
||||||
|
const activeIndex = ref('/')
|
||||||
|
const expandedSubMenus = ref([])
|
||||||
|
|
||||||
|
const toggleSidebar = () => {
|
||||||
|
sidebarVisible.value = !sidebarVisible.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeSidebar = () => {
|
||||||
|
sidebarVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleSubMenu = (url) => {
|
||||||
|
const idx = expandedSubMenus.value.indexOf(url)
|
||||||
|
if (idx > -1) {
|
||||||
|
expandedSubMenus.value.splice(idx, 1)
|
||||||
|
} else {
|
||||||
|
expandedSubMenus.value.push(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNavClick = (nav) => {
|
||||||
|
if (nav.hasChildren || nav.children?.length > 0) {
|
||||||
|
toggleSubMenu(nav.url)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (nav.open) {
|
||||||
|
window.open(nav.url)
|
||||||
|
sidebarVisible.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
navStore.changeNavIndex(nav.url)
|
||||||
|
activeIndex.value = nav.url
|
||||||
|
router.push(nav.url)
|
||||||
|
sidebarVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
navIndex,
|
||||||
|
(newVal) => {
|
||||||
|
activeIndex.value = newVal
|
||||||
|
},
|
||||||
|
{immediate: true}
|
||||||
|
)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
navList.value = window.nav?.header || []
|
||||||
|
})
|
||||||
|
|
||||||
|
const toMIIF = () => {
|
||||||
|
window.open('https://beian.miit.gov.cn')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="mobile-layout">
|
||||||
|
<!-- ==================== 顶部 Header ==================== -->
|
||||||
|
<div class="mobile-header">
|
||||||
|
<el-icon class="header-menu-icon" :size="22" @click="toggleSidebar">
|
||||||
|
<Menu />
|
||||||
|
</el-icon>
|
||||||
|
<div class="header-logo" @click="router.push('/')"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ==================== 侧边栏 ==================== -->
|
||||||
|
<transition name="sidebar-slide">
|
||||||
|
<div v-show="sidebarVisible" class="sidebar">
|
||||||
|
<div class="sidebar-nav">
|
||||||
|
<template v-for="nav in navList" :key="nav.id">
|
||||||
|
<!-- 有子菜单 -->
|
||||||
|
<div v-if="nav.hasChildren || nav.children?.length > 0" class="nav-group">
|
||||||
|
<div
|
||||||
|
class="nav-item nav-item--parent"
|
||||||
|
:class="{'nav-item--expanded': expandedSubMenus.includes(nav.url)}"
|
||||||
|
@click="handleNavClick(nav)"
|
||||||
|
>
|
||||||
|
<span class="nav-label">{{ nav.label }}</span>
|
||||||
|
<el-icon class="nav-arrow" :size="12">
|
||||||
|
<ArrowDown v-if="expandedSubMenus.includes(nav.url)" />
|
||||||
|
<ArrowRight v-else />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div v-show="expandedSubMenus.includes(nav.url)" class="nav-sub">
|
||||||
|
<div
|
||||||
|
v-for="child in nav.children"
|
||||||
|
:key="child.id"
|
||||||
|
class="nav-item nav-item--child"
|
||||||
|
:class="{'nav-item--active': activeIndex === child.url}"
|
||||||
|
@click="handleNavClick(child)"
|
||||||
|
>
|
||||||
|
{{ child.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 无子菜单 -->
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="nav-item"
|
||||||
|
:class="{'nav-item--active': activeIndex === nav.url}"
|
||||||
|
@click="handleNavClick(nav)"
|
||||||
|
>
|
||||||
|
<span class="nav-label">{{ nav.label }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
|
||||||
|
<!-- ==================== Content 内容区域 ==================== -->
|
||||||
|
<div class="mobile-content">
|
||||||
|
<router-view v-slot="{Component}">
|
||||||
|
<keep-alive include="New">
|
||||||
|
<component :is="Component" />
|
||||||
|
</keep-alive>
|
||||||
|
</router-view>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ==================== Footer 底部 ==================== -->
|
||||||
|
<div class="mobile-footer">
|
||||||
|
<div class="footer-info">
|
||||||
|
<span>Copyright © 2023.Rangu Technology Co.,Ltd.</span>
|
||||||
|
<span>燃谷科技(南京)有限公司</span>
|
||||||
|
</div>
|
||||||
|
<div class="footer-beian" @click="toMIIF">苏ICP备2023030548号-1</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
// ==================== 布局容器 ====================
|
||||||
|
.mobile-layout {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100vw;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: $bg_color;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Header ====================
|
||||||
|
.mobile-header {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%; /* no */
|
||||||
|
max-width: 100vw;
|
||||||
|
height: 44px; /* no */
|
||||||
|
background-color: $bg_color;
|
||||||
|
z-index: 110;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 10px; /* no */
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.header-menu-icon {
|
||||||
|
color: $white;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-logo {
|
||||||
|
background-image: url('/static/images/header/logo.png');
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
width: 100px;
|
||||||
|
height: 32px; /* no */
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 侧边栏遮罩层 ====================
|
||||||
|
.sidebar-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mask-fade-enter-active,
|
||||||
|
.mask-fade-leave-active {
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
.mask-fade-enter-from,
|
||||||
|
.mask-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 侧边栏(浮动层) ====================
|
||||||
|
.sidebar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 160px; /* no */
|
||||||
|
height: 100%;
|
||||||
|
background-color: $bg_color;
|
||||||
|
z-index: 105;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-top: 56px; /* no */
|
||||||
|
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.3); /* no */
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-slide-enter-active,
|
||||||
|
.sidebar-slide-leave-active {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
.sidebar-slide-enter-from,
|
||||||
|
.sidebar-slide-leave-to {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-nav {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-group {
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08); /* no */
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 14px 16px; /* no */
|
||||||
|
cursor: pointer;
|
||||||
|
color: #999999;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--parent {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--expanded {
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--child {
|
||||||
|
padding-left: 32px; /* no */
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-label {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-arrow {
|
||||||
|
color: #999999;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-sub {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Content 内容区域 ====================
|
||||||
|
.mobile-content {
|
||||||
|
margin-top: 44px; /* no */
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100vw;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Footer 底部 ====================
|
||||||
|
.mobile-footer {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100vw;
|
||||||
|
padding: 20px 16px; /* no */
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: center;
|
||||||
|
background-color: $bg_color;
|
||||||
|
|
||||||
|
.footer-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
color: #999999;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-beian {
|
||||||
|
color: #0056b3;
|
||||||
|
font-size: 11px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user