This commit is contained in:
yiqiuyang
2026-07-27 10:06:41 +08:00
parent 11166cb057
commit 5900370c58
20 changed files with 512 additions and 99 deletions

View File

@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
@ -10,9 +10,9 @@
name="description"
content="燃谷科技位于南京鼓楼万谷硅巷,秉承创新、质量和合作精神的低空领域技术创新公司,专注于低空安全监管、城市飞行服务和空间数据应用的企业。"
/>
<script src="/config.js"></script>
<script src="/config/nav.js"></script>
<script src="/config/product.js"></script>
<script src="/config.js"></script>
</head>
<body>
<div id="app"></div>

View File

@ -1,3 +1,7 @@
window.config = {
baseUrl: 'http://192.168.3.10:9121/',
downloadUrl: 'https://work.rangutech.cn:1001/',
featureUrl: 'https://sys.rangutech.com/#/',
assetsUrl: 'https://insider.rangutech.com/app_static_data/images/',
// downloadUrl: 'http://192.168.3.5:9193/',
}

View File

@ -25,7 +25,10 @@ window.nav = {
{id: 4, label: '新闻中心', url: '/news', hasChildren: false},
{id: 5, label: '关于我们', url: '/about', hasChildren: false},
{id: 6, label: '联系我们', url: '/link', hasChildren: false},
{id: 7, label: '资源下载', url: '/download', hasChildren: false},
{id: 8, label: '演示系统', url: window.config.featureUrl, hasChildren: false, open: true},
],
footer: [
{
id: 1,
@ -43,13 +46,19 @@ window.nav = {
// url: '/services',
// hasChildren: false,
// },
// {
// id: 3,
// label: '软件下载',
// url: '/download',
// hasChildren: false,
// },
{id: 4, label: '新闻中心', url: '/news', hasChildren: false},
{
id: 3,
label: '资源下载',
url: '/download',
hasChildren: true,
children: [
{id: 3.1, label: '资源下载', url: '/download'},
{id: 3.2, label: 'Android', url: '', downloadType: 'android'},
{id: 3.3, label: 'iOS', url: '', downloadType: 'ios'},
{id: 3.4, label: '客户端', url: '', downloadType: 'pc'},
],
},
// {id: 4, label: '新闻中心', url: '/news', hasChildren: false},
{
id: 5,
label: '关于燃谷',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 5.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 690 KiB

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 679 KiB

After

Width:  |  Height:  |  Size: 6.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -44,3 +44,20 @@ export function saveDetail(data) {
data,
})
}
// 获取下载资源
export function downloadList(params) {
return request({
url: `${window.config.downloadUrl}api/minio/Object/list`,
method: 'get',
params,
})
}
export function downloadFile(params) {
return request({
url: `${window.config.downloadUrl}api/minio/Object/url`,
method: 'get',
params,
})
}

View File

@ -0,0 +1,52 @@
<script setup>
defineProps({
type: {
type: String,
required: true,
},
})
const visible = defineModel({default: false})
const options = {
ios: {label: 'iOS', img: `${window.config.assetsUrl}app/ios.png`},
android: {label: 'Android', img: `${window.config.assetsUrl}app/android.png`},
}
</script>
<template>
<el-dialog v-model="visible" title="扫码下载" width="500px" :close-on-click-modal="true">
<div class="dialog-body flex j-c a-c">
<div class="download-option">
<img :src="options[type]?.img" :alt="type" />
<span class="label">{{ options[type]?.label }}</span>
</div>
</div>
</el-dialog>
</template>
<style lang="scss" scoped>
.dialog-body {
gap: 40px;
padding: 20px 0;
.download-option {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
cursor: pointer;
img {
width: 140px;
height: 140px;
object-fit: contain;
}
.label {
font-size: 16px;
color: #333;
}
}
}
</style>

View File

@ -28,6 +28,8 @@ export const useNavStore = defineStore(
}
},
{
persist: true,
persist: {
storage: sessionStorage,
},
}
)

View File

@ -1,55 +1,261 @@
<script setup>
import {ref, onMounted} from 'vue'
import Swiper from '@/components/swiper/index.vue'
import Swiper from '@/components/Swiper/index.vue'
import {downloadFile, downloadList} from '@/api'
import Banner from '@/components/Banner/index.vue'
import DownloadImg from '@/components/downloadImg/index.vue'
import {ElMessage} from 'element-plus'
const currentPage = ref(1)
const currentFilePage = ref(1)
onMounted(() => {})
onMounted(() => {
getList()
})
const list = ref([
{id: 1, imgUrl: 'main/1.png', label: 'XXX平台', subLabel: '具有XXX功能'},
{id: 2, imgUrl: 'main/1.png', label: 'XXX平台', subLabel: '具有XXX功能'},
{id: 3, imgUrl: 'main/1.png', label: 'XXX平台', subLabel: '具有XXX功能'},
{id: 4, imgUrl: 'main/1.png', label: 'XXX平台', subLabel: '具有XXX功能'},
{id: 5, imgUrl: 'main/1.png', label: 'XXX平台', subLabel: '具有XXX功能'},
{id: 6, imgUrl: 'main/1.png', label: 'XXX平台', subLabel: '具有XXX功能'},
])
const list = ref(null)
const downloadFile = (id) => {
console.log('id===>', id)
/**
* 将带有 Key路径的扁平列表转换为树形结构
* @param {Array} items - 原始数据,每个元素包含 Key, ETag, Size, LastModifiedDateTime 等
* @returns {Array} 树形数组,每个元素为文件或文件夹节点
*/
const transform = (items) => {
const root = {children: []}
const getOrCreateFolder = (parent, name, fullPath) => {
let folder = parent.children.find((child) => child.name === name && child.type === 'folder')
if (!folder) {
folder = {name, type: 'folder', children: [], fullPath}
parent.children.push(folder)
}
return folder
}
items.forEach((item) => {
const parts = item.Key.split('/')
if (parts.length === 1) {
root.children.push({
name: parts[0],
type: 'file',
etag: item.ETag,
size: item.Size,
lastModified: item.LastModifiedDateTime,
fullPath: item.Key,
})
} else {
let currentParent = root
let currentPath = ''
for (let i = 0; i < parts.length - 1; i++) {
const folderName = parts[i]
currentPath += (currentPath ? '/' : '') + folderName
currentParent = getOrCreateFolder(currentParent, folderName, currentPath + '/')
}
const fileName = parts[parts.length - 1]
currentParent.children.push({
name: fileName,
type: 'file',
etag: item.ETag,
size: item.Size,
lastModified: item.LastModifiedDateTime,
fullPath: item.Key,
})
}
})
return root.children
}
// el-tree 的 props 配置
const treeProps = {
children: 'children',
label: 'name',
}
/**
* 格式化文件大小
*/
const formatSize = (bytes) => {
if (!bytes) return ''
const units = ['B', 'KB', 'MB', 'GB']
let i = 0
let size = bytes
while (size >= 1024 && i < units.length - 1) {
size /= 1024
i++
}
return size.toFixed(2) + ' ' + units[i]
}
/**
* 获取下载的文件列表
*/
const getList = () => {
let params = {
recursive: true,
version: true,
prefix: '',
bucket: '',
}
downloadList(params)
.then((res) => {
console.log(res)
if (res.code == 0 && res?.data?.length > 0) {
list.value = transform(res.data)
console.log('list.value===>', list.value)
}
})
.catch((error) => {
console.log('error', error)
})
}
/**
* 下载文件
*/
const downloadFileFN = (data) => {
console.log('data===>', data)
let query = {
bucket: '',
objname: data.fullPath,
expiresMinutes: 60,
isDownload: true,
}
downloadFile(query)
.then((res) => {
console.log('res===>', res)
if (res.code === 0 && res.data) {
window.open(res.data)
}
})
.catch((err) => {
console.log('err===>', err)
})
}
// 提示用户敬请期待
const showMsg = () => {
ElMessage.warning('敬请期待!')
}
const imgType = ref('ios')
const showImgType = ref(false)
const openImg = (type) => {
imgType.value = type
showImgType.value = true
}
</script>
<template>
<div class="page">
<div class="placeholder"></div>
<Banner class="banner" img="banner/download.png" />
<Swiper
id="download"
title="软件下载"
id="one"
title="资源下载"
v-model="currentPage"
:sourceWidth="1600"
:sourceHeight="800"
:sourceWidth="1200"
:sourceHeight="400"
:show-pagination="false"
:auto-play="false"
:showHover="false"
>
<template #default>
<div class="my-card flex j-s wrap">
<div class="card flex j-s" v-for="item in list" :key="item.id">
<div class="img">
<img :src="`./static/main/${item.imgUrl}`" alt="" />
</div>
<div class="content">
<div class="label">{{ item.label }}</div>
<div class="sub-label">{{ item.subLabel }}</div>
<div class="download" @click="downloadFile(item.id)">
点击下载
</div>
</div>
<div class="my-card flex j-s">
<div class="img" @click="openImg('ios')">
<img src="/static/images/download/ios.png" alt="" />
</div>
<div class="img" @click="openImg('android')">
<img src="/static/images/download/android.png" alt="" />
</div>
<div class="img" @click="showMsg">
<img src="/static/images/download/windows.png" alt="" />
</div>
</div>
</template>
</Swiper>
<Swiper
id="file"
title="文件下载"
v-model="currentFilePage"
:sourceWidth="1200"
:sourceHeight="400"
:show-pagination="false"
:auto-play="false"
:showHover="false"
>
<div class="file-list">
<el-tree v-if="list && list.length" :data="list" :props="treeProps" accordion node-key="name">
<template #default="{data}">
<div class="tree-node">
<!-- 文件夹图标 -->
<svg
v-if="data.type === 'folder'"
class="node-icon folder-icon"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2 6C2 4.89543 2.89543 4 4 4H9.17157C9.70199 4 10.2107 4.21071 10.5858 4.58579L12 6H20C21.1046 6 22 6.89543 22 8V18C22 19.1046 21.1046 20 20 20H4C2.89543 20 2 19.1046 2 18V6Z"
fill="#F7C948"
stroke="#E5A800"
stroke-width="1.2"
/>
</svg>
<!-- 文件图标 -->
<svg
v-else
class="node-icon file-icon"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 2H14.1716C14.702 2 15.2107 2.21071 15.5858 2.58579L19.4142 6.41421C19.7893 6.78929 20 7.29799 20 7.82843V20C20 21.1046 19.1046 22 18 22H6C4.89543 22 4 21.1046 4 20V4C4 2.89543 4.89543 2 6 2Z"
fill="#A8C5E0"
stroke="#7DA8CC"
stroke-width="1.2"
/>
<path d="M14 2V6C14 7.10457 14.8954 8 16 8H20" stroke="#7DA8CC" stroke-width="1.2" fill="none" />
</svg>
<span class="node-label">{{ data.name }}</span>
<!-- 文件信息仅文件类型显示 -->
<template v-if="data.type === 'file'">
<span class="file-size">{{ formatSize(data.size) }}</span>
<span class="file-date">{{ data.lastModified }}</span>
<!-- 下载图标 -->
<svg
class="download-icon"
@click.stop="downloadFileFN(data)"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<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>
</div>
</template>
</el-tree>
<div v-else class="empty-tip">暂无下载资源</div>
</div>
</Swiper>
<DownloadImg :type="imgType" v-model="showImgType" />
</div>
</template>
@ -57,40 +263,133 @@ const downloadFile = (id) => {
.my-card {
width: 100%;
height: 100%;
.card {
width: 48%;
height: 170px;
box-sizing: border-box;
.img {
width: 300px;
height: 360px;
padding: 20px;
box-sizing: border-box;
.img {
width: 550px;
img {
padding: 20px;
width: 100%;
height: 100%;
background: #d9d9d9;
}
.content {
margin-top: 20px;
text-align: left;
.label {
font-family: 'PingFang SC';
font-weight: 600;
font-size: 26px;
color: $black;
margin-bottom: 20px;
}
.sub-label {
font-family: 'PingFang SC';
font-weight: 400;
font-size: 16px;
color: #999999;
margin-bottom: 30px;
}
.download {
font-family: 'PingFang SC';
font-weight: 400;
font-size: 18px;
color: #0389ff;
&:hover {
cursor: pointer;
transition: all 0.3s ease;
transform: translateY(-10px);
}
}
}
}
.file-title {
text-align: center;
font-family: 'PingFang SC';
font-weight: 600;
font-size: 1.5rem;
color: #333333;
&::after {
content: '';
display: block;
width: 120px;
height: 6px;
background-color: #0389ff;
position: relative;
left: 50%;
top: 20px;
translate: -50%;
}
}
.file-list {
width: 1200px;
max-height: 500px;
border: 1px solid #e5e5e5;
margin: 0px auto;
padding: 20px;
box-sizing: border-box;
overflow-y: auto;
text-align: left;
.empty-tip {
text-align: center;
color: #999;
font-size: 14px;
padding: 40px 0;
}
}
/* el-tree 节点自定义样式 */
.tree-node {
display: flex;
align-items: center;
width: 100%;
padding: 4px 0;
gap: 8px;
.node-icon {
width: 20px;
height: 20px;
flex-shrink: 0;
}
.node-label {
flex: 1;
font-size: 14px;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.file-size {
font-size: 12px;
color: #999;
white-space: nowrap;
min-width: 80px;
text-align: right;
}
.file-date {
font-size: 12px;
color: #999;
white-space: nowrap;
min-width: 140px;
text-align: right;
}
.download-icon {
width: 20px;
height: 20px;
flex-shrink: 0;
cursor: pointer;
transition: opacity 0.2s;
&:hover {
opacity: 0.7;
}
}
}
/* 覆盖 el-tree 默认样式 */
::v-deep(.el-tree) {
background: transparent;
.el-tree-node__content {
height: auto;
padding: 6px 0;
&:hover {
background-color: #f5f7fa;
}
}
.el-tree-node__expand-icon {
color: #666;
}
}
</style>

View File

@ -23,6 +23,10 @@ function toUrl(item) {
query: {type: cryptoEncrypt(JSON.stringify(item))},
})
}
function goToFeatural() {
window.open(window.config.featureUrl)
}
</script>
<template>
@ -31,7 +35,7 @@ function toUrl(item) {
<video autoplay muted loop @loadeddata="onVideoLoaded">
<source src="/static/video/display.mp4" type="video/mp4" />
</video>
<div class="banner-link"><span>进入演示系统</span></div>
<div class="banner-link" @click="goToFeatural"><span>进入演示系统</span></div>
</div>
<Swiper
id="home_swiper"

View File

@ -7,24 +7,26 @@ const navStore = useNavStore()
const router = useRouter()
const footerNav = ref([])
const emit = defineEmits(['downloadClick'])
onMounted(() => {
footerNav.value = window.nav.footer
})
const navClick = (url) => {
navStore.changeNavIndex(url)
router.push(url)
const navClick = (node) => {
if (node.downloadType) {
emit('downloadClick', node.downloadType)
return
}
if (!node.url) return
navStore.changeNavIndex(node.url)
router.push(node.url)
}
</script>
<template>
<ul class="footer-nav flex j-s">
<NavNode
v-for="node in footerNav"
:key="node.id"
:node="node"
@nav-click="navClick"
/>
<NavNode v-for="node in footerNav" :key="node.id" :node="node" @nav-click="navClick" />
</ul>
</template>

View File

@ -8,12 +8,12 @@ defineProps({
const emits = defineEmits(['navClick'])
const onClick = (url) => emits('navClick', url)
const onClick = (node) => emits('navClick', node)
</script>
<template>
<li class="footer-nav__item">
<div class="item-label" @click="onClick(node.url)">
<div class="item-label" @click="onClick(node)">
{{ node.label }}
</div>
@ -41,8 +41,8 @@ const onClick = (url) => emits('navClick', url)
cursor: pointer;
transition: all 0.3s ease;
&:hover {
-webkit-transform: translateY(-8px);
transform: translateY(-8px);
-webkit-transform: translateY(-2px);
transform: translateY(-2px);
}
}
}

View File

@ -3,6 +3,21 @@ import {ref, onMounted} from 'vue'
import Logo from './components/logo.vue'
import Nav from './components/nav.vue'
import QRCode from './components/QRCode.vue'
import DownloadImg from '@/components/downloadImg/index.vue'
import {ElMessage} from 'element-plus'
const imgType = ref('ios')
const showImgType = ref(false)
const handleDownloadClick = (type) => {
if (type === 'pc') {
ElMessage.warning('敬请期待!')
return
}
imgType.value = type
showImgType.value = true
}
onMounted(() => {})
const toMIIF = () => {
@ -16,7 +31,7 @@ const toMIIF = () => {
<Logo id="logo" />
<!-- 内容 -->
<Nav id="nav" />
<Nav id="nav" @download-click="handleDownloadClick" />
<!-- 二维码 -->
<QRCode id="QR" />
@ -29,6 +44,8 @@ const toMIIF = () => {
<span class="url" @click="toMIIF">苏ICP备2023030548号-1</span>
<span class="url" @click="toMIIF">苏ICP备2023030548号-5</span>
</div>
<DownloadImg :type="imgType" v-model="showImgType" />
</div>
</template>

View File

@ -1,19 +1,39 @@
<script setup>
import {ref, onMounted, watch, onUnmounted} from 'vue'
import {useRouter} from 'vue-router'
import {useNavStore} from '@/store/nav'
import {storeToRefs} from 'pinia'
// Store 相关
const navStore = useNavStore()
const {navIndex} = storeToRefs(navStore)
const router = useRouter()
// 响应式数据
const navList = ref([])
const activeIndex = ref('/')
// 根据 url 查找导航项(含子级)
const findNavItem = (url) => {
for (const nav of navList.value) {
if (nav.url === url && !nav.hasChildren) return nav
if (nav.children) {
const child = nav.children.find((c) => c.url === url)
if (child) return child
}
}
return null
}
// 处理导航选择
const handleSelect = (key) => {
const navItem = findNavItem(key)
if (navItem?.open) {
window.open(navItem.url)
return
}
navStore.changeNavIndex(key)
router.push(key)
}
// 监听 store 中的 navIndex 变化
@ -47,33 +67,20 @@ onUnmounted(() => {
active-text-color="#FFFFFF"
text-color="#999999"
background-color="$bg_color"
router
>
<template v-for="nav in navList" :key="nav.id">
<!-- 有子菜单的情况 -->
<el-sub-menu
v-if="nav.hasChildren || nav?.children?.length > 0"
:index="nav.url"
popper-class="pepper"
>
<el-sub-menu v-if="nav.hasChildren || nav?.children?.length > 0" :index="nav.url" popper-class="pepper">
<template #title>
<span>{{ nav.label }}</span>
</template>
<el-menu-item
v-for="child in nav.children"
:key="child.id"
:index="child.url"
>
<el-menu-item v-for="child in nav.children" :key="child.id" :index="child.url">
{{ child.label }}
</el-menu-item>
</el-sub-menu>
<!-- 无子菜单的情况 -->
<el-menu-item
v-else
:index="nav.url"
:class="{'is-active': activeIndex === nav.url}"
>
<el-menu-item v-else :index="nav.url" :class="{'is-active': activeIndex === nav.url}">
{{ nav.label }}
</el-menu-item>
</template>