移动端样式
This commit is contained in:
@@ -134,14 +134,14 @@ const {
|
|||||||
top: 50%;
|
top: 50%;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
padding: 0 8px; /* no */
|
padding: 0 8px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
.nav-btn {
|
.nav-btn {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
width: 32px; /* no */
|
width: 32px;
|
||||||
height: 32px; /* no */
|
height: 32px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: rgba(255, 255, 255, 0.9);
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
|||||||
@@ -15,22 +15,74 @@ const options = {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-dialog v-model="visible" title="扫码下载" width="500px" :close-on-click-modal="true">
|
<transition name="modal-fade">
|
||||||
<div class="dialog-body flex j-c a-c">
|
<div v-if="visible" class="modal-overlay" @click.self="visible = false">
|
||||||
|
<div class="modal-card">
|
||||||
|
<div class="modal-header">
|
||||||
|
<span class="modal-title">扫码下载</span>
|
||||||
|
<span class="modal-close" @click="visible = false">×</span>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
<div class="download-option">
|
<div class="download-option">
|
||||||
<img :src="options[type]?.img" :alt="type" />
|
<img :src="options[type]?.img" :alt="type" />
|
||||||
<span class="label">{{ options[type]?.label }}</span>
|
<span class="label">{{ options[type]?.label }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.dialog-body {
|
.modal-overlay {
|
||||||
gap: 40px;
|
position: fixed;
|
||||||
padding: 20px 0;
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 2000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.download-option {
|
.modal-card {
|
||||||
|
width: 280px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close {
|
||||||
|
font-size: 22px;
|
||||||
|
color: #999;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 20px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-option {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -47,6 +99,22 @@ const options = {
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-fade-enter-active,
|
||||||
|
.modal-fade-leave-active {
|
||||||
|
transition: opacity 0.25s ease;
|
||||||
|
|
||||||
|
.modal-card {
|
||||||
|
transition: transform 0.25s ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modal-fade-enter-from,
|
||||||
|
.modal-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
.modal-card {
|
||||||
|
transform: scale(0.9);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+217
-133
@@ -3,53 +3,58 @@ import {ref, onMounted, watch} from 'vue'
|
|||||||
import {useRouter} from 'vue-router'
|
import {useRouter} from 'vue-router'
|
||||||
import {useNavStore} from '@/store/nav'
|
import {useNavStore} from '@/store/nav'
|
||||||
import {storeToRefs} from 'pinia'
|
import {storeToRefs} from 'pinia'
|
||||||
import {Menu, ArrowDown, ArrowRight} from '@element-plus/icons-vue'
|
import {Menu, ArrowDown} from '@element-plus/icons-vue'
|
||||||
|
|
||||||
const navStore = useNavStore()
|
const navStore = useNavStore()
|
||||||
const {navIndex} = storeToRefs(navStore)
|
const {navIndex} = storeToRefs(navStore)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const sidebarVisible = ref(false)
|
|
||||||
const navList = ref([])
|
const navList = ref([])
|
||||||
const activeIndex = ref('/')
|
const activeIndex = ref('/')
|
||||||
const expandedSubMenus = ref([])
|
const expandedMenuId = ref(null)
|
||||||
|
|
||||||
const toggleSidebar = () => {
|
|
||||||
sidebarVisible.value = !sidebarVisible.value
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeSidebar = () => {
|
|
||||||
sidebarVisible.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleSubMenu = (navId) => {
|
|
||||||
const idx = expandedSubMenus.value.indexOf(navId)
|
|
||||||
if (idx > -1) {
|
|
||||||
expandedSubMenus.value.splice(idx, 1)
|
|
||||||
} else {
|
|
||||||
expandedSubMenus.value.push(navId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getTargetUrl = (nav) => {
|
const getTargetUrl = (nav) => {
|
||||||
return nav.mobileUrl || nav.url
|
return nav.mobileUrl || nav.url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isNavActive = (nav) => {
|
||||||
|
if (activeIndex.value === getTargetUrl(nav)) return true
|
||||||
|
if (nav.children?.length > 0) {
|
||||||
|
return nav.children.some((child) => activeIndex.value === getTargetUrl(child))
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
const handleNavClick = (nav) => {
|
const handleNavClick = (nav) => {
|
||||||
if (nav.hasChildren || nav.children?.length > 0) {
|
if (nav.hasChildren || nav.children?.length > 0) {
|
||||||
toggleSubMenu(nav.id)
|
expandedMenuId.value = expandedMenuId.value === nav.id ? null : nav.id
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
expandedMenuId.value = null
|
||||||
if (nav.open) {
|
if (nav.open) {
|
||||||
window.open(nav.url)
|
window.open(nav.url)
|
||||||
sidebarVisible.value = false
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const targetUrl = getTargetUrl(nav)
|
const targetUrl = getTargetUrl(nav)
|
||||||
navStore.changeNavIndex(targetUrl)
|
navStore.changeNavIndex(targetUrl)
|
||||||
activeIndex.value = targetUrl
|
activeIndex.value = targetUrl
|
||||||
router.push(targetUrl)
|
router.push(targetUrl)
|
||||||
sidebarVisible.value = false
|
}
|
||||||
|
|
||||||
|
const handleChildClick = (child) => {
|
||||||
|
expandedMenuId.value = null
|
||||||
|
if (child.open) {
|
||||||
|
window.open(child.url)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const targetUrl = getTargetUrl(child)
|
||||||
|
navStore.changeNavIndex(targetUrl)
|
||||||
|
activeIndex.value = targetUrl
|
||||||
|
router.push(targetUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeDropdown = () => {
|
||||||
|
expandedMenuId.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -73,54 +78,55 @@ const toMIIF = () => {
|
|||||||
<div class="mobile-layout">
|
<div class="mobile-layout">
|
||||||
<!-- ==================== 顶部 Header ==================== -->
|
<!-- ==================== 顶部 Header ==================== -->
|
||||||
<div class="mobile-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 class="header-logo" @click="router.push('/')"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ==================== 侧边栏 ==================== -->
|
<!-- ==================== 横向滚动菜单栏 ==================== -->
|
||||||
<transition name="sidebar-slide">
|
<div class="menu-bar">
|
||||||
<div v-show="sidebarVisible" class="sidebar">
|
<div class="menu-scroll">
|
||||||
<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
|
<div
|
||||||
class="nav-item nav-item--parent"
|
v-for="nav in navList"
|
||||||
:class="{'nav-item--expanded': expandedSubMenus.includes(nav.id)}"
|
:key="nav.id"
|
||||||
|
class="menu-item"
|
||||||
|
:class="{
|
||||||
|
'menu-item--active': isNavActive(nav),
|
||||||
|
'menu-item--expanded': expandedMenuId === nav.id,
|
||||||
|
}"
|
||||||
@click="handleNavClick(nav)"
|
@click="handleNavClick(nav)"
|
||||||
>
|
>
|
||||||
<span class="nav-label">{{ nav.label }}</span>
|
<span class="menu-label">{{ nav.label }}</span>
|
||||||
<el-icon class="nav-arrow" :size="12">
|
<el-icon
|
||||||
<ArrowDown v-if="expandedSubMenus.includes(nav.id)" />
|
v-if="nav.hasChildren || nav.children?.length > 0"
|
||||||
<ArrowRight v-else />
|
class="menu-arrow"
|
||||||
|
:size="10"
|
||||||
|
:class="{'menu-arrow--up': expandedMenuId === nav.id}"
|
||||||
|
>
|
||||||
|
<ArrowDown />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="expandedSubMenus.includes(nav.id)" class="nav-sub">
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ==================== 子菜单下拉遮罩 ==================== -->
|
||||||
|
<transition name="fade">
|
||||||
|
<div v-if="expandedMenuId" class="dropdown-mask" @click="closeDropdown"></div>
|
||||||
|
</transition>
|
||||||
|
|
||||||
|
<!-- ==================== 子菜单下拉面板 ==================== -->
|
||||||
|
<transition name="dropdown">
|
||||||
|
<div v-if="expandedMenuId" class="menu-dropdown">
|
||||||
|
<div class="dropdown-scroll">
|
||||||
<div
|
<div
|
||||||
v-for="child in nav.children"
|
v-for="child in navList.find((n) => n.id === expandedMenuId)?.children || []"
|
||||||
:key="child.id"
|
:key="child.id"
|
||||||
class="nav-item nav-item--child"
|
class="dropdown-item"
|
||||||
:class="{'nav-item--active': activeIndex === getTargetUrl(child)}"
|
:class="{'dropdown-item--active': activeIndex === getTargetUrl(child)}"
|
||||||
@click="handleNavClick(child)"
|
@click="handleChildClick(child)"
|
||||||
>
|
>
|
||||||
{{ child.label }}
|
{{ child.label }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 无子菜单 -->
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="nav-item"
|
|
||||||
:class="{'nav-item--active': activeIndex === getTargetUrl(nav)}"
|
|
||||||
@click="handleNavClick(nav)"
|
|
||||||
>
|
|
||||||
<span class="nav-label">{{ nav.label }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
<!-- ==================== Content 内容区域 ==================== -->
|
<!-- ==================== Content 内容区域 ==================== -->
|
||||||
@@ -134,6 +140,20 @@ const toMIIF = () => {
|
|||||||
|
|
||||||
<!-- ==================== Footer 底部 ==================== -->
|
<!-- ==================== Footer 底部 ==================== -->
|
||||||
<div class="mobile-footer">
|
<div class="mobile-footer">
|
||||||
|
<div class="footer-qr">
|
||||||
|
<div class="qr-item">
|
||||||
|
<img src="/static/images/footer/QR_code.png" />
|
||||||
|
<div class="qr-label">企业微信</div>
|
||||||
|
</div>
|
||||||
|
<div class="qr-item">
|
||||||
|
<img src="/static/images/footer/mark.png" />
|
||||||
|
<div class="qr-label">市场联系人</div>
|
||||||
|
</div>
|
||||||
|
<div class="qr-item">
|
||||||
|
<img src="/static/images/footer/sx.png" />
|
||||||
|
<div class="qr-label">陕西联系人</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="footer-info">
|
<div class="footer-info">
|
||||||
<span>Copyright © 2023.Rangu Technology Co.,Ltd.</span>
|
<span>Copyright © 2023.Rangu Technology Co.,Ltd.</span>
|
||||||
<span>燃谷科技(南京)有限公司</span>
|
<span>燃谷科技(南京)有限公司</span>
|
||||||
@@ -160,14 +180,14 @@ const toMIIF = () => {
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%; /* no */
|
width: 100%;
|
||||||
max-width: 100vw;
|
max-width: 100vw;
|
||||||
height: 44px; /* no */
|
height: 44px;
|
||||||
background-color: $bg_color;
|
background-color: $bg_color;
|
||||||
z-index: 110;
|
z-index: 110;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 10px; /* no */
|
padding: 0 10px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
.header-menu-icon {
|
.header-menu-icon {
|
||||||
@@ -177,78 +197,137 @@ const toMIIF = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.header-logo {
|
.header-logo {
|
||||||
|
width: 128px;
|
||||||
|
height: 44px;
|
||||||
background-image: url('/static/images/header/logo.png');
|
background-image: url('/static/images/header/logo.png');
|
||||||
background-size: contain;
|
background-size: 100% 100%;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
width: 100px;
|
|
||||||
height: 32px; /* no */
|
|
||||||
cursor: pointer;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 侧边栏遮罩层 ====================
|
// ==================== 横向滚动菜单栏 ====================
|
||||||
.sidebar-mask {
|
.menu-bar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 44px;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
max-width: 100vw;
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
height: 40px;
|
||||||
z-index: 100;
|
background-color: $bg_color;
|
||||||
|
z-index: 109;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mask-fade-enter-active,
|
.menu-scroll {
|
||||||
.mask-fade-leave-active {
|
display: flex;
|
||||||
transition: opacity 0.3s ease;
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
scrollbar-width: none;
|
||||||
|
padding: 0 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.mask-fade-enter-from,
|
|
||||||
.mask-fade-leave-to {
|
.menu-item {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 0 12px;
|
||||||
|
height: 100%;
|
||||||
|
color: #999999;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--expanded {
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-label {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-arrow {
|
||||||
|
margin-left: 4px;
|
||||||
|
color: #999999;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
|
||||||
|
&--up {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 子菜单下拉遮罩 ====================
|
||||||
|
.dropdown-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 84px;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100vw;
|
||||||
|
height: calc(100vh - 84px);
|
||||||
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
|
z-index: 107;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.25s ease;
|
||||||
|
}
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 侧边栏(浮动层) ====================
|
// ==================== 子菜单下拉面板 ====================
|
||||||
.sidebar {
|
.menu-dropdown {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 84px;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 160px; /* no */
|
width: 100%;
|
||||||
height: 100%;
|
max-width: 100vw;
|
||||||
|
max-height: 240px;
|
||||||
background-color: $bg_color;
|
background-color: $bg_color;
|
||||||
z-index: 105;
|
z-index: 108;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-scroll {
|
||||||
|
max-height: 240px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding-top: 56px; /* no */
|
-webkit-overflow-scrolling: touch;
|
||||||
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.3); /* no */
|
padding: 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-slide-enter-active,
|
.dropdown-item {
|
||||||
.sidebar-slide-leave-active {
|
padding: 12px 20px;
|
||||||
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;
|
color: #999999;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
transition: all 0.2s ease;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background-color: rgba(255, 255, 255, 0.05);
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
@@ -257,37 +336,23 @@ const toMIIF = () => {
|
|||||||
&--active {
|
&--active {
|
||||||
color: $white;
|
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 {
|
.dropdown-enter-active,
|
||||||
overflow: hidden;
|
.dropdown-leave-active {
|
||||||
|
transition:
|
||||||
|
transform 0.25s ease,
|
||||||
|
opacity 0.25s ease;
|
||||||
|
}
|
||||||
|
.dropdown-enter-from,
|
||||||
|
.dropdown-leave-to {
|
||||||
|
transform: translateY(-10px);
|
||||||
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== Content 内容区域 ====================
|
// ==================== Content 内容区域 ====================
|
||||||
.mobile-content {
|
.mobile-content {
|
||||||
margin-top: 44px; /* no */
|
margin-top: 84px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100vw;
|
max-width: 100vw;
|
||||||
@@ -298,11 +363,30 @@ const toMIIF = () => {
|
|||||||
.mobile-footer {
|
.mobile-footer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100vw;
|
max-width: 100vw;
|
||||||
padding: 20px 16px; /* no */
|
padding: 20px 16px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: $bg_color;
|
background-color: $bg_color;
|
||||||
|
|
||||||
|
.footer-qr {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.qr-item {
|
||||||
|
img {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
.qr-label {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.footer-info {
|
.footer-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -1,21 +1,14 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {ref, onMounted, getCurrentInstance} from 'vue'
|
import {ref} from 'vue'
|
||||||
import Swiper from '@/components/Swiper/mobile/index.vue'
|
|
||||||
import Banner from '@/components/Banner/index.vue'
|
import Banner from '@/components/Banner/index.vue'
|
||||||
|
|
||||||
const instance = getCurrentInstance()
|
|
||||||
const $fontSize = instance.appContext.config.globalProperties.$fontSize
|
|
||||||
|
|
||||||
const introduction = '燃谷科技成立于2023年,位于南京鼓楼万谷硅巷,秉承创新、质量和合作精神的低空领域技术创新公司,专注于低空安全监管、城市飞行服务和空间数据应用的企业。公司依托低空大数据、低空物联网、智能算法引擎、空间计算模型四个核心组件,自主研发数智底座,致力于物联网、空间计算领域,助力低空经济蓬勃发展。'
|
const introduction = '燃谷科技成立于2023年,位于南京鼓楼万谷硅巷,秉承创新、质量和合作精神的低空领域技术创新公司,专注于低空安全监管、城市飞行服务和空间数据应用的企业。公司依托低空大数据、低空物联网、智能算法引擎、空间计算模型四个核心组件,自主研发数智底座,致力于物联网、空间计算领域,助力低空经济蓬勃发展。'
|
||||||
|
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
|
|
||||||
const imgList = [
|
const imgList = [
|
||||||
{id: 1, width: 140, height: 90, list: ['main/1.png','main/2.png','main/3.png','main/4.png']},
|
{id: 1, list: ['main/1.png','main/2.png','main/3.png','main/4.png']},
|
||||||
{id: 2, width: 100, height: 145, list: ['main/5.png','main/6.png','main/7.png','main/8.png','main/9.png']},
|
{id: 2, list: ['main/5.png','main/6.png','main/7.png','main/8.png','main/9.png','main/10.png','main/11.png','main/12.png','main/13.png','main/14.png','main/15.png','main/16.png','main/17.png','main/18.png','main/19.png','main/20.png','main/21.png','main/22.png','main/23.png','main/24.png']},
|
||||||
{id: 3, width: 100, height: 145, list: ['main/10.png','main/11.png','main/12.png','main/13.png','main/14.png']},
|
|
||||||
{id: 4, width: 100, height: 145, list: ['main/15.png','main/16.png','main/17.png','main/18.png','main/19.png']},
|
|
||||||
{id: 5, width: 100, height: 145, list: ['main/20.png','main/21.png']},
|
|
||||||
]
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -36,12 +29,9 @@ const imgList = [
|
|||||||
<div class="section">
|
<div class="section">
|
||||||
<h2 class="section-title">资质证书</h2>
|
<h2 class="section-title">资质证书</h2>
|
||||||
<div class="cert-grid">
|
<div class="cert-grid">
|
||||||
<div v-for="listItem in imgList" :key="listItem.id" class="cert-row" :class="{'cert-row--few': listItem.list.length <= 4}">
|
<div v-for="listItem in imgList" :key="listItem.id" class="cert-row">
|
||||||
<div v-for="(img, idx) in listItem.list" :key="idx">
|
<div v-for="(img, idx) in listItem.list" :key="idx" class="cert-item">
|
||||||
<img
|
<img :src="`./static/images/${img}`" />
|
||||||
:style="{width: $fontSize(listItem.width) + 'px', height: $fontSize(listItem.height) + 'px', margin: '3px'}"
|
|
||||||
:src="`./static/images/${img}`"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,12 +64,17 @@ const imgList = [
|
|||||||
|
|
||||||
.cert-grid {
|
.cert-grid {
|
||||||
.cert-row {
|
.cert-row {
|
||||||
display: flex;
|
display: grid;
|
||||||
justify-content: center;
|
grid-template-columns: repeat(2, 1fr);
|
||||||
flex-wrap: wrap;
|
gap: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.cert-item {
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
.cert-row--few {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -27,14 +27,29 @@ const transform = (items) => {
|
|||||||
items.forEach((item) => {
|
items.forEach((item) => {
|
||||||
const parts = item.Key.split('/')
|
const parts = item.Key.split('/')
|
||||||
if (parts.length === 1) {
|
if (parts.length === 1) {
|
||||||
root.children.push({name: parts[0], type: 'file', etag: item.ETag, size: item.Size, lastModified: item.LastModifiedDateTime, fullPath: item.Key})
|
root.children.push({
|
||||||
|
name: parts[0],
|
||||||
|
type: 'file',
|
||||||
|
etag: item.ETag,
|
||||||
|
size: item.Size,
|
||||||
|
lastModified: item.LastModifiedDateTime,
|
||||||
|
fullPath: item.Key,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
let currentParent = root, currentPath = ''
|
let currentParent = root,
|
||||||
|
currentPath = ''
|
||||||
for (let i = 0; i < parts.length - 1; i++) {
|
for (let i = 0; i < parts.length - 1; i++) {
|
||||||
currentPath += (currentPath ? '/' : '') + parts[i]
|
currentPath += (currentPath ? '/' : '') + parts[i]
|
||||||
currentParent = getOrCreateFolder(currentParent, parts[i], currentPath + '/')
|
currentParent = getOrCreateFolder(currentParent, parts[i], currentPath + '/')
|
||||||
}
|
}
|
||||||
currentParent.children.push({name: parts[parts.length - 1], type: 'file', etag: item.ETag, size: item.Size, lastModified: item.LastModifiedDateTime, fullPath: item.Key})
|
currentParent.children.push({
|
||||||
|
name: parts[parts.length - 1],
|
||||||
|
type: 'file',
|
||||||
|
etag: item.ETag,
|
||||||
|
size: item.Size,
|
||||||
|
lastModified: item.LastModifiedDateTime,
|
||||||
|
fullPath: item.Key,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return root.children
|
return root.children
|
||||||
@@ -45,25 +60,36 @@ const treeProps = {children: 'children', label: 'name'}
|
|||||||
const formatSize = (bytes) => {
|
const formatSize = (bytes) => {
|
||||||
if (!bytes) return ''
|
if (!bytes) return ''
|
||||||
const units = ['B', 'KB', 'MB', 'GB']
|
const units = ['B', 'KB', 'MB', 'GB']
|
||||||
let i = 0, size = bytes
|
let i = 0,
|
||||||
while (size >= 1024 && i < units.length - 1) { size /= 1024; i++ }
|
size = bytes
|
||||||
|
while (size >= 1024 && i < units.length - 1) {
|
||||||
|
size /= 1024
|
||||||
|
i++
|
||||||
|
}
|
||||||
return size.toFixed(2) + ' ' + units[i]
|
return size.toFixed(2) + ' ' + units[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
const getList = () => {
|
const getList = () => {
|
||||||
downloadList({recursive: true, version: true, prefix: '', bucket: ''})
|
downloadList({recursive: true, version: true, prefix: '', bucket: ''})
|
||||||
.then((res) => { if (res.code == 0 && res?.data?.length > 0) list.value = transform(res.data) })
|
.then((res) => {
|
||||||
|
if (res.code == 0 && res?.data?.length > 0) list.value = transform(res.data)
|
||||||
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadFileFN = (data) => {
|
const downloadFileFN = (data) => {
|
||||||
downloadFile({bucket: '', objname: data.fullPath, expiresMinutes: 60, isDownload: true})
|
downloadFile({bucket: '', objname: data.fullPath, expiresMinutes: 60, isDownload: true})
|
||||||
.then((res) => { if (res.code === 0 && res.data) window.open(res.data) })
|
.then((res) => {
|
||||||
|
if (res.code === 0 && res.data) window.open(res.data)
|
||||||
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
const showMsg = () => ElMessage.warning('敬请期待!')
|
const showMsg = () => ElMessage.warning('敬请期待!')
|
||||||
const openImg = (type) => { imgType.value = type; showImgType.value = true }
|
const openImg = (type) => {
|
||||||
|
imgType.value = type
|
||||||
|
showImgType.value = true
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -90,13 +116,34 @@ const openImg = (type) => { imgType.value = type; showImgType.value = true }
|
|||||||
<div class="section">
|
<div class="section">
|
||||||
<h2 class="section-title">文件下载</h2>
|
<h2 class="section-title">文件下载</h2>
|
||||||
<div class="file-list">
|
<div class="file-list">
|
||||||
<el-tree v-if="list && list.length" :data="list" :props="treeProps" accordion node-key="name" default-expand-all>
|
<el-tree
|
||||||
|
v-if="list && list.length"
|
||||||
|
:data="list"
|
||||||
|
:props="treeProps"
|
||||||
|
accordion
|
||||||
|
node-key="name"
|
||||||
|
default-expand-all
|
||||||
|
>
|
||||||
<template #default="{data}">
|
<template #default="{data}">
|
||||||
<div class="tree-node">
|
<div class="tree-node">
|
||||||
<span class="node-label">{{ data.name }}</span>
|
<span class="node-label">{{ data.name }}</span>
|
||||||
<template v-if="data.type === 'file'">
|
<template v-if="data.type === 'file'">
|
||||||
<span class="file-size">{{ formatSize(data.size) }}</span>
|
<span class="file-size">{{ formatSize(data.size) }}</span>
|
||||||
<svg class="download-icon" @click.stop="downloadFileFN(data)" viewBox="0 0 24 24" fill="none"><path d="M12 3V15M12 15L8 11M12 15L16 11" stroke="#4A90D9" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M4 17V19C4 20.1046 4.89543 21 6 21H18C19.1046 21 20 20.1046 20 19V17" stroke="#4A90D9" stroke-width="2" stroke-linecap="round"/></svg>
|
<svg class="download-icon" @click.stop="downloadFileFN(data)" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path
|
||||||
|
d="M12 3V15M12 15L8 11M12 15L16 11"
|
||||||
|
stroke="#4A90D9"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M4 17V19C4 20.1046 4.89543 21 6 21H18C19.1046 21 20 20.1046 20 19V17"
|
||||||
|
stroke="#4A90D9"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -110,10 +157,13 @@ const openImg = (type) => { imgType.value = type; showImgType.value = true }
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.page { width: 100%; }
|
.page {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.section {
|
.section {
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
|
margin-bottom: 20px;
|
||||||
.section-title {
|
.section-title {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
@@ -130,7 +180,11 @@ const openImg = (type) => { imgType.value = type; showImgType.value = true }
|
|||||||
.app-card {
|
.app-card {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 120px;
|
height: 120px;
|
||||||
img { width: 100%; height: 100%; object-fit: contain; }
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,18 +195,48 @@ const openImg = (type) => { imgType.value = type; showImgType.value = true }
|
|||||||
padding: 12px;
|
padding: 12px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
.empty-tip { text-align: center; color: #999; font-size: 14px; padding: 30px 0; }
|
.empty-tip {
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 30px 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-node {
|
.tree-node {
|
||||||
display: flex; align-items: center; width: 100%; padding: 4px 0; gap: 6px;
|
display: flex;
|
||||||
.node-label { flex: 1; font-size: 13px; color: #333; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
align-items: center;
|
||||||
.file-size { font-size: 11px; color: #999; white-space: nowrap; min-width: 60px; text-align: right; }
|
width: 100%;
|
||||||
.download-icon { width: 18px; height: 18px; flex-shrink: 0; cursor: pointer; }
|
padding: 4px 0;
|
||||||
|
gap: 6px;
|
||||||
|
.node-label {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #333;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.file-size {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #999;
|
||||||
|
white-space: nowrap;
|
||||||
|
min-width: 60px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.download-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-tree) {
|
::v-deep(.el-tree) {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
.el-tree-node__content { height: auto; padding: 4px 0; }
|
.el-tree-node__content {
|
||||||
|
height: auto;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,8 +2,12 @@
|
|||||||
import {ref, onMounted} from 'vue'
|
import {ref, onMounted} from 'vue'
|
||||||
import Banner from '@/components/Banner/index.vue'
|
import Banner from '@/components/Banner/index.vue'
|
||||||
|
|
||||||
const toCompany = () => {
|
const toCompany = (type) => {
|
||||||
|
if (type === 'nj') {
|
||||||
window.open('https://www.amap.com/search?query=%E7%87%83%E8%B0%B7%E7%A7%91%E6%8A%80(%E5%8D%97%E4%BA%AC)%E6%9C%89%E9%99%90%E5%85%AC%E5%8F%B8')
|
window.open('https://www.amap.com/search?query=%E7%87%83%E8%B0%B7%E7%A7%91%E6%8A%80(%E5%8D%97%E4%BA%AC)%E6%9C%89%E9%99%90%E5%85%AC%E5%8F%B8')
|
||||||
|
} else {
|
||||||
|
window.open('https://ditu.amap.com/search?query=%E9%99%95%E8%A5%BF%E7%9C%81%E8%A5%BF%E5%AE%89%E5%B8%82%E9%9B%81%E5%A1%94%E5%8C%BA%E8%A5%BF%E6%B2%A3%E8%B7%AF56%E5%8F%B7%E4%B8%87%E7%A7%91%E6%B1%87%E6%99%BA%E4%B8%AD%E5%BF%83')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -20,7 +24,15 @@ const toCompany = () => {
|
|||||||
<div class="info-item">邮箱:company@rangutech.com</div>
|
<div class="info-item">邮箱:company@rangutech.com</div>
|
||||||
<div class="info-item">地址:江苏省南京市鼓楼区万谷硅巷5F</div>
|
<div class="info-item">地址:江苏省南京市鼓楼区万谷硅巷5F</div>
|
||||||
</div>
|
</div>
|
||||||
<img class="map-img" src="/static/images/link/map.png" @click="toCompany" />
|
<img class="map-img" src="/static/images/link/map.png" @click="toCompany('nj')" />
|
||||||
|
</div>
|
||||||
|
<div class="link-box">
|
||||||
|
<div class="info-list">
|
||||||
|
<div class="info-item name">燃谷科技(陕西)有限公司</div>
|
||||||
|
<div class="info-item">电话:15009261000</div>
|
||||||
|
<div class="info-item">地址:陕西省西安市雁塔区西沣路56号万科汇智中心2506</div>
|
||||||
|
</div>
|
||||||
|
<img class="map-img" src="/static/images/link/shanxi.png" @click="toCompany('sx')" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -44,6 +56,7 @@ const toCompany = () => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
|
||||||
.info-list {
|
.info-list {
|
||||||
.info-item {
|
.info-item {
|
||||||
|
|||||||
@@ -84,7 +84,8 @@ function toBack() {
|
|||||||
// ============== 硬件区域 ==============
|
// ============== 硬件区域 ==============
|
||||||
.hardware-section {
|
.hardware-section {
|
||||||
.hardware-img {
|
.hardware-img {
|
||||||
width: 100%;
|
margin-left: 10%;
|
||||||
|
width: 80%;
|
||||||
height: auto;
|
height: auto;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user