updated
|
|
@ -0,0 +1,13 @@
|
||||||
|
# 如需添加更多环境变量,请以 VITE_APP_ 开头声明
|
||||||
|
# 在代码中使用 import.meta.env.VITE_APP_XXX 获取指定变量
|
||||||
|
|
||||||
|
# 环境配置标识
|
||||||
|
VITE_APP_ENV='development'
|
||||||
|
|
||||||
|
# api接口请求地址
|
||||||
|
VITE_APP_BASE_API='http://test.gopmas.com/rs'
|
||||||
|
VITE_APP_DS_API='http://test.gopmas.com/ds'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
# 如需添加更多环境变量,请以 VITE_APP_ 开头声明
|
||||||
|
# 在代码中使用 import.meta.env.VITE_APP_XXX 获取指定变量
|
||||||
|
|
||||||
|
# 环境配置标识
|
||||||
|
VITE_APP_ENV='production'
|
||||||
|
|
||||||
|
# api接口请求地址
|
||||||
|
VITE_APP_BASE_API='/rescue'
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
*.zip
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>深海地球物理数据管理可视化平台</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"name": "geophysical-data-platform",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vue-tsc -b && vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@element-plus/icons-vue": "^2.3.1",
|
||||||
|
"axios": "^1.6.7",
|
||||||
|
"cesium": "^1.133.1",
|
||||||
|
"element-plus": "^2.5.6",
|
||||||
|
"md5": "^2.3.0",
|
||||||
|
"pinia": "^3.0.3",
|
||||||
|
"vue": "^3.4.38",
|
||||||
|
"vue-router": "^4.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^25.9.2",
|
||||||
|
"@vitejs/plugin-vue": "^5.1.3",
|
||||||
|
"sass": "^1.71.1",
|
||||||
|
"typescript": "^5.5.3",
|
||||||
|
"vite": "^5.4.2",
|
||||||
|
"vite-plugin-cesium": "^1.2.23",
|
||||||
|
"vue-tsc": "^2.1.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 21 KiB |
|
|
@ -0,0 +1,230 @@
|
||||||
|
<template>
|
||||||
|
<el-container>
|
||||||
|
<el-header class="header-bg" :class="{ 'header-home': isHomePage }">
|
||||||
|
<div class="header-content">
|
||||||
|
<div class="logo">
|
||||||
|
<img src="@/assets/logo.png" alt="Logo" />
|
||||||
|
<span>深海地球物理数据管理可视化平台</span>
|
||||||
|
</div>
|
||||||
|
<el-menu
|
||||||
|
mode="horizontal"
|
||||||
|
:default-active="activeMenu"
|
||||||
|
:ellipsis="false"
|
||||||
|
router
|
||||||
|
class="main-menu"
|
||||||
|
>
|
||||||
|
<!-- 登录后才显示的菜单项 -->
|
||||||
|
<template v-if="authStore.isLoggedIn">
|
||||||
|
<el-menu-item index="/">首页</el-menu-item>
|
||||||
|
<el-menu-item index="/search">数据入库</el-menu-item>
|
||||||
|
<el-menu-item index="/demand">数据检索</el-menu-item>
|
||||||
|
<el-menu-item index="/statistics">统计分析</el-menu-item>
|
||||||
|
<el-menu-item index="/contact">联系我们</el-menu-item>
|
||||||
|
</template>
|
||||||
|
<!-- 登录/退出按钮 -->
|
||||||
|
<el-menu-item v-if="!authStore.isLoggedIn" index="/login">登录</el-menu-item>
|
||||||
|
<el-menu-item v-else @click="logout">退出登录</el-menu-item>
|
||||||
|
</el-menu>
|
||||||
|
</div>
|
||||||
|
<img class="bg" v-show="activeMenu == '/statistics'" src="@/assets/bg.jpg" alt="背景图片" />
|
||||||
|
</el-header>
|
||||||
|
<el-main>
|
||||||
|
<router-view></router-view>
|
||||||
|
</el-main>
|
||||||
|
<el-footer>
|
||||||
|
<div>
|
||||||
|
<div class="title">
|
||||||
|
<img src="@/assets/logo.png" alt="Logo" />
|
||||||
|
<span>中国科学院深海科学与工程研究所</span>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<div class="item">
|
||||||
|
<div class="icon">
|
||||||
|
<img src="@/assets/icon_@.png" alt="Logo" />
|
||||||
|
</div>
|
||||||
|
<span>联系方式:chencx@idsse.ac.cn</span>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<div class="icon">
|
||||||
|
<img src="@/assets/icon_area.png" alt="Logo" />
|
||||||
|
</div>
|
||||||
|
<span>地址:三亚市鹿回头路28号</span>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<div class="icon">
|
||||||
|
<img src="@/assets/icon_mail.png" alt="Logo" />
|
||||||
|
</div>
|
||||||
|
<span>邮编:572000</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-footer>
|
||||||
|
</el-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch, computed } from 'vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { useAuthStore } from '@/store/authStore'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
|
const activeMenu = ref(route.path)
|
||||||
|
const isHomePage = computed(() => route.path === '/')
|
||||||
|
|
||||||
|
// 或者只监听特定属性
|
||||||
|
watch(
|
||||||
|
() => route.path,
|
||||||
|
(newPath) => {
|
||||||
|
activeMenu.value = newPath
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 退出登录方法
|
||||||
|
const logout = () => {
|
||||||
|
authStore.logout()
|
||||||
|
router.push('/login')
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.el-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-bg {
|
||||||
|
background-image: url(@/assets/footer_bg.png);
|
||||||
|
|
||||||
|
&.header-home {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 10;
|
||||||
|
background: linear-gradient(180deg, rgba(0, 30, 70, 0.85) 0%, rgba(0, 30, 70, 0.4) 100%);
|
||||||
|
background-image: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-header {
|
||||||
|
position: relative;
|
||||||
|
color: white;
|
||||||
|
height: 100px;
|
||||||
|
z-index: 0;
|
||||||
|
|
||||||
|
.bg {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100vh - 120px);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-right: 40px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-menu {
|
||||||
|
background-color: transparent;
|
||||||
|
border-bottom: none;
|
||||||
|
|
||||||
|
:deep(.el-menu-item) {
|
||||||
|
color: white;
|
||||||
|
font-size: 18px;
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
background-color: transparent;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-main {
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-footer {
|
||||||
|
height: 120px;
|
||||||
|
background-image: url(@/assets/footer_bg.png);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #fff;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
display: flex;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 20px;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
background: #263954;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 5px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 13px;
|
||||||
|
height: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,234 @@
|
||||||
|
import { MOCK_GEOPHYSICAL_DATA, MOCK_STATISTICS } from '@/mock/geophysicalData'
|
||||||
|
import { MOCK_DATA_FILES, nextFileId } from '@/mock/dataFiles'
|
||||||
|
|
||||||
|
/** @type {typeof MOCK_GEOPHYSICAL_DATA} */
|
||||||
|
let dataStore = [...MOCK_GEOPHYSICAL_DATA]
|
||||||
|
/** @type {Record<string, Array>} */
|
||||||
|
let fileStore = { ...MOCK_DATA_FILES }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据大类与类型映射
|
||||||
|
*/
|
||||||
|
const CATEGORY_TYPE_MAP = {
|
||||||
|
acoustic: ['ship_multibeam', 'submarine_multibeam', 'sidescan'],
|
||||||
|
seismic: ['shallow_seismic', 'seismic_2d', 'seismic_3d', 'obn_seismic'],
|
||||||
|
potential: ['gravity', 'ship_magnetic', 'submarine_magnetic'],
|
||||||
|
ocean: ['ctd', 'adcp', 'heat_flow']
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询地球物理数据
|
||||||
|
* @param {Object} params
|
||||||
|
* @returns {Promise<{page: {records: Array, total: number}}>}
|
||||||
|
*/
|
||||||
|
export function dataPage(params = {}) {
|
||||||
|
const {
|
||||||
|
start = 0,
|
||||||
|
limit = 10,
|
||||||
|
query = '',
|
||||||
|
data_type = '',
|
||||||
|
data_status = '',
|
||||||
|
data_secrecy = '',
|
||||||
|
data_code = '',
|
||||||
|
category = ''
|
||||||
|
} = params
|
||||||
|
|
||||||
|
let records = [...MOCK_GEOPHYSICAL_DATA]
|
||||||
|
|
||||||
|
if (query) {
|
||||||
|
const keyword = query.toLowerCase()
|
||||||
|
records = records.filter((item) =>
|
||||||
|
item.data_code.toLowerCase().includes(keyword) ||
|
||||||
|
item.data_name.toLowerCase().includes(keyword) ||
|
||||||
|
item.voyage_name.toLowerCase().includes(keyword)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (category && CATEGORY_TYPE_MAP[category]) {
|
||||||
|
const types = CATEGORY_TYPE_MAP[category]
|
||||||
|
records = records.filter((item) => types.includes(item.data_type))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data_code) {
|
||||||
|
records = records.filter((item) => item.data_code.includes(data_code))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data_type) {
|
||||||
|
records = records.filter((item) => item.data_type === data_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data_status) {
|
||||||
|
records = records.filter((item) => item.data_status === data_status)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data_secrecy) {
|
||||||
|
records = records.filter((item) => item.data_secrecy === data_secrecy)
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = records.length
|
||||||
|
const pageRecords = records.slice(start, start + limit)
|
||||||
|
|
||||||
|
return Promise.resolve({
|
||||||
|
success: true,
|
||||||
|
page: {
|
||||||
|
records: pageRecords,
|
||||||
|
total
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取主页统计数据
|
||||||
|
* @returns {Promise<{array: Array}>}
|
||||||
|
*/
|
||||||
|
export function reportMain() {
|
||||||
|
return Promise.resolve({
|
||||||
|
success: true,
|
||||||
|
array: [MOCK_STATISTICS]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本地 admin 登录
|
||||||
|
* @param {Object} data
|
||||||
|
* @returns {Promise<{success: boolean, entity?: string, message?: string}>}
|
||||||
|
*/
|
||||||
|
export function localLogin(data) {
|
||||||
|
const { loginName, loginPass } = data
|
||||||
|
if (loginName === 'admin' && loginPass === '21232f297a57a5a743894a0e4a801fc3') {
|
||||||
|
return Promise.resolve({
|
||||||
|
success: true,
|
||||||
|
entity: 'admin-token-pdata'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return Promise.resolve({
|
||||||
|
success: false,
|
||||||
|
message: '用户名或密码错误'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询单条地球物理数据
|
||||||
|
* @param {Object} params
|
||||||
|
* @returns {Promise<{success: boolean, entity: Object}>}
|
||||||
|
*/
|
||||||
|
export function dataFind(params = {}) {
|
||||||
|
const { data_id } = params
|
||||||
|
const entity = dataStore.find((item) => String(item.id) === String(data_id))
|
||||||
|
if (!entity) {
|
||||||
|
return Promise.resolve({ success: false, message: '数据不存在' })
|
||||||
|
}
|
||||||
|
return Promise.resolve({ success: true, entity: { ...entity } })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增或编辑地球物理数据
|
||||||
|
* @param {Object} data
|
||||||
|
* @returns {Promise<{success: boolean, entity: Object}>}
|
||||||
|
*/
|
||||||
|
export function dataEdit(data = {}) {
|
||||||
|
const now = new Date().toISOString().slice(0, 19).replace('T', ' ')
|
||||||
|
if (data.id) {
|
||||||
|
const index = dataStore.findIndex((item) => String(item.id) === String(data.id))
|
||||||
|
if (index === -1) {
|
||||||
|
return Promise.resolve({ success: false, message: '数据不存在' })
|
||||||
|
}
|
||||||
|
dataStore[index] = { ...dataStore[index], ...data }
|
||||||
|
return Promise.resolve({ success: true, entity: { ...dataStore[index] } })
|
||||||
|
}
|
||||||
|
|
||||||
|
const newId = Math.max(...dataStore.map((item) => item.id), 0) + 1
|
||||||
|
const entity = {
|
||||||
|
id: newId,
|
||||||
|
data_status: '待审核',
|
||||||
|
fill_time: data.fill_time || now,
|
||||||
|
...data
|
||||||
|
}
|
||||||
|
dataStore.unshift(entity)
|
||||||
|
fileStore[newId] = fileStore[newId] || []
|
||||||
|
return Promise.resolve({ success: true, entity: { ...entity } })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据关联资源列表
|
||||||
|
* @param {Object} params
|
||||||
|
* @returns {Promise<{success: boolean, array: Array}>}
|
||||||
|
*/
|
||||||
|
export function dataFileList(params = {}) {
|
||||||
|
const { data_id } = params
|
||||||
|
const list = fileStore[data_id] || []
|
||||||
|
return Promise.resolve({ success: true, array: [...list] })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件(mock)
|
||||||
|
* @param {Object} params
|
||||||
|
* @returns {Promise<{success: boolean, entity: Object}>}
|
||||||
|
*/
|
||||||
|
export function dataFileUpload(params = {}) {
|
||||||
|
const { data_id, file_name, file_size = 0 } = params
|
||||||
|
if (!data_id) {
|
||||||
|
return Promise.resolve({ success: false, message: '请先保存数据信息' })
|
||||||
|
}
|
||||||
|
if (!file_name) {
|
||||||
|
return Promise.resolve({ success: false, message: '请选择文件' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = {
|
||||||
|
id: nextFileId(),
|
||||||
|
file_name,
|
||||||
|
file_size: Number(file_size) || 0,
|
||||||
|
source_type: 'upload',
|
||||||
|
file_path: `/local/upload/${file_name}`,
|
||||||
|
create_time: new Date().toISOString().slice(0, 19).replace('T', ' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fileStore[data_id]) {
|
||||||
|
fileStore[data_id] = []
|
||||||
|
}
|
||||||
|
fileStore[data_id].unshift(record)
|
||||||
|
return Promise.resolve({ success: true, entity: record })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定云资源路径(mock)
|
||||||
|
* @param {Object} params
|
||||||
|
* @returns {Promise<{success: boolean, entity: Object}>}
|
||||||
|
*/
|
||||||
|
export function dataCloudPathAdd(params = {}) {
|
||||||
|
const { data_id, cloud_path, resource_name = '' } = params
|
||||||
|
if (!data_id) {
|
||||||
|
return Promise.resolve({ success: false, message: '请先保存数据信息' })
|
||||||
|
}
|
||||||
|
if (!cloud_path) {
|
||||||
|
return Promise.resolve({ success: false, message: '请输入云资源路径' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileName = resource_name || cloud_path.split('/').pop() || cloud_path
|
||||||
|
const record = {
|
||||||
|
id: nextFileId(),
|
||||||
|
file_name: fileName,
|
||||||
|
file_size: 0,
|
||||||
|
source_type: 'cloud',
|
||||||
|
file_path: cloud_path,
|
||||||
|
create_time: new Date().toISOString().slice(0, 19).replace('T', ' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fileStore[data_id]) {
|
||||||
|
fileStore[data_id] = []
|
||||||
|
}
|
||||||
|
fileStore[data_id].unshift(record)
|
||||||
|
return Promise.resolve({ success: true, entity: record })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除数据资源
|
||||||
|
* @param {Object} params
|
||||||
|
* @returns {Promise<{success: boolean}>}
|
||||||
|
*/
|
||||||
|
export function dataFileDelete(params = {}) {
|
||||||
|
const { data_id, file_id } = params
|
||||||
|
const list = fileStore[data_id] || []
|
||||||
|
fileStore[data_id] = list.filter((item) => String(item.id) !== String(file_id))
|
||||||
|
return Promise.resolve({ success: true })
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
import {
|
||||||
|
DATA_SPACES,
|
||||||
|
DIRECTORY_TREE,
|
||||||
|
FILES_BY_FOLDER,
|
||||||
|
DEFAULT_FOLDER_ID
|
||||||
|
} from '@/mock/dataSpace.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据空间列表
|
||||||
|
* @returns {Promise<{success: boolean, array: Array}>}
|
||||||
|
*/
|
||||||
|
export function getDataSpaces() {
|
||||||
|
return Promise.resolve({ success: true, array: DATA_SPACES })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取目录树
|
||||||
|
* @returns {Promise<{success: boolean, array: Array}>}
|
||||||
|
*/
|
||||||
|
export function getDirectoryTree() {
|
||||||
|
return Promise.resolve({ success: true, array: DIRECTORY_TREE })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取目录下文件列表
|
||||||
|
* @param {Object} params
|
||||||
|
* @returns {Promise<{success: boolean, array: Array, folder: Object}>}
|
||||||
|
*/
|
||||||
|
export function getFolderFiles(params = {}) {
|
||||||
|
const { folder_id = DEFAULT_FOLDER_ID } = params
|
||||||
|
const files = FILES_BY_FOLDER[folder_id] || []
|
||||||
|
return Promise.resolve({
|
||||||
|
success: true,
|
||||||
|
folder_id,
|
||||||
|
array: [...files]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据节点 ID 查找树节点
|
||||||
|
* @param {string} nodeId
|
||||||
|
* @param {Array} nodes
|
||||||
|
* @returns {Object|null}
|
||||||
|
*/
|
||||||
|
export function findTreeNode(nodeId, nodes = DIRECTORY_TREE) {
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node.id === nodeId) return node
|
||||||
|
if (node.children?.length) {
|
||||||
|
const found = findTreeNode(nodeId, node.children)
|
||||||
|
if (found) return found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 登录
|
||||||
|
export const login = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/frame/optr/login.htm',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取参数列表
|
||||||
|
export const getParam = () => {
|
||||||
|
return request({
|
||||||
|
url: '/param/list.htm',
|
||||||
|
method: 'post'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取航次列表
|
||||||
|
export const voyageReg = (data) => {
|
||||||
|
return request({
|
||||||
|
baseURL: data.baseURL,
|
||||||
|
url: '/api/voyage/list.htm',
|
||||||
|
method: 'post'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关联航次
|
||||||
|
export const sampleAdd = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/rescue/reg/sample/add.htm',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取已关联航次列表
|
||||||
|
export const sampleList = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/rescue/reg/sample/list.htm',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除已关联航次
|
||||||
|
export const sampleDelete = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/rescue/reg/sample/delete.htm',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取任务列表
|
||||||
|
export const regPage = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/rescue/reg/page.htm',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询任务详情
|
||||||
|
export const regFind = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/rescue/reg/find.htm',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增/编辑任务
|
||||||
|
export const regEdit = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/rescue/reg/edit.htm',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 附件列表
|
||||||
|
export const regFileList = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/rescue/reg/file/list.htm',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看附件接口
|
||||||
|
export const regFileView = () => {
|
||||||
|
return request({
|
||||||
|
url: '/rescue/reg/file/view.htm',
|
||||||
|
method: 'post'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 附件删除
|
||||||
|
export const regFileDelete = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/rescue/reg/file/delete.htm',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统计接口
|
||||||
|
export const reportMain = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/report/main.htm',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完成任务
|
||||||
|
export const regComplete = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/rescue/reg/complete.htm',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 1003 KiB |
|
After Width: | Height: | Size: 142 KiB |
|
After Width: | Height: | Size: 192 KiB |
|
After Width: | Height: | Size: 242 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 158 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 326 B |
|
After Width: | Height: | Size: 439 B |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 556 B |
|
After Width: | Height: | Size: 517 B |
|
After Width: | Height: | Size: 408 B |
|
After Width: | Height: | Size: 411 B |
|
After Width: | Height: | Size: 411 B |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
|
@ -0,0 +1,41 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
defineProps<{ msg: string }>()
|
||||||
|
|
||||||
|
const count = ref(0)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h1>{{ msg }}</h1>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<button type="button" @click="count++">count is {{ count }}</button>
|
||||||
|
<p>
|
||||||
|
Edit
|
||||||
|
<code>components/HelloWorld.vue</code> to test HMR
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Check out
|
||||||
|
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
||||||
|
>create-vue</a
|
||||||
|
>, the official Vue + Vite starter
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Learn more about IDE Support for Vue in the
|
||||||
|
<a
|
||||||
|
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
|
||||||
|
target="_blank"
|
||||||
|
>Vue Docs Scaling up Guide</a
|
||||||
|
>.
|
||||||
|
</p>
|
||||||
|
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.read-the-docs {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
<template>
|
||||||
|
<svg :width="size" :height="size" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<!-- 文件夹 -->
|
||||||
|
<g v-if="type === 'folder'">
|
||||||
|
<path d="M8 18C8 15.7909 9.79086 14 12 14H26L30 18H52C54.2091 18 56 19.7909 56 22V48C56 50.2091 54.2091 52 52 52H12C9.79086 52 8 50.2091 8 48V18Z" fill="#FFC107" opacity="0.85"/>
|
||||||
|
<path d="M8 22H56V48C56 50.2091 54.2091 52 52 52H12C9.79086 52 8 50.2091 8 48V22Z" fill="#FFD54F"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- RAR 压缩包 -->
|
||||||
|
<g v-else-if="ext === 'rar' || ext === 'zip'">
|
||||||
|
<rect x="14" y="8" width="36" height="48" rx="4" fill="#FFC107"/>
|
||||||
|
<rect x="14" y="8" width="36" height="14" rx="4" fill="#FFB300"/>
|
||||||
|
<text x="32" y="19" text-anchor="middle" fill="#fff" font-size="9" font-weight="bold">{{ ext.toUpperCase() }}</text>
|
||||||
|
<path d="M22 30H42M22 36H38M22 42H34" stroke="#fff" stroke-width="2" stroke-linecap="round" opacity="0.7"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- NetCDF / 数据文件 -->
|
||||||
|
<g v-else-if="ext === 'nc' || ext === 'sgy'">
|
||||||
|
<rect x="14" y="8" width="36" height="48" rx="4" fill="#42A5F5"/>
|
||||||
|
<rect x="14" y="8" width="36" height="14" rx="4" fill="#1E88E5"/>
|
||||||
|
<text x="32" y="19" text-anchor="middle" fill="#fff" font-size="8" font-weight="bold">{{ ext.toUpperCase() }}</text>
|
||||||
|
<path d="M20 28L32 24L44 28V44H20V28Z" stroke="#fff" stroke-width="1.5" fill="none" opacity="0.8"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- 图片 -->
|
||||||
|
<g v-else-if="ext === 'png' || ext === 'jpg' || ext === 'tif'">
|
||||||
|
<rect x="14" y="10" width="36" height="44" rx="4" fill="#66BB6A"/>
|
||||||
|
<circle cx="24" cy="24" r="4" fill="#fff" opacity="0.8"/>
|
||||||
|
<path d="M18 46L28 34L36 40L46 28V50H18V46Z" fill="#fff" opacity="0.6"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- PDF -->
|
||||||
|
<g v-else-if="ext === 'pdf'">
|
||||||
|
<rect x="14" y="8" width="36" height="48" rx="4" fill="#EF5350"/>
|
||||||
|
<text x="32" y="36" text-anchor="middle" fill="#fff" font-size="10" font-weight="bold">PDF</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- 默认文件 -->
|
||||||
|
<g v-else>
|
||||||
|
<rect x="14" y="8" width="36" height="48" rx="4" fill="#90A4AE"/>
|
||||||
|
<path d="M22 28H42M22 34H38M22 40H34" stroke="#fff" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 文件/文件夹类型图标
|
||||||
|
*/
|
||||||
|
defineProps({
|
||||||
|
type: { type: String, default: 'file' },
|
||||||
|
ext: { type: String, default: '' },
|
||||||
|
size: { type: [Number, String], default: 64 }
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<template>
|
||||||
|
<svg :width="size" :height="size" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 14H36V34H12V14Z" stroke="currentColor" stroke-width="2"/>
|
||||||
|
<path d="M18 20H30" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<path d="M18 26H26" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<path d="M18 32H22" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 数据集/记录图标
|
||||||
|
*/
|
||||||
|
defineProps({
|
||||||
|
size: { type: [Number, String], default: 36 }
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<template>
|
||||||
|
<svg :width="size" :height="size" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="6" y="6" width="16" height="16" rx="2" stroke="currentColor" stroke-width="2"/>
|
||||||
|
<rect x="26" y="6" width="16" height="16" rx="2" stroke="currentColor" stroke-width="2"/>
|
||||||
|
<rect x="6" y="26" width="16" height="16" rx="2" stroke="currentColor" stroke-width="2"/>
|
||||||
|
<rect x="26" y="26" width="16" height="16" rx="2" stroke="currentColor" stroke-width="2"/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 网格统计图标
|
||||||
|
*/
|
||||||
|
defineProps({
|
||||||
|
size: { type: [Number, String], default: 36 }
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<template>
|
||||||
|
<svg :width="size" :height="size" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="24" cy="24" r="14" stroke="currentColor" stroke-width="2"/>
|
||||||
|
<path d="M24 10V24L32 28" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<path d="M24 24L16 20" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 磁力/位场图标
|
||||||
|
*/
|
||||||
|
defineProps({
|
||||||
|
size: { type: [Number, String], default: 36 }
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<template>
|
||||||
|
<svg :width="size" :height="size" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<ellipse cx="24" cy="28" rx="14" ry="8" stroke="currentColor" stroke-width="2"/>
|
||||||
|
<path d="M10 28C14 20 34 20 38 28" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<path d="M24 12V20" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<circle cx="24" cy="10" r="2" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 多波束/声学图标
|
||||||
|
*/
|
||||||
|
defineProps({
|
||||||
|
size: { type: [Number, String], default: 36 }
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<template>
|
||||||
|
<svg :width="size" :height="size" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M8 36L16 24L24 30L32 18L40 26" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M8 38H40" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<circle cx="16" cy="24" r="2" fill="currentColor"/>
|
||||||
|
<circle cx="24" cy="30" r="2" fill="currentColor"/>
|
||||||
|
<circle cx="32" cy="18" r="2" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 波形/地震数据图标
|
||||||
|
*/
|
||||||
|
defineProps({
|
||||||
|
size: { type: [Number, String], default: 36 }
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
:width="size"
|
||||||
|
:height="size"
|
||||||
|
viewBox="0 0 48 48"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<!-- 多波束 -->
|
||||||
|
<g v-if="type === 'multibeam'">
|
||||||
|
<path d="M24 8L28 16H36L29.5 21.5L31.5 30L24 25.5L16.5 30L18.5 21.5L12 16H20L24 8Z" fill="url(#stat-blue)" opacity="0.25"/>
|
||||||
|
<path d="M8 34C12 28 20 26 24 26C28 26 36 28 40 34" stroke="url(#stat-blue)" stroke-width="2.2" stroke-linecap="round"/>
|
||||||
|
<path d="M16 34C18 31 22 30 24 30C26 30 30 31 32 34" stroke="url(#stat-blue)" stroke-width="1.8" stroke-linecap="round" opacity="0.7"/>
|
||||||
|
<circle cx="24" cy="14" r="2.5" fill="url(#stat-blue)"/>
|
||||||
|
<path d="M24 16.5V22" stroke="url(#stat-blue)" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- 地震数据 -->
|
||||||
|
<g v-else-if="type === 'seismic'">
|
||||||
|
<rect x="8" y="10" width="32" height="28" rx="4" fill="url(#stat-orange)" opacity="0.15"/>
|
||||||
|
<path d="M10 30L16 22L22 28L28 16L34 24L38 18" stroke="url(#stat-orange)" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<circle cx="16" cy="22" r="2" fill="#e67e22"/>
|
||||||
|
<circle cx="28" cy="16" r="2" fill="#e67e22"/>
|
||||||
|
<circle cx="34" cy="24" r="2" fill="#e67e22"/>
|
||||||
|
<path d="M10 36H38" stroke="url(#stat-orange)" stroke-width="1.5" stroke-linecap="round" opacity="0.5"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- 磁力数据 -->
|
||||||
|
<g v-else-if="type === 'magnetic'">
|
||||||
|
<circle cx="24" cy="24" r="14" stroke="url(#stat-green)" stroke-width="2" opacity="0.35"/>
|
||||||
|
<path d="M24 10V14M24 34V38M10 24H14M34 24H38" stroke="url(#stat-green)" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<path d="M18 18L21 21M27 27L30 30M30 18L27 21M21 27L18 30" stroke="url(#stat-green)" stroke-width="1.8" stroke-linecap="round" opacity="0.6"/>
|
||||||
|
<circle cx="24" cy="24" r="5" fill="url(#stat-green)"/>
|
||||||
|
<path d="M24 19V29M19 24H29" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- 侧扫数据 -->
|
||||||
|
<g v-else-if="type === 'sidescan'">
|
||||||
|
<path d="M6 24H42" stroke="url(#stat-purple)" stroke-width="2" stroke-linecap="round" opacity="0.4"/>
|
||||||
|
<path d="M24 8C30 14 34 20 34 24C34 28 30 34 24 40C18 34 14 28 14 24C14 20 18 14 24 8Z" stroke="url(#stat-purple)" stroke-width="2.2" fill="url(#stat-purple)" fill-opacity="0.12"/>
|
||||||
|
<path d="M24 14C28 18 30 21 30 24C30 27 28 30 24 34C20 30 18 27 18 24C18 21 20 18 24 14Z" stroke="url(#stat-purple)" stroke-width="1.6" opacity="0.7"/>
|
||||||
|
<circle cx="24" cy="24" r="2.5" fill="url(#stat-purple)"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- 数据集 -->
|
||||||
|
<g v-else-if="type === 'dataset'">
|
||||||
|
<rect x="10" y="8" width="22" height="28" rx="3" fill="url(#stat-blue)" opacity="0.15"/>
|
||||||
|
<rect x="14" y="12" width="22" height="28" rx="3" fill="#fff" stroke="url(#stat-blue)" stroke-width="2"/>
|
||||||
|
<path d="M20 20H30M20 26H28M20 32H26" stroke="url(#stat-blue)" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<circle cx="32" cy="32" r="8" fill="url(#stat-blue)"/>
|
||||||
|
<path d="M29 32L31 34L35 30" stroke="#fff" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- 重力数据 -->
|
||||||
|
<g v-else-if="type === 'gravity'">
|
||||||
|
<circle cx="24" cy="26" r="12" stroke="url(#stat-teal)" stroke-width="2" fill="url(#stat-teal)" fill-opacity="0.1"/>
|
||||||
|
<ellipse cx="24" cy="26" rx="12" ry="4" stroke="url(#stat-teal)" stroke-width="1.5" opacity="0.5"/>
|
||||||
|
<path d="M24 14V18" stroke="url(#stat-teal)" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<circle cx="24" cy="12" r="2" fill="url(#stat-teal)"/>
|
||||||
|
<path d="M18 22L20 24M28 28L30 30M30 22L28 24M20 28L18 30" stroke="url(#stat-teal)" stroke-width="1.5" stroke-linecap="round" opacity="0.55"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- 海洋观测 -->
|
||||||
|
<g v-else-if="type === 'ocean'">
|
||||||
|
<path d="M6 32C10 28 14 30 18 28C22 26 26 30 30 28C34 26 38 28 42 32" stroke="url(#stat-cyan)" stroke-width="2.2" stroke-linecap="round"/>
|
||||||
|
<path d="M6 38C10 34 14 36 18 34C22 32 26 36 30 34C34 32 38 34 42 38" stroke="url(#stat-cyan)" stroke-width="2" stroke-linecap="round" opacity="0.6"/>
|
||||||
|
<rect x="20" y="10" width="8" height="18" rx="2" fill="url(#stat-cyan)" opacity="0.2" stroke="url(#stat-cyan)" stroke-width="1.8"/>
|
||||||
|
<circle cx="24" cy="14" r="2" fill="url(#stat-cyan)"/>
|
||||||
|
<path d="M22 20H26M22 24H26" stroke="url(#stat-cyan)" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- 数据记录 -->
|
||||||
|
<g v-else>
|
||||||
|
<rect x="8" y="10" width="32" height="28" rx="4" fill="url(#stat-indigo)" opacity="0.12"/>
|
||||||
|
<path d="M14 18H34M14 24H30M14 30H26" stroke="url(#stat-indigo)" stroke-width="2.2" stroke-linecap="round"/>
|
||||||
|
<rect x="28" y="8" width="12" height="12" rx="3" fill="url(#stat-indigo)"/>
|
||||||
|
<path d="M31 14H37M34 11V17" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/>
|
||||||
|
<circle cx="18" cy="18" r="1.5" fill="url(#stat-indigo)" opacity="0.5"/>
|
||||||
|
<circle cx="18" cy="24" r="1.5" fill="url(#stat-indigo)" opacity="0.5"/>
|
||||||
|
<circle cx="18" cy="30" r="1.5" fill="url(#stat-indigo)" opacity="0.5"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="stat-blue" x1="8" y1="8" x2="40" y2="40" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#1a8fd4"/>
|
||||||
|
<stop offset="1" stop-color="#0056a4"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="stat-orange" x1="8" y1="10" x2="40" y2="38" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#f5a623"/>
|
||||||
|
<stop offset="1" stop-color="#e67e22"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="stat-green" x1="10" y1="10" x2="38" y2="38" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#2ecc71"/>
|
||||||
|
<stop offset="1" stop-color="#1a9d5c"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="stat-purple" x1="14" y1="8" x2="34" y2="40" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#9b59b6"/>
|
||||||
|
<stop offset="1" stop-color="#7a1ad4"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="stat-teal" x1="12" y1="12" x2="36" y2="38" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#1ad4a8"/>
|
||||||
|
<stop offset="1" stop-color="#0c8a6a"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="stat-cyan" x1="6" y1="10" x2="42" y2="38" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#4dc9f6"/>
|
||||||
|
<stop offset="1" stop-color="#1296db"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="stat-indigo" x1="8" y1="8" x2="40" y2="38" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#667eea"/>
|
||||||
|
<stop offset="1" stop-color="#4a5fc1"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 首页统计卡片精美图标
|
||||||
|
*/
|
||||||
|
defineProps({
|
||||||
|
type: { type: String, required: true },
|
||||||
|
size: { type: [Number, String], default: 40 }
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
/**
|
||||||
|
* 地球物理数据类型定义
|
||||||
|
*/
|
||||||
|
export const DATA_CATEGORIES = [
|
||||||
|
{
|
||||||
|
label: '多波束数据',
|
||||||
|
value: 'multibeam',
|
||||||
|
children: [
|
||||||
|
{ label: '船载多波束', value: 'ship_multibeam' },
|
||||||
|
{ label: '潜载多波束', value: 'submarine_multibeam' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '侧扫数据',
|
||||||
|
value: 'sidescan',
|
||||||
|
children: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '地震数据',
|
||||||
|
value: 'seismic',
|
||||||
|
children: [
|
||||||
|
{ label: '浅地层地震剖面数据', value: 'shallow_seismic' },
|
||||||
|
{ label: '2D地震数据', value: 'seismic_2d' },
|
||||||
|
{ label: '3D地震数据', value: 'seismic_3d' },
|
||||||
|
{ label: 'OBN地震数据', value: 'obn_seismic' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '重力数据',
|
||||||
|
value: 'gravity',
|
||||||
|
children: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '磁力数据',
|
||||||
|
value: 'magnetic',
|
||||||
|
children: [
|
||||||
|
{ label: '船载磁力', value: 'ship_magnetic' },
|
||||||
|
{ label: '潜载磁力', value: 'submarine_magnetic' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'CTD数据',
|
||||||
|
value: 'ctd',
|
||||||
|
children: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ADCP数据',
|
||||||
|
value: 'adcp',
|
||||||
|
children: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '海底热流数据',
|
||||||
|
value: 'heat_flow',
|
||||||
|
children: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扁平化数据类型选项(含子类型)
|
||||||
|
* @returns {Array<{label: string, value: string, category: string}>}
|
||||||
|
*/
|
||||||
|
export function getFlatDataTypeOptions() {
|
||||||
|
const options = []
|
||||||
|
DATA_CATEGORIES.forEach((category) => {
|
||||||
|
if (category.children.length === 0) {
|
||||||
|
options.push({
|
||||||
|
label: category.label,
|
||||||
|
value: category.value,
|
||||||
|
category: category.value
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
category.children.forEach((child) => {
|
||||||
|
options.push({
|
||||||
|
label: child.label,
|
||||||
|
value: child.value,
|
||||||
|
category: category.value
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据类型值获取显示名称
|
||||||
|
* @param {string} typeValue
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function getDataTypeLabel(typeValue) {
|
||||||
|
const option = getFlatDataTypeOptions().find((item) => item.value === typeValue)
|
||||||
|
return option ? option.label : typeValue
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据类型值获取所属大类
|
||||||
|
* @param {string} typeValue
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function getDataCategory(typeValue) {
|
||||||
|
const option = getFlatDataTypeOptions().find((item) => item.value === typeValue)
|
||||||
|
return option ? option.category : ''
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
import acousticImg from '@/assets/home/acoustic.jpg'
|
||||||
|
import seismicImg from '@/assets/home/seismic.jpg'
|
||||||
|
import potentialImg from '@/assets/home/potential.jpg'
|
||||||
|
import oceanImg from '@/assets/home/ocean.jpg'
|
||||||
|
import multibeamImg from '@/assets/home/multibeam.jpg'
|
||||||
|
import submarineImg from '@/assets/home/submarine.jpg'
|
||||||
|
import sidescanImg from '@/assets/home/sidescan.jpg'
|
||||||
|
import gravityImg from '@/assets/home/gravity.jpg'
|
||||||
|
import magneticImg from '@/assets/home/magnetic.jpg'
|
||||||
|
import ctdImg from '@/assets/home/ctd.jpg'
|
||||||
|
import adcpImg from '@/assets/home/adcp.jpg'
|
||||||
|
import heatflowImg from '@/assets/home/heatflow.jpg'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据资源大类封面图
|
||||||
|
*/
|
||||||
|
export const CATEGORY_IMAGES = {
|
||||||
|
acoustic: acousticImg,
|
||||||
|
seismic: seismicImg,
|
||||||
|
potential: potentialImg,
|
||||||
|
ocean: oceanImg
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 各数据类型封面图
|
||||||
|
*/
|
||||||
|
export const DATA_TYPE_IMAGES = {
|
||||||
|
ship_multibeam: multibeamImg,
|
||||||
|
submarine_multibeam: submarineImg,
|
||||||
|
sidescan: sidescanImg,
|
||||||
|
shallow_seismic: seismicImg,
|
||||||
|
seismic_2d: seismicImg,
|
||||||
|
seismic_3d: seismicImg,
|
||||||
|
obn_seismic: seismicImg,
|
||||||
|
gravity: gravityImg,
|
||||||
|
ship_magnetic: magneticImg,
|
||||||
|
submarine_magnetic: magneticImg,
|
||||||
|
ctd: ctdImg,
|
||||||
|
adcp: adcpImg,
|
||||||
|
heat_flow: heatflowImg
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据类型对应封面图
|
||||||
|
* @param {string} typeValue
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function getDataTypeImage(typeValue) {
|
||||||
|
return DATA_TYPE_IMAGES[typeValue] || acousticImg
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取资源大类封面图
|
||||||
|
* @param {string} categoryId
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function getCategoryImage(categoryId) {
|
||||||
|
return CATEGORY_IMAGES[categoryId] || acousticImg
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
import ElementPlus from 'element-plus'
|
||||||
|
import 'element-plus/dist/index.css'
|
||||||
|
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||||
|
import zhCn from 'element-plus/es/locale/lang/zh-cn'; // 引入中文语言包
|
||||||
|
import router from './router'
|
||||||
|
import App from './App.vue'
|
||||||
|
import './style.css'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
|
const pinia = createPinia()
|
||||||
|
|
||||||
|
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||||
|
app.component(key, component)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use(pinia)
|
||||||
|
|
||||||
|
app.use(ElementPlus, {
|
||||||
|
locale: zhCn
|
||||||
|
})
|
||||||
|
app.use(router)
|
||||||
|
app.mount('#app')
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
/**
|
||||||
|
* 数据资源 mock 存储(文件与云路径)
|
||||||
|
*/
|
||||||
|
export const MOCK_DATA_FILES = {
|
||||||
|
1: [
|
||||||
|
{
|
||||||
|
id: 101,
|
||||||
|
file_name: 'multibeam_raw_001.all',
|
||||||
|
file_size: 2936012800,
|
||||||
|
source_type: 'upload',
|
||||||
|
file_path: '/local/upload/multibeam_raw_001.all',
|
||||||
|
create_time: '2024-03-15 11:00:00'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
6: [
|
||||||
|
{
|
||||||
|
id: 601,
|
||||||
|
file_name: 'seismic_3d_volume.sgy',
|
||||||
|
file_size: 45097156608,
|
||||||
|
source_type: 'cloud',
|
||||||
|
file_path: 's3://geodata-bucket/seismic/qiongdongnan/seismic_3d_volume.sgy',
|
||||||
|
create_time: '2024-01-21 09:10:00'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileIdSeed = 1000
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成新文件记录 ID
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
export function nextFileId() {
|
||||||
|
fileIdSeed += 1
|
||||||
|
return fileIdSeed
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,198 @@
|
||||||
|
import { DATA_CATEGORIES } from '@/constants/dataTypes.js'
|
||||||
|
|
||||||
|
export const DATA_SPACES = [
|
||||||
|
{ id: 'geophys-main', label: '深海地球物理数据空间' },
|
||||||
|
{ id: 'geophys-south', label: '南海地球物理调查数据空间' },
|
||||||
|
{ id: 'geophys-pacific', label: '西太平洋地球物理数据空间' }
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据数据类型常量生成目录树
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
function buildDirectoryTree() {
|
||||||
|
const children = DATA_CATEGORIES.map((category) => {
|
||||||
|
if (category.children.length > 0) {
|
||||||
|
return {
|
||||||
|
id: category.value,
|
||||||
|
label: category.label,
|
||||||
|
children: category.children.map((child) => ({
|
||||||
|
id: child.value,
|
||||||
|
label: child.label
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: category.value,
|
||||||
|
label: category.label
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return [{
|
||||||
|
id: 'root',
|
||||||
|
label: '深海地球物理数据空间',
|
||||||
|
children
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 目录树结构(按地球物理数据类型组织)
|
||||||
|
*/
|
||||||
|
export const DIRECTORY_TREE = buildDirectoryTree()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 各目录下文件
|
||||||
|
* @type {Record<string, Array>}
|
||||||
|
*/
|
||||||
|
export const FILES_BY_FOLDER = {
|
||||||
|
ship_multibeam: [
|
||||||
|
{
|
||||||
|
id: 'mb-ship-1',
|
||||||
|
name: 'MB-ship-line001.all',
|
||||||
|
ext: 'all',
|
||||||
|
size: 2147483648,
|
||||||
|
update_time: '2024-03-15 10:30:00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mb-ship-2',
|
||||||
|
name: 'MB-ship-line002.all',
|
||||||
|
ext: 'all',
|
||||||
|
size: 1879048192,
|
||||||
|
update_time: '2024-03-15 11:20:00'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
submarine_multibeam: [
|
||||||
|
{
|
||||||
|
id: 'mb-sub-1',
|
||||||
|
name: 'HROV-MB-survey-001.nc',
|
||||||
|
ext: 'nc',
|
||||||
|
size: 536870912,
|
||||||
|
update_time: '2024-03-18 09:15:00'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
sidescan: [
|
||||||
|
{
|
||||||
|
id: 'ss-1',
|
||||||
|
name: 'SSS-track-001.xtf',
|
||||||
|
ext: 'xtf',
|
||||||
|
size: 1048576000,
|
||||||
|
update_time: '2024-03-20 14:00:00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ss-2',
|
||||||
|
name: 'SSS-mosaic.tif',
|
||||||
|
ext: 'tif',
|
||||||
|
size: 524288000,
|
||||||
|
update_time: '2024-03-21 09:30:00'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
shallow_seismic: [
|
||||||
|
{
|
||||||
|
id: 'sh-se-1',
|
||||||
|
name: 'CHIRP-profile-001.sgy',
|
||||||
|
ext: 'sgy',
|
||||||
|
size: 3221225472,
|
||||||
|
update_time: '2024-04-02 08:30:00'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
seismic_2d: [
|
||||||
|
{
|
||||||
|
id: 'se-2d-1',
|
||||||
|
name: '2D-line001.sgy',
|
||||||
|
ext: 'sgy',
|
||||||
|
size: 4294967296,
|
||||||
|
update_time: '2024-04-02 09:45:00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'se-2d-2',
|
||||||
|
name: '2D-line002.sgy',
|
||||||
|
ext: 'sgy',
|
||||||
|
size: 3758096384,
|
||||||
|
update_time: '2024-04-03 10:15:00'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
seismic_3d: [
|
||||||
|
{
|
||||||
|
id: 'se-3d-1',
|
||||||
|
name: '3D-cube-volume.segy',
|
||||||
|
ext: 'segy',
|
||||||
|
size: 8589934592,
|
||||||
|
update_time: '2024-04-05 16:20:00'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
obn_seismic: [
|
||||||
|
{
|
||||||
|
id: 'obn-1',
|
||||||
|
name: 'OBN-receiver-nodes.rar',
|
||||||
|
ext: 'rar',
|
||||||
|
size: 6442450944,
|
||||||
|
update_time: '2024-04-08 11:00:00'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
gravity: [
|
||||||
|
{
|
||||||
|
id: 'grav-1',
|
||||||
|
name: 'gravity-anomaly-grid.grd',
|
||||||
|
ext: 'grd',
|
||||||
|
size: 67108864,
|
||||||
|
update_time: '2024-04-10 08:00:00'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
ship_magnetic: [
|
||||||
|
{
|
||||||
|
id: 'mag-ship-1',
|
||||||
|
name: 'MAG-ship-track-001.mgd',
|
||||||
|
ext: 'mgd',
|
||||||
|
size: 134217728,
|
||||||
|
update_time: '2024-04-12 13:45:00'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
submarine_magnetic: [
|
||||||
|
{
|
||||||
|
id: 'mag-sub-1',
|
||||||
|
name: 'HROV-MAG-survey-001.dat',
|
||||||
|
ext: 'dat',
|
||||||
|
size: 268435456,
|
||||||
|
update_time: '2024-04-14 15:20:00'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
ctd: [
|
||||||
|
{
|
||||||
|
id: 'ctd-1',
|
||||||
|
name: 'CTD-cast-001.cnv',
|
||||||
|
ext: 'cnv',
|
||||||
|
size: 2097152,
|
||||||
|
update_time: '2024-04-16 07:30:00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ctd-2',
|
||||||
|
name: 'CTD-profile-summary.csv',
|
||||||
|
ext: 'csv',
|
||||||
|
size: 524288,
|
||||||
|
update_time: '2024-04-16 08:00:00'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
adcp: [
|
||||||
|
{
|
||||||
|
id: 'adcp-1',
|
||||||
|
name: 'ADCP-current-profile.pd0',
|
||||||
|
ext: 'pd0',
|
||||||
|
size: 4194304,
|
||||||
|
update_time: '2024-04-18 10:00:00'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
heat_flow: [
|
||||||
|
{
|
||||||
|
id: 'hf-1',
|
||||||
|
name: 'heat-flow-station-001.txt',
|
||||||
|
ext: 'txt',
|
||||||
|
size: 65536,
|
||||||
|
update_time: '2024-04-20 14:30:00'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认选中目录
|
||||||
|
*/
|
||||||
|
export const DEFAULT_FOLDER_ID = 'ship_multibeam'
|
||||||
|
|
@ -0,0 +1,211 @@
|
||||||
|
/**
|
||||||
|
* 地球物理数据 mock 数据集
|
||||||
|
*/
|
||||||
|
export const MOCK_GEOPHYSICAL_DATA = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
data_code: 'MB-SH-2024-001',
|
||||||
|
data_type: 'ship_multibeam',
|
||||||
|
data_name: '南海北部船载多波束测深数据',
|
||||||
|
voyage_name: '探索一号2024航次',
|
||||||
|
data_status: '已入库',
|
||||||
|
data_lon: 113.52,
|
||||||
|
data_lat: 18.25,
|
||||||
|
fill_time: '2024-03-15 10:30:00',
|
||||||
|
fill_person: '张工',
|
||||||
|
data_secrecy: '否',
|
||||||
|
file_size: '2.8 GB',
|
||||||
|
survey_area: '120 km²'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
data_code: 'MB-SU-2024-002',
|
||||||
|
data_type: 'submarine_multibeam',
|
||||||
|
data_name: '西太平洋潜载多波束地形数据',
|
||||||
|
voyage_name: '奋斗者号2024航次',
|
||||||
|
data_status: '处理中',
|
||||||
|
data_lon: 125.18,
|
||||||
|
data_lat: 12.36,
|
||||||
|
fill_time: '2024-04-02 14:20:00',
|
||||||
|
fill_person: '李工',
|
||||||
|
data_secrecy: '是',
|
||||||
|
file_size: '5.1 GB',
|
||||||
|
survey_area: '85 km²'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
data_code: 'SS-2024-003',
|
||||||
|
data_type: 'sidescan',
|
||||||
|
data_name: '马里亚纳海沟侧扫声呐影像',
|
||||||
|
voyage_name: '探索二号2024航次',
|
||||||
|
data_status: '已入库',
|
||||||
|
data_lon: 142.2,
|
||||||
|
data_lat: 11.35,
|
||||||
|
fill_time: '2024-05-10 09:15:00',
|
||||||
|
fill_person: '王工',
|
||||||
|
data_secrecy: '否',
|
||||||
|
file_size: '1.2 GB',
|
||||||
|
survey_area: '45 km²'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
data_code: 'SE-SH-2024-004',
|
||||||
|
data_type: 'shallow_seismic',
|
||||||
|
data_name: '珠江口浅地层地震剖面',
|
||||||
|
voyage_name: '海洋六号2024航次',
|
||||||
|
data_status: '已入库',
|
||||||
|
data_lon: 114.05,
|
||||||
|
data_lat: 22.08,
|
||||||
|
fill_time: '2024-02-28 16:45:00',
|
||||||
|
fill_person: '赵工',
|
||||||
|
data_secrecy: '否',
|
||||||
|
file_size: '3.6 GB',
|
||||||
|
survey_area: '200 km²'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
data_code: 'SE-2D-2024-005',
|
||||||
|
data_type: 'seismic_2d',
|
||||||
|
data_name: '南海北部2D地震测线数据',
|
||||||
|
voyage_name: '海洋地质十二号2024航次',
|
||||||
|
data_status: '待审核',
|
||||||
|
data_lon: 115.32,
|
||||||
|
data_lat: 19.45,
|
||||||
|
fill_time: '2024-06-01 11:00:00',
|
||||||
|
fill_person: '陈工',
|
||||||
|
data_secrecy: '是',
|
||||||
|
file_size: '8.4 GB',
|
||||||
|
survey_area: '350 km²'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
data_code: 'SE-3D-2024-006',
|
||||||
|
data_type: 'seismic_3d',
|
||||||
|
data_name: '琼东南盆地3D地震数据体',
|
||||||
|
voyage_name: '海洋石油7082024航次',
|
||||||
|
data_status: '已入库',
|
||||||
|
data_lon: 111.8,
|
||||||
|
data_lat: 17.92,
|
||||||
|
fill_time: '2024-01-20 08:30:00',
|
||||||
|
fill_person: '刘工',
|
||||||
|
data_secrecy: '是',
|
||||||
|
file_size: '42.0 GB',
|
||||||
|
survey_area: '500 km²'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
data_code: 'SE-OBN-2024-007',
|
||||||
|
data_type: 'obn_seismic',
|
||||||
|
data_name: 'OBN节点地震采集数据',
|
||||||
|
voyage_name: '探索三号2024航次',
|
||||||
|
data_status: '处理中',
|
||||||
|
data_lon: 118.65,
|
||||||
|
data_lat: 20.12,
|
||||||
|
fill_time: '2024-07-08 13:25:00',
|
||||||
|
fill_person: '周工',
|
||||||
|
data_secrecy: '是',
|
||||||
|
file_size: '15.7 GB',
|
||||||
|
survey_area: '180 km²'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
data_code: 'GR-2024-008',
|
||||||
|
data_type: 'gravity',
|
||||||
|
data_name: '南海重力异常测量数据',
|
||||||
|
voyage_name: '向阳红012024航次',
|
||||||
|
data_status: '已入库',
|
||||||
|
data_lon: 112.45,
|
||||||
|
data_lat: 16.78,
|
||||||
|
fill_time: '2024-03-22 15:10:00',
|
||||||
|
fill_person: '吴工',
|
||||||
|
data_secrecy: '否',
|
||||||
|
file_size: '680 MB',
|
||||||
|
survey_area: '600 km²'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
data_code: 'MG-SH-2024-009',
|
||||||
|
data_type: 'ship_magnetic',
|
||||||
|
data_name: '船载磁力梯度测量数据',
|
||||||
|
voyage_name: '海洋四号2024航次',
|
||||||
|
data_status: '已入库',
|
||||||
|
data_lon: 119.28,
|
||||||
|
data_lat: 21.55,
|
||||||
|
fill_time: '2024-04-18 10:50:00',
|
||||||
|
fill_person: '郑工',
|
||||||
|
data_secrecy: '否',
|
||||||
|
file_size: '420 MB',
|
||||||
|
survey_area: '280 km²'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
data_code: 'MG-SU-2024-010',
|
||||||
|
data_type: 'submarine_magnetic',
|
||||||
|
data_name: '潜载磁力异常探测数据',
|
||||||
|
voyage_name: '奋斗者号2024航次',
|
||||||
|
data_status: '待审核',
|
||||||
|
data_lon: 126.05,
|
||||||
|
data_lat: 13.88,
|
||||||
|
fill_time: '2024-05-25 17:30:00',
|
||||||
|
fill_person: '孙工',
|
||||||
|
data_secrecy: '是',
|
||||||
|
file_size: '950 MB',
|
||||||
|
survey_area: '65 km²'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
data_code: 'CTD-2024-011',
|
||||||
|
data_type: 'ctd',
|
||||||
|
data_name: '南海深层CTD温盐深剖面',
|
||||||
|
voyage_name: '探索一号2024航次',
|
||||||
|
data_status: '已入库',
|
||||||
|
data_lon: 114.88,
|
||||||
|
data_lat: 18.62,
|
||||||
|
fill_time: '2024-02-14 09:40:00',
|
||||||
|
fill_person: '钱工',
|
||||||
|
data_secrecy: '否',
|
||||||
|
file_size: '128 MB',
|
||||||
|
survey_area: '—'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
data_code: 'ADCP-2024-012',
|
||||||
|
data_type: 'adcp',
|
||||||
|
data_name: '西太平洋ADCP海流观测数据',
|
||||||
|
voyage_name: '向阳红18号2024航次',
|
||||||
|
data_status: '已入库',
|
||||||
|
data_lon: 130.42,
|
||||||
|
data_lat: 15.23,
|
||||||
|
fill_time: '2024-06-15 12:00:00',
|
||||||
|
fill_person: '冯工',
|
||||||
|
data_secrecy: '否',
|
||||||
|
file_size: '256 MB',
|
||||||
|
survey_area: '—'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 13,
|
||||||
|
data_code: 'HF-2024-013',
|
||||||
|
data_type: 'heat_flow',
|
||||||
|
data_name: '南海北部海底热流测量数据',
|
||||||
|
voyage_name: '探索二号2024航次',
|
||||||
|
data_status: '处理中',
|
||||||
|
data_lon: 116.15,
|
||||||
|
data_lat: 19.08,
|
||||||
|
fill_time: '2024-07-20 14:55:00',
|
||||||
|
fill_person: '韩工',
|
||||||
|
data_secrecy: '否',
|
||||||
|
file_size: '86 MB',
|
||||||
|
survey_area: '—'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主页统计数据
|
||||||
|
*/
|
||||||
|
export const MOCK_STATISTICS = {
|
||||||
|
data_total: 13,
|
||||||
|
area_total: '2850',
|
||||||
|
voyage_total: 8,
|
||||||
|
type_total: 8,
|
||||||
|
volume_total: '80.5'
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
import { CATEGORY_IMAGES } from '@/constants/homeImages.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 首页数据资源分类
|
||||||
|
*/
|
||||||
|
export const HOME_RESOURCE_CATEGORIES = [
|
||||||
|
{
|
||||||
|
id: 'acoustic',
|
||||||
|
label: '声学测量数据',
|
||||||
|
desc: '多波束、侧扫声呐',
|
||||||
|
route: '/demand',
|
||||||
|
query: { category: 'acoustic' },
|
||||||
|
count: 3,
|
||||||
|
cover: CATEGORY_IMAGES.acoustic
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'seismic',
|
||||||
|
label: '地震勘探数据',
|
||||||
|
desc: '浅剖、2D/3D、OBN',
|
||||||
|
route: '/demand',
|
||||||
|
query: { category: 'seismic' },
|
||||||
|
count: 4,
|
||||||
|
cover: CATEGORY_IMAGES.seismic
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'potential',
|
||||||
|
label: '重磁位场数据',
|
||||||
|
desc: '重力、磁力测量',
|
||||||
|
route: '/demand',
|
||||||
|
query: { category: 'potential' },
|
||||||
|
count: 3,
|
||||||
|
cover: CATEGORY_IMAGES.potential
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ocean',
|
||||||
|
label: '海洋环境数据',
|
||||||
|
desc: 'CTD、ADCP、热流',
|
||||||
|
route: '/demand',
|
||||||
|
query: { category: 'ocean' },
|
||||||
|
count: 3,
|
||||||
|
cover: CATEGORY_IMAGES.ocean
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 首页 Hero 统计卡片
|
||||||
|
*/
|
||||||
|
export const HOME_STAT_CARDS = [
|
||||||
|
{ key: 'multibeam', label: '多波束', value: '2', unit: '类' },
|
||||||
|
{ key: 'seismic', label: '地震数据', value: '4', unit: '类' },
|
||||||
|
{ key: 'magnetic', label: '磁力数据', value: '2', unit: '类' },
|
||||||
|
{ key: 'sidescan', label: '侧扫数据', value: '1', unit: '类' },
|
||||||
|
{ key: 'dataset', label: '数据集', value: '13', unit: '个' },
|
||||||
|
{ key: 'gravity', label: '重力数据', value: '1', unit: '类' },
|
||||||
|
{ key: 'ocean', label: '海洋观测', value: '3', unit: '类' },
|
||||||
|
{ key: 'records', label: '数据记录', value: '82,454', unit: '条' }
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 热门数据排序字段
|
||||||
|
*/
|
||||||
|
export const HOT_DATA_TABS = [
|
||||||
|
{ key: 'views', label: '浏览量' },
|
||||||
|
{ key: 'downloads', label: '下载量' },
|
||||||
|
{ key: 'focus', label: '关注度' }
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为 mock 数据附加热门指标
|
||||||
|
* @param {Array} dataList
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
export function enrichHotDataMetrics(dataList) {
|
||||||
|
return dataList.map((item, index) => ({
|
||||||
|
...item,
|
||||||
|
views: 1280 - index * 86,
|
||||||
|
downloads: 356 - index * 28,
|
||||||
|
focus: 96 - index * 6,
|
||||||
|
hotRank: index + 1
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||||
|
import { useAuthStore } from '@/store/authStore'
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHashHistory(),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'Home',
|
||||||
|
component: () => import('@/views/Home.vue'),
|
||||||
|
meta: { title: '首页', requiresAuth: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/search',
|
||||||
|
name: 'DataImport',
|
||||||
|
component: () => import('@/views/DataImport.vue'),
|
||||||
|
meta: { title: '数据入库', requiresAuth: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/demand',
|
||||||
|
name: 'DataSearch',
|
||||||
|
component: () => import('@/views/DataSearch.vue'),
|
||||||
|
meta: { title: '数据检索', requiresAuth: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/input/:id?',
|
||||||
|
name: 'DataInput',
|
||||||
|
component: () => import('@/views/TaskData.vue'),
|
||||||
|
meta: { title: '数据信息录入', requiresAuth: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/statistics',
|
||||||
|
name: 'Statistics',
|
||||||
|
component: () => import('@/views/Statistics.vue'),
|
||||||
|
meta: { title: '统计分析', requiresAuth: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/contact',
|
||||||
|
name: 'Contact',
|
||||||
|
component: () => import('@/views/Contact.vue'),
|
||||||
|
meta: { title: '联系我们', requiresAuth: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'Login',
|
||||||
|
component: () => import('@/views/Login.vue'),
|
||||||
|
meta: { title: '登录', requiresAuth: false }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
router.beforeEach((to, _from, next) => {
|
||||||
|
if (to.meta.title) {
|
||||||
|
document.title = '深海地球物理数据管理可视化平台 - ' + to.meta.title
|
||||||
|
}
|
||||||
|
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
|
if (to.meta.requiresAuth && !authStore.isLoggedIn) {
|
||||||
|
next({
|
||||||
|
path: '/login',
|
||||||
|
query: { redirect: to.fullPath }
|
||||||
|
})
|
||||||
|
} else if (to.path === '/login' && authStore.isLoggedIn) {
|
||||||
|
next('/')
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
// src/store/authStore.js
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
|
const token = ref(localStorage.getItem('token') || null)
|
||||||
|
|
||||||
|
// 计算属性:是否已登录
|
||||||
|
const isLoggedIn = computed(() => !!token.value)
|
||||||
|
|
||||||
|
// 登录方法
|
||||||
|
function login(newToken) {
|
||||||
|
token.value = newToken
|
||||||
|
localStorage.setItem('token', newToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 退出登录方法
|
||||||
|
function logout() {
|
||||||
|
token.value = null
|
||||||
|
localStorage.removeItem('token')
|
||||||
|
}
|
||||||
|
|
||||||
|
return { token, isLoggedIn, login, logout }
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
:root {
|
||||||
|
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
color-scheme: light dark;
|
||||||
|
color: rgba(255, 255, 255, 0.87);
|
||||||
|
background-color: #242424;
|
||||||
|
|
||||||
|
font-synthesis: none;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #646cff;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #535bf2;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
min-width: 320px;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#app {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
import * as Cesium from "cesium";
|
||||||
|
|
||||||
|
export function modifyCesiumMouseWheel(viewer) {
|
||||||
|
// 地图缩放改为按下 Ctrl + 鼠标滚轮
|
||||||
|
viewer.scene.screenSpaceCameraController.zoomEventTypes = [{ eventType: Cesium.CameraEventType.WHEEL, modifier: Cesium.KeyboardEventModifier.CTRL }];
|
||||||
|
|
||||||
|
// 改写 Cesium 原生鼠标滚轮事件
|
||||||
|
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
|
||||||
|
handler.setInputAction((event) => {
|
||||||
|
if (event < 0) {
|
||||||
|
window.scrollBy({ left: 0, top: 15, behavior: 'auto' });
|
||||||
|
} else {
|
||||||
|
window.scrollBy({ left: 0, top: -15, behavior: 'auto' });
|
||||||
|
}
|
||||||
|
}, Cesium.ScreenSpaceEventType.WHEEL);
|
||||||
|
|
||||||
|
// 鼠标移动时显示提示信息
|
||||||
|
handler.setInputAction((event) => {
|
||||||
|
document.getElementById('mouseWheelTip').style.display = '';
|
||||||
|
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
let imageryViewModels = [];
|
||||||
|
export function setImageryViewModels(cesiumHostAddr) {
|
||||||
|
if (imageryViewModels.length == 0) {
|
||||||
|
// 世界影像地图
|
||||||
|
const world_imagery = new Cesium.WebMapTileServiceImageryProvider(
|
||||||
|
{
|
||||||
|
url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer'
|
||||||
|
// url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer'
|
||||||
|
// url: 'https://services.arcgisonline.com/ArcGIS/rest/services/Ocean/World_Ocean_Base/MapServer'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// 彩色地形底图
|
||||||
|
const colorMap = new Cesium.WebMapTileServiceImageryProvider(
|
||||||
|
{
|
||||||
|
url: cesiumHostAddr + 'geoserver/gwc/service/wmts/rest/ougp:world/{TileMatrixSet}/{TileMatrixSet}:{TileMatrix}/{TileRow}/{TileCol}?format=image/jpeg',
|
||||||
|
layer: 'ougp:world',
|
||||||
|
style: 'default',
|
||||||
|
format: 'image/jpeg',
|
||||||
|
tileMatrixSetID: 'EPSG:4326',
|
||||||
|
tilingScheme: new Cesium.GeographicTilingScheme()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// 灰度地形底图
|
||||||
|
const grayMap = new Cesium.WebMapTileServiceImageryProvider(
|
||||||
|
{
|
||||||
|
url: cesiumHostAddr + 'geoserver/gwc/service/wmts/rest/ougp:world_gray/{TileMatrixSet}/{TileMatrixSet}:{TileMatrix}/{TileRow}/{TileCol}?format=image/jpeg',
|
||||||
|
layer: 'ougp:world_gray',
|
||||||
|
style: 'default',
|
||||||
|
format: 'image/jpeg',
|
||||||
|
tileMatrixSetID: 'EPSG:4326',
|
||||||
|
tilingScheme: new Cesium.GeographicTilingScheme()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// 自定义底图
|
||||||
|
imageryViewModels.push(new Cesium.ProviderViewModel({
|
||||||
|
name: "世界影像地图",
|
||||||
|
iconUrl: "static/images/world_imagery.png",
|
||||||
|
tooltip: "世界影像地图",
|
||||||
|
creationFunction: function () {
|
||||||
|
return world_imagery;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
imageryViewModels.push(new Cesium.ProviderViewModel({
|
||||||
|
name: "地形地图",
|
||||||
|
iconUrl: "static/images/world_color.png",
|
||||||
|
tooltip: "彩色地形底图",
|
||||||
|
creationFunction: function () {
|
||||||
|
return colorMap;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
imageryViewModels.push(new Cesium.ProviderViewModel({
|
||||||
|
name: "灰度地图",
|
||||||
|
iconUrl: "static/images/world_gray.png",
|
||||||
|
tooltip: "灰度地形底图",
|
||||||
|
creationFunction: function () {
|
||||||
|
return grayMap;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return imageryViewModels;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,228 @@
|
||||||
|
/**
|
||||||
|
* 通用js方法封装处理
|
||||||
|
* Copyright (c) 2019 ruoyi
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 日期格式化
|
||||||
|
export function parseTime(time, pattern) {
|
||||||
|
if (arguments.length === 0 || !time) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
|
||||||
|
let date
|
||||||
|
if (typeof time === 'object') {
|
||||||
|
date = time
|
||||||
|
} else {
|
||||||
|
if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
|
||||||
|
time = parseInt(time)
|
||||||
|
} else if (typeof time === 'string') {
|
||||||
|
time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), '')
|
||||||
|
}
|
||||||
|
if ((typeof time === 'number') && (time.toString().length === 10)) {
|
||||||
|
time = time * 1000
|
||||||
|
}
|
||||||
|
date = new Date(time)
|
||||||
|
}
|
||||||
|
const formatObj = {
|
||||||
|
y: date.getFullYear(),
|
||||||
|
m: date.getMonth() + 1,
|
||||||
|
d: date.getDate(),
|
||||||
|
h: date.getHours(),
|
||||||
|
i: date.getMinutes(),
|
||||||
|
s: date.getSeconds(),
|
||||||
|
a: date.getDay()
|
||||||
|
}
|
||||||
|
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
|
||||||
|
let value = formatObj[key]
|
||||||
|
// Note: getDay() returns 0 on Sunday
|
||||||
|
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
|
||||||
|
if (result.length > 0 && value < 10) {
|
||||||
|
value = '0' + value
|
||||||
|
}
|
||||||
|
return value || 0
|
||||||
|
})
|
||||||
|
return time_str
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单重置
|
||||||
|
export function resetForm(refName) {
|
||||||
|
if (this.$refs[refName]) {
|
||||||
|
this.$refs[refName].resetFields()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加日期范围
|
||||||
|
export function addDateRange(params, dateRange, propName) {
|
||||||
|
let search = params
|
||||||
|
search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {}
|
||||||
|
dateRange = Array.isArray(dateRange) ? dateRange : []
|
||||||
|
if (typeof (propName) === 'undefined') {
|
||||||
|
search.params['beginTime'] = dateRange[0]
|
||||||
|
search.params['endTime'] = dateRange[1]
|
||||||
|
} else {
|
||||||
|
search.params['begin' + propName] = dateRange[0]
|
||||||
|
search.params['end' + propName] = dateRange[1]
|
||||||
|
}
|
||||||
|
return search
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回显数据字典
|
||||||
|
export function selectDictLabel(datas, value) {
|
||||||
|
if (value === undefined) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var actions = []
|
||||||
|
Object.keys(datas).some((key) => {
|
||||||
|
if (datas[key].value == ('' + value)) {
|
||||||
|
actions.push(datas[key].label)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (actions.length === 0) {
|
||||||
|
actions.push(value)
|
||||||
|
}
|
||||||
|
return actions.join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回显数据字典(字符串、数组)
|
||||||
|
export function selectDictLabels(datas, value, separator) {
|
||||||
|
if (value === undefined || value.length ===0) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value = value.join(",")
|
||||||
|
}
|
||||||
|
var actions = []
|
||||||
|
var currentSeparator = undefined === separator ? "," : separator
|
||||||
|
var temp = value.split(currentSeparator)
|
||||||
|
Object.keys(value.split(currentSeparator)).some((val) => {
|
||||||
|
var match = false
|
||||||
|
Object.keys(datas).some((key) => {
|
||||||
|
if (datas[key].value == ('' + temp[val])) {
|
||||||
|
actions.push(datas[key].label + currentSeparator)
|
||||||
|
match = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!match) {
|
||||||
|
actions.push(temp[val] + currentSeparator)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return actions.join('').substring(0, actions.join('').length - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字符串格式化(%s )
|
||||||
|
export function sprintf(str) {
|
||||||
|
var args = arguments, flag = true, i = 1
|
||||||
|
str = str.replace(/%s/g, function () {
|
||||||
|
var arg = args[i++]
|
||||||
|
if (typeof arg === 'undefined') {
|
||||||
|
flag = false
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
return arg
|
||||||
|
})
|
||||||
|
return flag ? str : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换字符串,undefined,null等转化为""
|
||||||
|
export function parseStrEmpty(str) {
|
||||||
|
if (!str || str == "undefined" || str == "null") {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据合并
|
||||||
|
export function mergeRecursive(source, target) {
|
||||||
|
for (var p in target) {
|
||||||
|
try {
|
||||||
|
if (target[p].constructor == Object) {
|
||||||
|
source[p] = mergeRecursive(source[p], target[p])
|
||||||
|
} else {
|
||||||
|
source[p] = target[p]
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
source[p] = target[p]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return source
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造树型结构数据
|
||||||
|
* @param {*} data 数据源
|
||||||
|
* @param {*} id id字段 默认 'id'
|
||||||
|
* @param {*} parentId 父节点字段 默认 'parentId'
|
||||||
|
* @param {*} children 孩子节点字段 默认 'children'
|
||||||
|
*/
|
||||||
|
export function handleTree(data, id, parentId, children) {
|
||||||
|
let config = {
|
||||||
|
id: id || 'id',
|
||||||
|
parentId: parentId || 'parentId',
|
||||||
|
childrenList: children || 'children'
|
||||||
|
}
|
||||||
|
|
||||||
|
var childrenListMap = {}
|
||||||
|
var tree = []
|
||||||
|
for (let d of data) {
|
||||||
|
let id = d[config.id]
|
||||||
|
childrenListMap[id] = d
|
||||||
|
if (!d[config.childrenList]) {
|
||||||
|
d[config.childrenList] = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let d of data) {
|
||||||
|
let parentId = d[config.parentId]
|
||||||
|
let parentObj = childrenListMap[parentId]
|
||||||
|
if (!parentObj) {
|
||||||
|
tree.push(d)
|
||||||
|
} else {
|
||||||
|
parentObj[config.childrenList].push(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tree
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数处理
|
||||||
|
* @param {*} params 参数
|
||||||
|
*/
|
||||||
|
export function tansParams(params) {
|
||||||
|
let result = ''
|
||||||
|
for (const propName of Object.keys(params)) {
|
||||||
|
const value = params[propName]
|
||||||
|
var part = encodeURIComponent(propName) + "="
|
||||||
|
if (value !== null && value !== "" && typeof (value) !== "undefined") {
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
for (const key of Object.keys(value)) {
|
||||||
|
if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') {
|
||||||
|
let params = propName + '[' + key + ']'
|
||||||
|
var subPart = encodeURIComponent(params) + "="
|
||||||
|
result += subPart + encodeURIComponent(value[key]) + "&"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result += part + encodeURIComponent(value) + "&"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回项目路径
|
||||||
|
export function getNormalPath(p) {
|
||||||
|
if (p.length === 0 || !p || p == 'undefined') {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
let res = p.replace('//', '/')
|
||||||
|
if (res[res.length - 1] === '/') {
|
||||||
|
return res.slice(0, res.length - 1)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证是否为blob格式
|
||||||
|
export function blobValidate(data) {
|
||||||
|
return data.type !== 'application/json'
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
import axios from 'axios'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
|
|
||||||
|
axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||||
|
// 创建axios实例
|
||||||
|
const service = axios.create({
|
||||||
|
// axios中请求配置有baseURL选项,表示请求URL公共部分
|
||||||
|
baseURL: import.meta.env.VITE_APP_BASE_API,
|
||||||
|
// 超时
|
||||||
|
timeout: 10000
|
||||||
|
})
|
||||||
|
|
||||||
|
// request拦截器
|
||||||
|
service.interceptors.request.use(config => {
|
||||||
|
// 过滤掉参数值为空的参数项
|
||||||
|
if (config.params) {
|
||||||
|
config.params = filterEmptyParams(config.params);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.data && typeof config.data === 'object') {
|
||||||
|
// 根据Content-Type处理不同格式的数据
|
||||||
|
if (config.headers['Content-Type'] === 'application/x-www-form-urlencoded') {
|
||||||
|
// 表单编码数据需要特殊处理
|
||||||
|
config.data = filterFormData(config.data);
|
||||||
|
} else {
|
||||||
|
config.data = filterEmptyParams(config.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}, error => {
|
||||||
|
console.log(error)
|
||||||
|
Promise.reject(error)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过滤空值参数
|
||||||
|
* @param {Object} params 参数对象
|
||||||
|
* @returns {Object} 过滤后的参数对象
|
||||||
|
*/
|
||||||
|
function filterEmptyParams(params) {
|
||||||
|
const filtered = {};
|
||||||
|
for (const key in params) {
|
||||||
|
const value = params[key];
|
||||||
|
// 保留非空值:非null、非undefined、非空字符串、非空数组
|
||||||
|
if (value !== null && value !== undefined && value !== '') {
|
||||||
|
if (Array.isArray(value) && value.length === 0) {
|
||||||
|
continue; // 跳过空数组
|
||||||
|
}
|
||||||
|
filtered[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过滤表单编码数据
|
||||||
|
* @param {Object|string} data 表单数据
|
||||||
|
* @returns {string} 过滤后的表单字符串
|
||||||
|
*/
|
||||||
|
function filterFormData(data) {
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
// 如果是字符串,尝试解析为URLSearchParams
|
||||||
|
const params = new URLSearchParams(data);
|
||||||
|
for (const [key, value] of params.entries()) {
|
||||||
|
if (value === '' || value === 'null' || value === 'undefined') {
|
||||||
|
params.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return params.toString();
|
||||||
|
} else if (typeof data === 'object') {
|
||||||
|
// 如果是对象,先过滤空值再转换为URLSearchParams
|
||||||
|
const filtered = filterEmptyParams(data);
|
||||||
|
return new URLSearchParams(filtered).toString();
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应拦截器
|
||||||
|
service.interceptors.response.use(res => {
|
||||||
|
const code = res.data.errorCode || 0
|
||||||
|
const msg = res.data.errorMsg || ''
|
||||||
|
if (code === 20000) {
|
||||||
|
ElMessage({ message: msg, type: 'error' })
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(res.data)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.log('err' + error)
|
||||||
|
let { message } = error
|
||||||
|
ElMessage({ message: message, type: 'error', duration: 5 * 1000 })
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default service
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
<template>
|
||||||
|
<div class="aboutUs">
|
||||||
|
<div class="banner">
|
||||||
|
<p class="bold">关于我们</p>
|
||||||
|
<p>团队成员主要由海洋地球物理学家、软件工程师和数据管理员构成,隶属于中国科学院深海科学与工程研究所,开展船潜载地球物理数据处理和深海数据系统信息化工具开发的应用基础研究,服务支撑fundingOptions、深海深渊科考、深海考古等国家重大需求。</p>
|
||||||
|
<p class="bold">团队组成</p>
|
||||||
|
<p><span class="fw-700">陈传绪</span>:团队负责人,船潜载地球物理数据处理、算法开发,DSDS架构总设计。</p>
|
||||||
|
<p class="fw-700">* DSDS(Deep-Sea Data System)</p>
|
||||||
|
<p><span class="fw-700">邹小珊</span>:软件工程师,负责 C/S 框架客户端软件和信息化工具开发</p>
|
||||||
|
<p><span class="fw-700">黄云明</span>:软件工程师,负责 B/S 框架航次潜次数据门户及应用开发</p>
|
||||||
|
<p><span class="fw-700">冯万丽</span>:数据管理员,负责航次和潜次数据预处理、清洁、规范化归档入库</p>
|
||||||
|
<p class="fw-700">* OUGP(Onboard-Underwater Geophysics Processing)</p>
|
||||||
|
<p><span class="fw-700">张汉羽</span>:地球物理,船潜载地球物理数据处理、算法开发,分布式光纤传感方向负责人</p>
|
||||||
|
<p><span class="fw-700">谢欣彤</span>:地球物理,船潜载地球物理数据处理和三维可视化</p>
|
||||||
|
<p><span class="fw-700">李栋</span>:地球物理,海底地震仪数据处理成像</p>
|
||||||
|
<p><span class="fw-700">廖佩铃</span>:地球物理,船潜载声学探测数据处理和成像</p>
|
||||||
|
<p><span class="fw-700">常瀚文</span>:信息安全,软件开发和数据安全</p>
|
||||||
|
<p class="bold">联系我们</p>
|
||||||
|
<p><span class="fw-700">陈传绪</span>:chencx@idsse.ac.cn</p>
|
||||||
|
<p><span class="fw-700">张汉羽</span>:zhanghy@idsse.ac.cn</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
.aboutUs {
|
||||||
|
width: 100%;
|
||||||
|
background: url(@/assets/bg2.png) center no-repeat;
|
||||||
|
background-size: auto 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.banner {
|
||||||
|
width: 1200px;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 100px 0;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bold {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 40px 0 10px;
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
line-height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fw-700 {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,495 @@
|
||||||
|
<template>
|
||||||
|
<div class="data-space-page">
|
||||||
|
<!-- 顶部工具栏 -->
|
||||||
|
<div class="top-toolbar">
|
||||||
|
<div class="toolbar-left">
|
||||||
|
<el-button link type="primary" @click="goBackList">
|
||||||
|
<el-icon><ArrowLeft /></el-icon>
|
||||||
|
返回列表
|
||||||
|
</el-button>
|
||||||
|
<el-select v-model="currentSpace" class="space-select" size="large">
|
||||||
|
<el-option
|
||||||
|
v-for="space in spaceList"
|
||||||
|
:key="space.id"
|
||||||
|
:label="space.label"
|
||||||
|
:value="space.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="toolbar-right">
|
||||||
|
<el-button-group class="view-toggle">
|
||||||
|
<el-button :type="viewMode === 'grid' ? 'primary' : 'default'" @click="viewMode = 'grid'">
|
||||||
|
<el-icon><Grid /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
<el-button :type="viewMode === 'list' ? 'primary' : 'default'" @click="viewMode = 'list'">
|
||||||
|
<el-icon><List /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</el-button-group>
|
||||||
|
<el-button type="primary" @click="uploadDialogVisible = true">
|
||||||
|
<el-icon><Upload /></el-icon>
|
||||||
|
上传文件
|
||||||
|
</el-button>
|
||||||
|
<el-button type="warning" plain @click="importDialogVisible = true">
|
||||||
|
<el-icon><Download /></el-icon>
|
||||||
|
空间导入
|
||||||
|
</el-button>
|
||||||
|
<el-button type="success" plain @click="ftpDialogVisible = true">
|
||||||
|
<el-icon><Monitor /></el-icon>
|
||||||
|
FTP地址
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="spaceDialogVisible = true">空间管理</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 主体:目录树 + 文件预览 -->
|
||||||
|
<div class="main-panel">
|
||||||
|
<aside class="tree-panel">
|
||||||
|
<div class="panel-title">
|
||||||
|
<el-icon><FolderOpened /></el-icon>
|
||||||
|
文件目录
|
||||||
|
</div>
|
||||||
|
<el-tree
|
||||||
|
ref="treeRef"
|
||||||
|
:data="treeData"
|
||||||
|
node-key="id"
|
||||||
|
:props="treeProps"
|
||||||
|
:default-expanded-keys="defaultExpandedKeys"
|
||||||
|
:current-node-key="currentFolderId"
|
||||||
|
highlight-current
|
||||||
|
@node-click="handleNodeClick"
|
||||||
|
>
|
||||||
|
<template #default="{ node, data }">
|
||||||
|
<span class="tree-node">
|
||||||
|
<FileTypeIcon type="folder" :size="18" class="tree-folder-icon" />
|
||||||
|
<span :class="{ 'is-active': data.id === currentFolderId }">{{ node.label }}</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-tree>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<section class="preview-panel">
|
||||||
|
<div class="preview-header">
|
||||||
|
<span class="path">{{ currentPathLabel }}</span>
|
||||||
|
<span class="file-count">共 {{ fileList.length }} 项</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 网格视图 -->
|
||||||
|
<div v-if="viewMode === 'grid'" class="file-grid">
|
||||||
|
<div
|
||||||
|
v-for="file in fileList"
|
||||||
|
:key="file.id"
|
||||||
|
class="file-item"
|
||||||
|
:class="{ selected: selectedFileId === file.id }"
|
||||||
|
@click="selectFile(file)"
|
||||||
|
@dblclick="previewFile(file)"
|
||||||
|
>
|
||||||
|
<FileTypeIcon :ext="file.ext" :size="72" />
|
||||||
|
<div class="file-name" :title="file.name">{{ file.name }}</div>
|
||||||
|
</div>
|
||||||
|
<el-empty v-if="!fileList.length" description="当前目录暂无文件" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 列表视图 -->
|
||||||
|
<el-table
|
||||||
|
v-else
|
||||||
|
:data="fileList"
|
||||||
|
border
|
||||||
|
stripe
|
||||||
|
highlight-current-row
|
||||||
|
@row-click="(row) => selectFile(row)"
|
||||||
|
@row-dblclick="previewFile"
|
||||||
|
>
|
||||||
|
<el-table-column label="名称" min-width="260">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="list-name">
|
||||||
|
<FileTypeIcon :ext="row.ext" :size="24" />
|
||||||
|
<span>{{ row.name }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="ext" label="类型" width="80" />
|
||||||
|
<el-table-column label="大小" width="120">
|
||||||
|
<template #default="{ row }">{{ formatFileSize(row.size) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="update_time" label="修改时间" width="180" />
|
||||||
|
</el-table>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 上传文件弹窗 -->
|
||||||
|
<el-dialog v-model="uploadDialogVisible" title="上传文件" width="520" destroy-on-close>
|
||||||
|
<el-upload drag :auto-upload="false" :limit="5">
|
||||||
|
<el-icon class="upload-icon"><Upload /></el-icon>
|
||||||
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||||
|
<template #tip>
|
||||||
|
<div class="el-upload__tip">上传至当前目录:{{ currentPathLabel }}</div>
|
||||||
|
</template>
|
||||||
|
</el-upload>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="uploadDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="confirmUpload">确认上传</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 空间导入弹窗 -->
|
||||||
|
<el-dialog v-model="importDialogVisible" title="空间导入" width="560" destroy-on-close>
|
||||||
|
<el-form label-width="100px">
|
||||||
|
<el-form-item label="源空间路径">
|
||||||
|
<el-input v-model="importForm.sourcePath" placeholder="s3://bucket/path 或 NAS 路径" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="目标目录">
|
||||||
|
<el-input :model-value="currentPathLabel" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="导入说明">
|
||||||
|
<el-input v-model="importForm.remark" type="textarea" :rows="2" placeholder="可选" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="importDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="confirmImport">开始导入</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- FTP 地址弹窗 -->
|
||||||
|
<el-dialog v-model="ftpDialogVisible" title="FTP 地址" width="520" destroy-on-close>
|
||||||
|
<el-descriptions :column="1" border>
|
||||||
|
<el-descriptions-item label="FTP 主机">ftp.idsse.ac.cn</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="端口">21</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="当前路径">/geophys/{{ currentFolderId }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="完整地址">ftp://ftp.idsse.ac.cn/geophys/{{ currentFolderId }}</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="ftpDialogVisible = false">关闭</el-button>
|
||||||
|
<el-button type="primary" @click="copyFtpPath">复制地址</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 空间管理弹窗 -->
|
||||||
|
<el-dialog v-model="spaceDialogVisible" title="空间管理" width="480">
|
||||||
|
<p>当前数据空间:<strong>{{ currentSpaceLabel }}</strong></p>
|
||||||
|
<p class="tip-text">支持创建子目录、设置权限及配额管理(演示功能)。</p>
|
||||||
|
<template #footer>
|
||||||
|
<el-button type="primary" @click="spaceDialogVisible = false">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 文件预览弹窗 -->
|
||||||
|
<el-dialog v-model="previewVisible" :title="previewFileInfo?.name" width="640">
|
||||||
|
<el-descriptions v-if="previewFileInfo" :column="1" border>
|
||||||
|
<el-descriptions-item label="文件名">{{ previewFileInfo.name }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="类型">{{ previewFileInfo.ext?.toUpperCase() }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="大小">{{ formatFileSize(previewFileInfo.size) }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="修改时间">{{ previewFileInfo.update_time }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="所在目录">{{ currentPathLabel }}</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import {
|
||||||
|
ArrowLeft, Grid, List, Upload, Download,
|
||||||
|
Monitor, FolderOpened
|
||||||
|
} from '@element-plus/icons-vue'
|
||||||
|
import FileTypeIcon from '@/components/dataSpace/FileTypeIcon.vue'
|
||||||
|
import {
|
||||||
|
getDataSpaces,
|
||||||
|
getDirectoryTree,
|
||||||
|
getFolderFiles
|
||||||
|
} from '@/api/dataSpace.js'
|
||||||
|
import { DEFAULT_FOLDER_ID } from '@/mock/dataSpace.js'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const treeRef = ref(null)
|
||||||
|
|
||||||
|
const spaceList = ref([])
|
||||||
|
const treeData = ref([])
|
||||||
|
const fileList = ref([])
|
||||||
|
const currentSpace = ref('geophys-main')
|
||||||
|
const currentFolderId = ref(DEFAULT_FOLDER_ID)
|
||||||
|
const viewMode = ref('grid')
|
||||||
|
const selectedFileId = ref('')
|
||||||
|
const defaultExpandedKeys = ref(['root', 'multibeam', 'seismic', 'magnetic'])
|
||||||
|
|
||||||
|
const uploadDialogVisible = ref(false)
|
||||||
|
const importDialogVisible = ref(false)
|
||||||
|
const ftpDialogVisible = ref(false)
|
||||||
|
const spaceDialogVisible = ref(false)
|
||||||
|
const previewVisible = ref(false)
|
||||||
|
const previewFileInfo = ref(null)
|
||||||
|
|
||||||
|
const importForm = ref({ sourcePath: '', remark: '' })
|
||||||
|
|
||||||
|
const treeProps = { label: 'label', children: 'children' }
|
||||||
|
|
||||||
|
const currentSpaceLabel = computed(() =>
|
||||||
|
spaceList.value.find((s) => s.id === currentSpace.value)?.label || ''
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建当前目录路径显示
|
||||||
|
*/
|
||||||
|
const currentPathLabel = computed(() => {
|
||||||
|
const parts = []
|
||||||
|
const buildPath = (nodes, targetId, path = []) => {
|
||||||
|
for (const node of nodes) {
|
||||||
|
const next = [...path, node.label]
|
||||||
|
if (node.id === targetId) {
|
||||||
|
parts.push(...next)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (node.children?.length && buildPath(node.children, targetId, next)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
buildPath(treeData.value, currentFolderId.value)
|
||||||
|
return parts.join(' / ') || '深海地球物理数据空间'
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化文件大小
|
||||||
|
* @param {number} bytes
|
||||||
|
*/
|
||||||
|
const formatFileSize = (bytes) => {
|
||||||
|
if (!bytes) return '—'
|
||||||
|
const k = 1024
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||||
|
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadSpaces = () => {
|
||||||
|
getDataSpaces().then((res) => {
|
||||||
|
spaceList.value = res.array || []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadTree = () => {
|
||||||
|
getDirectoryTree().then((res) => {
|
||||||
|
treeData.value = res.array || []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadFiles = () => {
|
||||||
|
getFolderFiles({ folder_id: currentFolderId.value }).then((res) => {
|
||||||
|
fileList.value = res.array || []
|
||||||
|
selectedFileId.value = ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNodeClick = (data) => {
|
||||||
|
if (!data.children?.length) {
|
||||||
|
currentFolderId.value = data.id
|
||||||
|
loadFiles()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectFile = (file) => {
|
||||||
|
selectedFileId.value = file.id
|
||||||
|
}
|
||||||
|
|
||||||
|
const previewFile = (file) => {
|
||||||
|
previewFileInfo.value = file
|
||||||
|
previewVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const goBackList = () => {
|
||||||
|
router.push('/demand')
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmUpload = () => {
|
||||||
|
ElMessage.success(`文件已上传至:${currentPathLabel.value}`)
|
||||||
|
uploadDialogVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmImport = () => {
|
||||||
|
if (!importForm.value.sourcePath) {
|
||||||
|
ElMessage.warning('请输入源空间路径')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ElMessage.success('空间导入任务已提交')
|
||||||
|
importDialogVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyFtpPath = () => {
|
||||||
|
const path = `ftp://ftp.idsse.ac.cn/geophys/${currentFolderId.value}`
|
||||||
|
navigator.clipboard?.writeText(path).then(() => {
|
||||||
|
ElMessage.success('FTP 地址已复制')
|
||||||
|
}).catch(() => {
|
||||||
|
ElMessage.info(path)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadSpaces()
|
||||||
|
loadTree()
|
||||||
|
loadFiles()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.data-space-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: calc(100vh - 220px);
|
||||||
|
min-height: 600px;
|
||||||
|
background: #fff;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
background: #fafafa;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.toolbar-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.space-select {
|
||||||
|
width: 280px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-toggle {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-panel {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-panel {
|
||||||
|
width: 280px;
|
||||||
|
border-right: 1px solid #e8e8e8;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: #fafafa;
|
||||||
|
|
||||||
|
.panel-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
.is-active {
|
||||||
|
color: #e6a23c;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-tree-node__content) {
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content) {
|
||||||
|
background-color: #ecf5ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-panel {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
.preview-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 20px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #666;
|
||||||
|
|
||||||
|
.path {
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-grid {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-content: flex-start;
|
||||||
|
gap: 24px;
|
||||||
|
padding: 24px;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.file-item {
|
||||||
|
width: 120px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.selected {
|
||||||
|
background: #f0f7ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
word-break: break-all;
|
||||||
|
line-height: 1.4;
|
||||||
|
max-width: 110px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-text {
|
||||||
|
color: #999;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,404 @@
|
||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<div class="left" v-show="isShow">
|
||||||
|
<img class="fold" v-show="isShow" src="@/assets/icons/折叠.png" @click="handleFold">
|
||||||
|
<div class="search">
|
||||||
|
<p class="rule-text">编号规则:数据类型_航次_4位编号</p>
|
||||||
|
<div class="input-bar">
|
||||||
|
<el-input
|
||||||
|
v-model="dataCode"
|
||||||
|
style="width: 120px"
|
||||||
|
size="large"
|
||||||
|
placeholder="数据编号"
|
||||||
|
/>
|
||||||
|
<el-select
|
||||||
|
v-model="dataType"
|
||||||
|
placeholder="数据类型"
|
||||||
|
size="large"
|
||||||
|
style="width: 140px"
|
||||||
|
>
|
||||||
|
<el-option label="全部" value="" />
|
||||||
|
<el-option
|
||||||
|
v-for="item in dataTypeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-select
|
||||||
|
v-model="dataStatus"
|
||||||
|
placeholder="入库状态"
|
||||||
|
size="large"
|
||||||
|
style="width: 120px"
|
||||||
|
>
|
||||||
|
<el-option label="全部" value="" />
|
||||||
|
<el-option label="已入库" value="已入库" />
|
||||||
|
<el-option label="处理中" value="处理中" />
|
||||||
|
<el-option label="待审核" value="待审核" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="btn-bar">
|
||||||
|
<el-button :icon="RefreshRight" @click="onReset">重置</el-button>
|
||||||
|
<el-button type="primary" :icon="Search" @click="onSearch">搜索</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="line" />
|
||||||
|
<div class="list">
|
||||||
|
<div class="item" v-for="item in dataList" :key="item.id">
|
||||||
|
<div class="name">{{ item.data_code }}</div>
|
||||||
|
<div class="time">{{ item.fill_time }}</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">航次:<span>{{ item.voyage_name }}</span></div>
|
||||||
|
<div class="col">入库状态:<span>{{ item.data_status }}</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">数据类型:<span>{{ getDataTypeLabel(item.data_type) }}</span></div>
|
||||||
|
<div class="col">数据名称:<span>{{ item.data_name }}</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">数据量:<span>{{ item.file_size }}</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pagination">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="currentPage"
|
||||||
|
v-model:page-size="pageSize"
|
||||||
|
layout="total, prev, pager, next"
|
||||||
|
:total="total"
|
||||||
|
@current-change="handleCurrentChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="map">
|
||||||
|
<div id="cesiumContainer" style="width: 100%; height: 100%;"></div>
|
||||||
|
<img class="unfold" v-show="!isShow" src="@/assets/icons/展开.png" alt="" @click="handleUnfold">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
title="数据详情"
|
||||||
|
width="500"
|
||||||
|
>
|
||||||
|
<div class="item" v-if="currentMarker">
|
||||||
|
<div class="name">{{ currentMarker.data_code }}</div>
|
||||||
|
<div class="time">{{ currentMarker.fill_time }}</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">航次:<span>{{ currentMarker.voyage_name }}</span></div>
|
||||||
|
<div class="col">入库状态:<span>{{ currentMarker.data_status }}</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">数据类型:<span>{{ getDataTypeLabel(currentMarker.data_type) }}</span></div>
|
||||||
|
<div class="col">数据名称:<span>{{ currentMarker.data_name }}</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">位置:<span>{{ currentMarker.data_lon }},{{ currentMarker.data_lat }}</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import * as Cesium from 'cesium'
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { Search, RefreshRight } from '@element-plus/icons-vue'
|
||||||
|
import { dataPage } from '@/api/data.js'
|
||||||
|
import { getParam } from '@/api/task.js'
|
||||||
|
import { modifyCesiumMouseWheel } from '@/utils/common'
|
||||||
|
import { getFlatDataTypeOptions, getDataTypeLabel } from '@/constants/dataTypes.js'
|
||||||
|
|
||||||
|
import markerIcon1 from '@/assets/icons/标注点1.png'
|
||||||
|
import markerIcon2 from '@/assets/icons/标注点2.png'
|
||||||
|
import markerIcon3 from '@/assets/icons/标注点3.png'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const isShow = ref(true)
|
||||||
|
const dataCode = ref('')
|
||||||
|
const dataType = ref('')
|
||||||
|
const dataStatus = ref('')
|
||||||
|
const searchQuery = ref('')
|
||||||
|
const searchCategory = ref('')
|
||||||
|
const currentPage = ref(1)
|
||||||
|
const pageSize = ref(10)
|
||||||
|
const start = ref(0)
|
||||||
|
const total = ref(0)
|
||||||
|
const dataList = ref([])
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const currentMarker = ref(null)
|
||||||
|
const dataTypeOptions = getFlatDataTypeOptions()
|
||||||
|
|
||||||
|
const handleFold = () => {
|
||||||
|
isShow.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUnfold = () => {
|
||||||
|
isShow.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCurrentChange = (val) => {
|
||||||
|
currentPage.value = val
|
||||||
|
start.value = pageSize.value * (val - 1)
|
||||||
|
getDataList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDataList = () => {
|
||||||
|
dataPage({
|
||||||
|
start: start.value,
|
||||||
|
limit: 10,
|
||||||
|
data_type: dataType.value,
|
||||||
|
data_status: dataStatus.value,
|
||||||
|
data_code: dataCode.value,
|
||||||
|
query: searchQuery.value,
|
||||||
|
category: searchCategory.value
|
||||||
|
}).then(res => {
|
||||||
|
dataList.value = res.page.records
|
||||||
|
total.value = res.page.total
|
||||||
|
updateMapMarkers()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateMapMarkers = () => {
|
||||||
|
if (!viewer) return
|
||||||
|
viewer.entities.removeAll()
|
||||||
|
dataList.value.forEach(item => {
|
||||||
|
let icon = markerIcon1
|
||||||
|
if (item.data_status === '处理中') icon = markerIcon2
|
||||||
|
if (item.data_status === '已入库') icon = markerIcon3
|
||||||
|
|
||||||
|
viewer.entities.add({
|
||||||
|
position: Cesium.Cartesian3.fromDegrees(item.data_lon, item.data_lat),
|
||||||
|
billboard: {
|
||||||
|
image: icon,
|
||||||
|
width: 32,
|
||||||
|
height: 32
|
||||||
|
},
|
||||||
|
properties: item
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onReset = () => {
|
||||||
|
dataType.value = ''
|
||||||
|
dataStatus.value = ''
|
||||||
|
dataCode.value = ''
|
||||||
|
start.value = 0
|
||||||
|
getDataList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSearch = () => {
|
||||||
|
start.value = 0
|
||||||
|
getDataList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const CesiumHostAddr = ref('')
|
||||||
|
|
||||||
|
const getParamsList = () => {
|
||||||
|
if (import.meta.env.VITE_APP_ENV === 'development') {
|
||||||
|
CesiumHostAddr.value = import.meta.env.VITE_APP_DS_API
|
||||||
|
initMap()
|
||||||
|
} else {
|
||||||
|
getParam().then(res => {
|
||||||
|
const data = res.array.find(item => item.param_name === 'CesiumHostAddr')
|
||||||
|
if (data?.param_value) {
|
||||||
|
CesiumHostAddr.value = data.param_value
|
||||||
|
} else {
|
||||||
|
CesiumHostAddr.value = `${window.location.protocol}//${window.location.hostname}:${window.location.port}/tiles/`
|
||||||
|
}
|
||||||
|
initMap()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let viewer
|
||||||
|
|
||||||
|
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkY2Y5NWY2Ni1mODQ1LTQ3YTUtYjc4Zi1jYzhjMGI2YzcxMWYiLCJpZCI6MTI5ODMwLCJpYXQiOjE2Nzk0Njk5NTd9.DTH54ioOH-HLqeNIetBe9hFyrPOX2Vp1AQmZzw8TIZ4'
|
||||||
|
|
||||||
|
const cesiumConfig = {
|
||||||
|
sceneMode: Cesium.SceneMode.SCENE2D,
|
||||||
|
homeButton: false,
|
||||||
|
sceneModePicker: false,
|
||||||
|
fullscreenButton: false,
|
||||||
|
infoBox: false,
|
||||||
|
selectionIndicator: false,
|
||||||
|
baseLayerPicker: false,
|
||||||
|
shadows: true,
|
||||||
|
shouldAnimate: true,
|
||||||
|
animation: false,
|
||||||
|
timeline: false,
|
||||||
|
geocoder: false,
|
||||||
|
navigationHelpButton: false,
|
||||||
|
contextOptions: {
|
||||||
|
contextType: 2
|
||||||
|
},
|
||||||
|
creditContainer: document.createElement('div')
|
||||||
|
}
|
||||||
|
|
||||||
|
const initMap = () => {
|
||||||
|
viewer = new Cesium.Viewer('cesiumContainer', cesiumConfig)
|
||||||
|
viewer._cesiumWidget._creditContainer.style.display = 'none'
|
||||||
|
viewer.scene.sun.show = false
|
||||||
|
viewer.scene.moon.show = false
|
||||||
|
viewer.scene.fog.enabled = false
|
||||||
|
viewer.scene.skyAtmosphere.show = false
|
||||||
|
viewer.scene.skyBox.show = false
|
||||||
|
viewer.scene.globe.enableLighting = false
|
||||||
|
viewer.shadowMap.darkness = 0.8
|
||||||
|
viewer.scene._sunBloom = false
|
||||||
|
viewer.scene.globe.showGroundAtmosphere = false
|
||||||
|
viewer.scene.postProcessStages.fxaa.enabled = true
|
||||||
|
viewer.camera.setView({
|
||||||
|
destination: Cesium.Cartesian3.fromDegrees(112, 18, 4000000)
|
||||||
|
})
|
||||||
|
modifyCesiumMouseWheel(viewer)
|
||||||
|
|
||||||
|
if (CesiumHostAddr.value) {
|
||||||
|
const shipline = new Cesium.WebMapTileServiceImageryProvider({
|
||||||
|
url: CesiumHostAddr.value + 'geoserver/gwc/service/wmts/rest/ougp:正射影像图_GM输出_20240516/{TileMatrixSet}/{TileMatrixSet}:{TileMatrix}/{TileRow}/{TileCol}?format=image/png',
|
||||||
|
layer: 'ougp:正射影像图_GM输出_20240516',
|
||||||
|
style: 'default',
|
||||||
|
format: 'image/png',
|
||||||
|
tileMatrixSetID: 'EPSG:4326',
|
||||||
|
tilingScheme: new Cesium.GeographicTilingScheme()
|
||||||
|
})
|
||||||
|
viewer.imageryLayers.addImageryProvider(shipline)
|
||||||
|
|
||||||
|
const contour6000 = new Cesium.WebMapTileServiceImageryProvider({
|
||||||
|
url: CesiumHostAddr.value + 'geoserver/gwc/service/wmts/rest/ougp:contour6000/{TileMatrixSet}/{TileMatrixSet}:{TileMatrix}/{TileRow}/{TileCol}?format=image/png',
|
||||||
|
layer: 'ougp:contour6000',
|
||||||
|
style: 'default',
|
||||||
|
format: 'image/png',
|
||||||
|
tileMatrixSetID: 'EPSG:4326',
|
||||||
|
tilingScheme: new Cesium.GeographicTilingScheme()
|
||||||
|
})
|
||||||
|
viewer.imageryLayers.addImageryProvider(contour6000)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
|
||||||
|
handler.setInputAction((click) => {
|
||||||
|
const picked = viewer.scene.pick(click.position)
|
||||||
|
if (Cesium.defined(picked) && picked.id?.properties) {
|
||||||
|
currentMarker.value = picked.id.properties.getValue()
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK)
|
||||||
|
|
||||||
|
updateMapMarkers()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (route.query.query) {
|
||||||
|
searchQuery.value = String(route.query.query)
|
||||||
|
}
|
||||||
|
if (route.query.category) {
|
||||||
|
searchCategory.value = String(route.query.category)
|
||||||
|
}
|
||||||
|
getParamsList()
|
||||||
|
getDataList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
height: 820px;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
width: 450px;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 30px 30px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.fold {
|
||||||
|
position: absolute;
|
||||||
|
top: 350px;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-text {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
.input-bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-bar {
|
||||||
|
margin-top: 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
margin-top: 10px;
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
color: #333;
|
||||||
|
height: 620px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
padding: 30px 0;
|
||||||
|
border-bottom: 1px solid #dddddd;
|
||||||
|
|
||||||
|
&:hover .name {
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
padding-bottom: 5px;
|
||||||
|
color: #000;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
padding: 5px 0;
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
.col {
|
||||||
|
flex: 1;
|
||||||
|
color: #999;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.map {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.unfold {
|
||||||
|
position: absolute;
|
||||||
|
top: 350px;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
margin: 10px 0 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,847 @@
|
||||||
|
<template>
|
||||||
|
<div class="home-container">
|
||||||
|
<!-- Hero 区域 -->
|
||||||
|
<section class="hero">
|
||||||
|
<div class="hero-overlay"></div>
|
||||||
|
<div class="hero-content">
|
||||||
|
<div class="search-box">
|
||||||
|
<div class="search-input-wrap">
|
||||||
|
<el-icon class="search-icon"><Search /></el-icon>
|
||||||
|
<input
|
||||||
|
v-model="searchKeyword"
|
||||||
|
type="text"
|
||||||
|
placeholder="请输入检索关键词"
|
||||||
|
@keyup.enter="handleHeroSearch"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button class="search-btn" @click="handleHeroSearch">搜索</button>
|
||||||
|
</div>
|
||||||
|
<p class="hero-desc">
|
||||||
|
深海地球物理数据管理可视化平台面向深海调查与科学研究需求,汇聚多波束、地震、重磁、侧扫及海洋环境等多类型地球物理数据,
|
||||||
|
提供数据入库、可视化检索与空间分析能力,服务数据贡献者、科研团队和管理机构。
|
||||||
|
</p>
|
||||||
|
<div class="stat-grid">
|
||||||
|
<div class="stat-card" v-for="item in statCards" :key="item.key">
|
||||||
|
<div class="stat-icon" :class="`stat-icon--${item.key}`">
|
||||||
|
<StatIcon :type="item.key" :size="44" />
|
||||||
|
</div>
|
||||||
|
<div class="stat-info">
|
||||||
|
<div class="stat-label">{{ item.label }}</div>
|
||||||
|
<div class="stat-value">
|
||||||
|
<span>{{ item.value }}</span>{{ item.unit }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 数据资源 -->
|
||||||
|
<section class="section resources-section">
|
||||||
|
<div class="section-inner">
|
||||||
|
<div class="section-header">
|
||||||
|
<span class="section-bar"></span>
|
||||||
|
<h2>数据资源</h2>
|
||||||
|
</div>
|
||||||
|
<div class="resource-grid">
|
||||||
|
<div
|
||||||
|
class="resource-card"
|
||||||
|
v-for="(item, index) in resourceCategories"
|
||||||
|
:key="item.id"
|
||||||
|
:class="{ active: activeResource === index }"
|
||||||
|
@click="handleResourceClick(item, index)"
|
||||||
|
>
|
||||||
|
<div class="resource-cover" :style="{ backgroundImage: `url(${item.cover})` }">
|
||||||
|
<div class="resource-cover-overlay"></div>
|
||||||
|
<span class="resource-type-tag">{{ item.desc }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="resource-footer">
|
||||||
|
<div class="resource-name">{{ item.label }}</div>
|
||||||
|
<div class="resource-count">{{ item.count }} 个数据集</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 热门数据 -->
|
||||||
|
<section class="section hot-section">
|
||||||
|
<div class="section-inner">
|
||||||
|
<div class="section-header hot-header">
|
||||||
|
<div class="left">
|
||||||
|
<span class="section-bar"></span>
|
||||||
|
<h2>热门数据</h2>
|
||||||
|
</div>
|
||||||
|
<div class="hot-tabs">
|
||||||
|
<button
|
||||||
|
v-for="tab in hotTabs"
|
||||||
|
:key="tab.key"
|
||||||
|
:class="{ active: hotTab === tab.key }"
|
||||||
|
@click="hotTab = tab.key"
|
||||||
|
>
|
||||||
|
{{ tab.label }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="hot-content">
|
||||||
|
<div class="hot-featured" v-if="featuredData" @click="goDataDetail(featuredData.id)">
|
||||||
|
<span class="hot-badge">HOT</span>
|
||||||
|
<div class="featured-cover">
|
||||||
|
<img :src="getDataTypeImage(featuredData.data_type)" :alt="featuredData.data_name" />
|
||||||
|
<div class="featured-cover-overlay"></div>
|
||||||
|
<div class="featured-type">{{ getDataTypeLabel(featuredData.data_type) }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="featured-body">
|
||||||
|
<h3>{{ featuredData.data_name }}</h3>
|
||||||
|
<p>{{ featuredData.data_code }} · {{ featuredData.voyage_name }} · {{ featuredData.file_size }}</p>
|
||||||
|
<div class="meta-grid">
|
||||||
|
<div class="meta-item">
|
||||||
|
<span class="meta-label">数据类型</span>
|
||||||
|
<span class="meta-value">{{ getDataTypeLabel(featuredData.data_type) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span class="meta-label">数据编号</span>
|
||||||
|
<span class="meta-value">{{ featuredData.data_code }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span class="meta-label">入库时间</span>
|
||||||
|
<span class="meta-value">{{ featuredData.fill_time }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="hot-list">
|
||||||
|
<div
|
||||||
|
class="hot-item"
|
||||||
|
v-for="item in hotListData"
|
||||||
|
:key="item.id"
|
||||||
|
@click="goDataDetail(item.id)"
|
||||||
|
>
|
||||||
|
<span class="hot-badge small">HOT</span>
|
||||||
|
<div class="hot-thumb">
|
||||||
|
<img :src="getDataTypeImage(item.data_type)" :alt="item.data_name" />
|
||||||
|
</div>
|
||||||
|
<div class="hot-item-body">
|
||||||
|
<h4>{{ item.data_name }}</h4>
|
||||||
|
<p>{{ item.data_code }} · {{ item.voyage_name }}</p>
|
||||||
|
<div class="meta-grid compact">
|
||||||
|
<div class="meta-item">
|
||||||
|
<span class="meta-label">数据类型</span>
|
||||||
|
<span class="meta-value">{{ getDataTypeLabel(item.data_type) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span class="meta-label">数据编号</span>
|
||||||
|
<span class="meta-value">{{ item.data_code }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span class="meta-label">入库时间</span>
|
||||||
|
<span class="meta-value">{{ item.fill_time }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 空间分布地图 -->
|
||||||
|
<section class="section map-section">
|
||||||
|
<div class="section-inner">
|
||||||
|
<div class="section-header center">
|
||||||
|
<span class="section-bar"></span>
|
||||||
|
<h2>地球物理数据空间分布</h2>
|
||||||
|
</div>
|
||||||
|
<div class="map" id="cesiumContainer"></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import * as Cesium from 'cesium'
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { Search } from '@element-plus/icons-vue'
|
||||||
|
import { getParam } from '@/api/task.js'
|
||||||
|
import { dataPage } from '@/api/data.js'
|
||||||
|
import { modifyCesiumMouseWheel } from '@/utils/common'
|
||||||
|
import { getDataTypeLabel } from '@/constants/dataTypes.js'
|
||||||
|
import { getDataTypeImage } from '@/constants/homeImages.js'
|
||||||
|
import {
|
||||||
|
HOME_RESOURCE_CATEGORIES,
|
||||||
|
HOME_STAT_CARDS,
|
||||||
|
HOT_DATA_TABS,
|
||||||
|
enrichHotDataMetrics
|
||||||
|
} from '@/mock/homeData.js'
|
||||||
|
import StatIcon from '@/components/home/StatIcon.vue'
|
||||||
|
|
||||||
|
import markerIcon1 from '@/assets/icons/标注点1.png'
|
||||||
|
import markerIcon2 from '@/assets/icons/标注点2.png'
|
||||||
|
import markerIcon3 from '@/assets/icons/标注点3.png'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const searchKeyword = ref('')
|
||||||
|
const statCards = HOME_STAT_CARDS
|
||||||
|
const resourceCategories = HOME_RESOURCE_CATEGORIES
|
||||||
|
const hotTabs = HOT_DATA_TABS
|
||||||
|
const hotTab = ref('views')
|
||||||
|
const activeResource = ref(1)
|
||||||
|
const hotDataList = ref([])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按当前 Tab 排序后的热门数据
|
||||||
|
*/
|
||||||
|
const sortedHotData = computed(() => {
|
||||||
|
const list = [...hotDataList.value]
|
||||||
|
const field = hotTab.value
|
||||||
|
return list.sort((a, b) => (b[field] || 0) - (a[field] || 0))
|
||||||
|
})
|
||||||
|
|
||||||
|
const featuredData = computed(() => sortedHotData.value[0] || null)
|
||||||
|
const hotListData = computed(() => sortedHotData.value.slice(1, 4))
|
||||||
|
|
||||||
|
const handleHeroSearch = () => {
|
||||||
|
router.push({
|
||||||
|
path: '/demand',
|
||||||
|
query: searchKeyword.value ? { query: searchKeyword.value } : {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleResourceClick = (item, index) => {
|
||||||
|
activeResource.value = index
|
||||||
|
router.push({ path: item.route, query: item.query })
|
||||||
|
}
|
||||||
|
|
||||||
|
const goDataDetail = (id) => {
|
||||||
|
router.push(`/input/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDataList = () => {
|
||||||
|
dataPage({ start: 0, limit: 13 }).then(res => {
|
||||||
|
hotDataList.value = enrichHotDataMetrics(res.page.records)
|
||||||
|
updateMapMarkers()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const CesiumHostAddr = ref('')
|
||||||
|
|
||||||
|
const getParamsList = () => {
|
||||||
|
if (import.meta.env.VITE_APP_ENV === 'development') {
|
||||||
|
CesiumHostAddr.value = import.meta.env.VITE_APP_DS_API
|
||||||
|
initMap()
|
||||||
|
} else {
|
||||||
|
getParam().then(res => {
|
||||||
|
const data = res.array.find(item => item.param_name === 'CesiumHostAddr')
|
||||||
|
CesiumHostAddr.value = data?.param_value ||
|
||||||
|
`${window.location.protocol}//${window.location.hostname}:${window.location.port}/tiles/`
|
||||||
|
initMap()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let viewer
|
||||||
|
|
||||||
|
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkY2Y5NWY2Ni1mODQ1LTQ3YTUtYjc4Zi1jYzhjMGI2YzcxMWYiLCJpZCI6MTI5ODMwLCJpYXQiOjE2Nzk0Njk5NTd9.DTH54ioOH-HLqeNIetBe9hFyrPOX2Vp1AQmZzw8TIZ4'
|
||||||
|
|
||||||
|
const cesiumConfig = {
|
||||||
|
sceneMode: Cesium.SceneMode.SCENE2D,
|
||||||
|
homeButton: false,
|
||||||
|
sceneModePicker: false,
|
||||||
|
fullscreenButton: false,
|
||||||
|
infoBox: false,
|
||||||
|
selectionIndicator: false,
|
||||||
|
baseLayerPicker: false,
|
||||||
|
shadows: true,
|
||||||
|
shouldAnimate: true,
|
||||||
|
animation: false,
|
||||||
|
timeline: false,
|
||||||
|
geocoder: false,
|
||||||
|
navigationHelpButton: false,
|
||||||
|
contextOptions: { contextType: 2 },
|
||||||
|
creditContainer: document.createElement('div')
|
||||||
|
}
|
||||||
|
|
||||||
|
const initMap = () => {
|
||||||
|
viewer = new Cesium.Viewer('cesiumContainer', cesiumConfig)
|
||||||
|
viewer._cesiumWidget._creditContainer.style.display = 'none'
|
||||||
|
viewer.scene.sun.show = false
|
||||||
|
viewer.scene.moon.show = false
|
||||||
|
viewer.scene.fog.enabled = false
|
||||||
|
viewer.scene.skyAtmosphere.show = false
|
||||||
|
viewer.scene.skyBox.show = false
|
||||||
|
viewer.scene.globe.enableLighting = false
|
||||||
|
viewer.shadowMap.darkness = 0.8
|
||||||
|
viewer.scene._sunBloom = false
|
||||||
|
viewer.scene.globe.showGroundAtmosphere = false
|
||||||
|
viewer.scene.postProcessStages.fxaa.enabled = true
|
||||||
|
viewer.camera.setView({
|
||||||
|
destination: Cesium.Cartesian3.fromDegrees(112, 18, 4000000)
|
||||||
|
})
|
||||||
|
modifyCesiumMouseWheel(viewer)
|
||||||
|
|
||||||
|
if (CesiumHostAddr.value) {
|
||||||
|
viewer.imageryLayers.addImageryProvider(new Cesium.WebMapTileServiceImageryProvider({
|
||||||
|
url: CesiumHostAddr.value + 'geoserver/gwc/service/wmts/rest/ougp:正射影像图_GM输出_20240516/{TileMatrixSet}/{TileMatrixSet}:{TileMatrix}/{TileRow}/{TileCol}?format=image/png',
|
||||||
|
layer: 'ougp:正射影像图_GM输出_20240516',
|
||||||
|
style: 'default',
|
||||||
|
format: 'image/png',
|
||||||
|
tileMatrixSetID: 'EPSG:4326',
|
||||||
|
tilingScheme: new Cesium.GeographicTilingScheme()
|
||||||
|
}))
|
||||||
|
viewer.imageryLayers.addImageryProvider(new Cesium.WebMapTileServiceImageryProvider({
|
||||||
|
url: CesiumHostAddr.value + 'geoserver/gwc/service/wmts/rest/ougp:contour6000/{TileMatrixSet}/{TileMatrixSet}:{TileMatrix}/{TileRow}/{TileCol}?format=image/png',
|
||||||
|
layer: 'ougp:contour6000',
|
||||||
|
style: 'default',
|
||||||
|
format: 'image/png',
|
||||||
|
tileMatrixSetID: 'EPSG:4326',
|
||||||
|
tilingScheme: new Cesium.GeographicTilingScheme()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMapMarkers()
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateMapMarkers = () => {
|
||||||
|
if (!viewer) return
|
||||||
|
viewer.entities.removeAll()
|
||||||
|
hotDataList.value.forEach(item => {
|
||||||
|
let icon = markerIcon1
|
||||||
|
if (item.data_status === '处理中') icon = markerIcon2
|
||||||
|
if (item.data_status === '已入库') icon = markerIcon3
|
||||||
|
viewer.entities.add({
|
||||||
|
position: Cesium.Cartesian3.fromDegrees(item.data_lon, item.data_lat),
|
||||||
|
billboard: { image: icon, width: 28, height: 28 }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getDataList()
|
||||||
|
getParamsList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.home-container {
|
||||||
|
background: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
position: relative;
|
||||||
|
margin-top: -100px;
|
||||||
|
padding: 224px 8% 60px;
|
||||||
|
min-height: 520px;
|
||||||
|
background: url(@/assets/loginBg.jpg) center / cover no-repeat;
|
||||||
|
|
||||||
|
.hero-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(180deg, rgba(0, 40, 90, 0.55) 0%, rgba(0, 60, 120, 0.75) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
display: flex;
|
||||||
|
max-width: 720px;
|
||||||
|
margin: 8px auto 24px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||||
|
|
||||||
|
.search-input-wrap {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 16px;
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #999;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
flex: 1;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
height: 52px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-btn {
|
||||||
|
width: 100px;
|
||||||
|
border: none;
|
||||||
|
background: #0056a4;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #004080;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-desc {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto 36px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.8;
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 20px 22px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(255, 255, 255, 0.78);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.55);
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 40, 90, 0.12);
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 28px rgba(0, 40, 90, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 14px;
|
||||||
|
|
||||||
|
&--multibeam {
|
||||||
|
background: linear-gradient(145deg, #eef6fc 0%, #d6ebf9 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--seismic {
|
||||||
|
background: linear-gradient(145deg, #fef6ec 0%, #fde8cf 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--magnetic {
|
||||||
|
background: linear-gradient(145deg, #edf9f1 0%, #d4f0de 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--sidescan {
|
||||||
|
background: linear-gradient(145deg, #f4eefb 0%, #e8dcf5 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--dataset {
|
||||||
|
background: linear-gradient(145deg, #eef4fc 0%, #d8e6f7 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--gravity {
|
||||||
|
background: linear-gradient(145deg, #ecf9f5 0%, #d0f0e6 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--ocean {
|
||||||
|
background: linear-gradient(145deg, #eef9fd 0%, #d4eef9 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--records {
|
||||||
|
background: linear-gradient(145deg, #f0f2fc 0%, #dde3f7 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-info {
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #888;
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #0056a4;
|
||||||
|
margin-right: 3px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
padding: 50px 0;
|
||||||
|
|
||||||
|
.section-inner {
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 8%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
|
||||||
|
&.center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hot-header {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-bar {
|
||||||
|
display: inline-block;
|
||||||
|
width: 4px;
|
||||||
|
height: 22px;
|
||||||
|
background: #0056a4;
|
||||||
|
border-radius: 2px;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #242424;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.resources-section {
|
||||||
|
background: #f5f7fa url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23d0d8e0' fill-opacity='0.3'%3E%3Ccircle cx='1' cy='1' r='1'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
|
||||||
|
|
||||||
|
.resource-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
cursor: pointer;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
transition: all 0.25s ease;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.active {
|
||||||
|
border-color: #0056a4;
|
||||||
|
box-shadow: 0 6px 20px rgba(0, 86, 164, 0.18);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-cover {
|
||||||
|
position: relative;
|
||||||
|
height: 160px;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
padding: 16px;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
|
||||||
|
.resource-cover-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(180deg, rgba(0, 20, 50, 0.15) 0%, rgba(0, 30, 70, 0.55) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-type-tag {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-footer {
|
||||||
|
padding: 16px 18px;
|
||||||
|
|
||||||
|
.resource-name {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #242424;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-count {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hot-section {
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
.hot-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 6px 18px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #fff;
|
||||||
|
color: #666;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #0056a4;
|
||||||
|
border-color: #0056a4;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(.active) {
|
||||||
|
border-color: #0056a4;
|
||||||
|
color: #0056a4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hot-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1.2fr;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hot-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
left: 12px;
|
||||||
|
z-index: 2;
|
||||||
|
background: #f5a623;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
|
&.small {
|
||||||
|
top: 8px;
|
||||||
|
left: 8px;
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 1px 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hot-featured {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: box-shadow 0.25s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-cover {
|
||||||
|
position: relative;
|
||||||
|
height: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-cover-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(180deg, rgba(0, 30, 70, 0.1) 0%, rgba(0, 30, 70, 0.55) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-type {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 1;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: rgba(255, 255, 255, 0.95);
|
||||||
|
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
|
||||||
|
padding: 6px 14px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(0, 50, 100, 0.45);
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-body {
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #0056a4;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0 0 16px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hot-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hot-item {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
background: #fafbfc;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.25s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #0056a4;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 86, 164, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hot-thumb {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 120px;
|
||||||
|
height: 90px;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hot-item-body {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin: 0 0 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #0056a4;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
&.compact {
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-item {
|
||||||
|
.meta-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #bbb;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-value {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #444;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-section {
|
||||||
|
background: #f5f7fa;
|
||||||
|
padding-bottom: 60px;
|
||||||
|
|
||||||
|
.map {
|
||||||
|
width: 100%;
|
||||||
|
height: 600px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.hero .stat-grid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.resources-section .resource-grid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hot-section .hot-content {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,189 @@
|
||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<div class="content">
|
||||||
|
<div class="left">
|
||||||
|
<p>面向深海地球物理调查需求,构建用户体验良好的地球物理数据目录信息平台。</p>
|
||||||
|
<p>提供便捷安全的数据入库、统计分析和可视化检索工具,服务数据贡献者、资助机构和管理机构。</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="login_img">
|
||||||
|
<img src="@/assets/logo.png" alt="">
|
||||||
|
<div class="cn">深海地球物理数据管理可视化平台</div>
|
||||||
|
</div>
|
||||||
|
<div class="form">
|
||||||
|
<div class="input">
|
||||||
|
<img src="@/assets/icon_user.png" alt="">
|
||||||
|
<input v-model="loginName" type="text" placeholder="请输入用户名">
|
||||||
|
</div>
|
||||||
|
<div class="input">
|
||||||
|
<img src="@/assets/icon_pwd.png" alt="">
|
||||||
|
<input v-model="loginPass" type="password" placeholder="请输入密码" @keyup.enter="onLogin">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="btn">
|
||||||
|
<el-button @click="onReset">重置</el-button>
|
||||||
|
<el-button type="primary" @click="onLogin">登录</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { localLogin } from '@/api/data.js'
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import md5 from 'md5'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useAuthStore } from '@/store/authStore'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
|
const loginName = ref('')
|
||||||
|
const loginPass = ref('')
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (authStore.isLoggedIn) {
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const onReset = () => {
|
||||||
|
loginName.value = ''
|
||||||
|
loginPass.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const onLogin = () => {
|
||||||
|
if (!loginName.value) return ElMessage('请输入用户名')
|
||||||
|
if (!loginPass.value) return ElMessage('请输入密码')
|
||||||
|
|
||||||
|
localLogin({
|
||||||
|
loginName: loginName.value,
|
||||||
|
loginPass: md5(loginPass.value)
|
||||||
|
}).then(res => {
|
||||||
|
if (res.success) {
|
||||||
|
authStore.login(res.entity)
|
||||||
|
ElMessage.success('登录成功')
|
||||||
|
router.push('/')
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message || '登录失败')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
background: url(@/assets/loginBg.jpg) center no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
max-width: 1400px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
width: 50%;
|
||||||
|
height: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
p {
|
||||||
|
font-size: 26px;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
width: 540px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 45px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.login_img {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn {
|
||||||
|
font-size: 28px;
|
||||||
|
color: #000;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.input {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
width: 100%;
|
||||||
|
height: 60px;
|
||||||
|
border: 1px solid #eeeeee;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
margin-right: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 90%;
|
||||||
|
height: auto;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
outline: none;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 60px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
width: 48%;
|
||||||
|
height: 50px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<div class="title">深海应急搜捞 统计分析</div>
|
||||||
|
<div class="nums">
|
||||||
|
<div class="item">
|
||||||
|
<img src="@/assets/icons/任务.png" alt="任务" />
|
||||||
|
<div class="right">
|
||||||
|
<div>完成航次</div>
|
||||||
|
<div><span>{{ rescueTotal }}</span>次</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<img src="@/assets/icons/区域.png" alt="区域" />
|
||||||
|
<div class="right">
|
||||||
|
<div>潜水次数</div>
|
||||||
|
<div><span>{{ divingTotal }}</span>次</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<img src="@/assets/icons/航次.png" alt="航次" />
|
||||||
|
<div class="right">
|
||||||
|
<div>挽回经济损失</div>
|
||||||
|
<div><span>{{ priceTotal }}</span>万</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<img src="@/assets/icons/潜水艇.png" alt="潜水艇" />
|
||||||
|
<div class="right">
|
||||||
|
<div>专项调查区域覆盖</div>
|
||||||
|
<div><span>{{ areaTotal }}</span>平方公里</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<img src="@/assets/icons/潜艇.png" alt="潜艇" />
|
||||||
|
<div class="right">
|
||||||
|
<div>航行时间</div>
|
||||||
|
<div><span>{{ hourTotal }}</span>小时</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { reportMain } from '@/api/task.js'
|
||||||
|
import {ref, onMounted} from "vue";
|
||||||
|
|
||||||
|
const areaTotal = ref('')
|
||||||
|
const divingTotal = ref('')
|
||||||
|
const hourTotal = ref('')
|
||||||
|
const mileageTotal = ref('')
|
||||||
|
const priceTotal = ref('')
|
||||||
|
const rescueTotal = ref('')
|
||||||
|
const getData = () => {
|
||||||
|
reportMain().then(res => {
|
||||||
|
const obj = res.array[0]
|
||||||
|
areaTotal.value = obj.area_total
|
||||||
|
divingTotal.value = obj.diving_total
|
||||||
|
hourTotal.value = obj.hour_total
|
||||||
|
mileageTotal.value = obj.mileage_total
|
||||||
|
priceTotal.value = obj.price_total
|
||||||
|
rescueTotal.value = obj.rescue_total
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
padding: 0 8%;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-top: 100px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 40px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nums {
|
||||||
|
margin-top: 200px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: rgba(33, 54, 87, 0.7);
|
||||||
|
|
||||||
|
.item {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 40px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<div class="title">地球物理数据入库</div>
|
||||||
|
<el-divider />
|
||||||
|
<el-tabs v-model="activeName" type="card">
|
||||||
|
<el-tab-pane label="数据信息" name="data">
|
||||||
|
<FormData
|
||||||
|
:data-id="dataId"
|
||||||
|
@saved="onSaved"
|
||||||
|
@upload="openUploadDialog"
|
||||||
|
@cloud-path="openCloudDialog"
|
||||||
|
/>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="数据资源" name="resource" v-if="dataId">
|
||||||
|
<DataResourcePanel ref="resourcePanelRef" :data-id="dataId" />
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, nextTick } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import FormData from '@/views/common/FormData.vue'
|
||||||
|
import DataResourcePanel from '@/views/common/DataResourcePanel.vue'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const dataId = ref(route.params.id || '')
|
||||||
|
const activeName = ref('data')
|
||||||
|
const resourcePanelRef = ref(null)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据保存成功后更新 ID
|
||||||
|
* @param {Object} entity
|
||||||
|
*/
|
||||||
|
const onSaved = (entity) => {
|
||||||
|
if (entity?.id) {
|
||||||
|
dataId.value = String(entity.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const openUploadDialog = async () => {
|
||||||
|
if (!dataId.value) {
|
||||||
|
ElMessage.warning('请先保存数据基本信息')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
activeName.value = 'resource'
|
||||||
|
await nextTick()
|
||||||
|
resourcePanelRef.value?.openUploadDialog?.()
|
||||||
|
}
|
||||||
|
|
||||||
|
const openCloudDialog = async () => {
|
||||||
|
if (!dataId.value) {
|
||||||
|
ElMessage.warning('请先保存数据基本信息')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
activeName.value = 'resource'
|
||||||
|
await nextTick()
|
||||||
|
resourcePanelRef.value?.openCloudDialog?.()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
padding: 20px 8%;
|
||||||
|
color: #242424;
|
||||||
|
min-height: calc(100vh - 220px);
|
||||||
|
background-color: rgb(241, 244, 251);
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,432 @@
|
||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<div class="left" v-show="isShow">
|
||||||
|
<img class="fold" v-show="isShow" src="@/assets/icons/折叠.png" @click="handleFold">
|
||||||
|
<div class="search">
|
||||||
|
<p class="rule-text">编号规则:任务类型_目标类型_4位编号</p>
|
||||||
|
<div class="input-bar">
|
||||||
|
<el-input
|
||||||
|
v-model="rescueCode"
|
||||||
|
style="width: 120px"
|
||||||
|
size="large"
|
||||||
|
placeholder="任务编号"
|
||||||
|
/>
|
||||||
|
<el-select
|
||||||
|
v-model="rescueType"
|
||||||
|
placeholder="任务类型"
|
||||||
|
size="large"
|
||||||
|
style="width: 120px"
|
||||||
|
>
|
||||||
|
<el-option label="应急搜捞" value="SAR" />
|
||||||
|
<el-option label="专项调查" value="SI" />
|
||||||
|
</el-select>
|
||||||
|
<el-select
|
||||||
|
v-model="rescueResponseLevel"
|
||||||
|
placeholder="响应等级"
|
||||||
|
size="large"
|
||||||
|
style="width: 120px"
|
||||||
|
>
|
||||||
|
<el-option label="一级" value="一级" />
|
||||||
|
<el-option label="二级" value="二级" />
|
||||||
|
<el-option label="三级" value="三级" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="btn-bar">
|
||||||
|
<el-button :icon="RefreshRight" @click="onReset">重置</el-button>
|
||||||
|
<el-button type="primary" :icon="Search" @click="onSearch">搜索</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="line" />
|
||||||
|
<div class="list">
|
||||||
|
<div class="item" v-for="item in dataList" :key="item.id">
|
||||||
|
<div class="name">{{ item.rescue_code }}</div>
|
||||||
|
<div class="time">{{ item.fill_time }}</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">航次:<span>{{ item.voyage_name }}</span></div>
|
||||||
|
<div class="col">任务状态:<span>{{ item.rescue_status }}</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">快速响应等级:<span>{{ item.rescue_response_level }}</span></div>
|
||||||
|
<div class="col">目标名称:<span>{{ item.rescue_target_name }}</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">任务类型:<span>{{ item.rescue_type === 'SAR' ? '应急搜捞' : '专项调查' }}</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pagination">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="currentPage"
|
||||||
|
v-model:page-size="pageSize"
|
||||||
|
layout="total, prev, pager, next"
|
||||||
|
:total="total"
|
||||||
|
@current-change="handleCurrentChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="map">
|
||||||
|
<div id="cesiumContainer" style="width: 100%; height: 100%;"></div>
|
||||||
|
<img class="unfold" v-show="!isShow" src="@/assets/icons/展开.png" alt="" @click="handleUnfold">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
title="任务详情"
|
||||||
|
width="500"
|
||||||
|
>
|
||||||
|
<div class="item">
|
||||||
|
<div class="name">{{ currentMarker.rescue_code }}</div>
|
||||||
|
<div class="time">{{ currentMarker.fill_time }}</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">航次:<span>{{ currentMarker.voyage_name }}</span></div>
|
||||||
|
<div class="col">任务状态:<span>{{ currentMarker.rescue_status }}</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">快速响应等级:<span>{{ currentMarker.rescue_response_level }}</span></div>
|
||||||
|
<div class="col">目标名称:<span>{{ currentMarker.rescue_target_name }}</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">任务类型:<span>{{ currentMarker.rescue_type === 'SAR' ? '应急搜捞' : '专项调查' }}</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import * as Cesium from "cesium";
|
||||||
|
import {ref, onMounted} from 'vue'
|
||||||
|
import { Search, RefreshRight } from '@element-plus/icons-vue'
|
||||||
|
import { regPage, getParam } from '@/api/task.js'
|
||||||
|
import { modifyCesiumMouseWheel, setImageryViewModels } from '@/utils/common'
|
||||||
|
|
||||||
|
// 导入静态图片icon
|
||||||
|
import markerIcon1 from "@/assets/icons/标注点1.png";
|
||||||
|
import markerIcon2 from "@/assets/icons/标注点2.png";
|
||||||
|
import markerIcon3 from "@/assets/icons/标注点3.png";
|
||||||
|
|
||||||
|
// 左侧数据栏展开与收缩
|
||||||
|
const isShow = ref(true)
|
||||||
|
|
||||||
|
const handleFold = () => {
|
||||||
|
isShow.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUnfold = () => {
|
||||||
|
isShow.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const rescueCode = ref('')
|
||||||
|
const rescueType = ref('')
|
||||||
|
const rescueResponseLevel = ref('')
|
||||||
|
const currentPage = ref(1)
|
||||||
|
const pageSize = ref(10)
|
||||||
|
const start = ref(0)
|
||||||
|
const total = ref(0)
|
||||||
|
// 列表数据
|
||||||
|
const dataList = ref([])
|
||||||
|
const markers = ref([])
|
||||||
|
|
||||||
|
const handleCurrentChange = (val) => {
|
||||||
|
currentPage.value = val
|
||||||
|
start.value = pageSize.value * (val -1)
|
||||||
|
getDataList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取列表数据
|
||||||
|
const getDataList = () => {
|
||||||
|
regPage({
|
||||||
|
start: start.value,
|
||||||
|
limit: 10,
|
||||||
|
rescue_type: rescueType.value,
|
||||||
|
rescue_response_level: rescueResponseLevel.value,
|
||||||
|
rescue_code: rescueCode.value,
|
||||||
|
}).then(res => {
|
||||||
|
dataList.value = res.page.records
|
||||||
|
const data = []
|
||||||
|
dataList.value.forEach(item => {
|
||||||
|
if (item.rescue_status === '任务开始') {
|
||||||
|
data.push({
|
||||||
|
position: {
|
||||||
|
lng: item.rescue_target_lon,
|
||||||
|
lat: item.rescue_target_lat
|
||||||
|
},
|
||||||
|
icon: markerIcon1,
|
||||||
|
id: item.tsy_id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (item.rescue_status === '任务处理中') {
|
||||||
|
data.push({
|
||||||
|
position: {
|
||||||
|
lng: item.rescue_target_lon,
|
||||||
|
lat: item.rescue_target_lat
|
||||||
|
},
|
||||||
|
icon: markerIcon2,
|
||||||
|
id: item.tsy_id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (item.rescue_status === '任务已完成') {
|
||||||
|
data.push({
|
||||||
|
position: {
|
||||||
|
lng: item.rescue_target_lon,
|
||||||
|
lat: item.rescue_target_lat
|
||||||
|
},
|
||||||
|
icon: markerIcon3,
|
||||||
|
id: item.tsy_id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
markers.value = data
|
||||||
|
total.value = res.page.total
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onReset = () => {
|
||||||
|
rescueType.value = ''
|
||||||
|
rescueResponseLevel.value = ''
|
||||||
|
rescueCode.value = ''
|
||||||
|
start.value = 0
|
||||||
|
getDataList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSearch = () => {
|
||||||
|
start.value = 0
|
||||||
|
getDataList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 地图
|
||||||
|
const CesiumHostAddr = ref('')
|
||||||
|
// 获取系统参数列表
|
||||||
|
const getParamsList = () => {
|
||||||
|
// 判断当前环境
|
||||||
|
if (import.meta.env.VITE_APP_ENV === 'development') {
|
||||||
|
// 开发环境:直接使用环境变量中的值
|
||||||
|
CesiumHostAddr.value = import.meta.env.VITE_APP_DS_API
|
||||||
|
initMap();
|
||||||
|
} else {
|
||||||
|
// 生产环境:通过接口获取参数
|
||||||
|
getParam().then(res => {
|
||||||
|
const data = res.array.find(item => item.param_name === 'CesiumHostAddr')
|
||||||
|
|
||||||
|
if (data?.param_value) {
|
||||||
|
CesiumHostAddr.value = data.param_value
|
||||||
|
} else {
|
||||||
|
// 备用方案:基于当前窗口位置构建URL[1](@ref)
|
||||||
|
CesiumHostAddr.value = `${window.location.protocol}//${window.location.hostname}:${window.location.port}/tiles/`
|
||||||
|
}
|
||||||
|
initMap();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let viewer;
|
||||||
|
|
||||||
|
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkY2Y5NWY2Ni1mODQ1LTQ3YTUtYjc4Zi1jYzhjMGI2YzcxMWYiLCJpZCI6MTI5ODMwLCJpYXQiOjE2Nzk0Njk5NTd9.DTH54ioOH-HLqeNIetBe9hFyrPOX2Vp1AQmZzw8TIZ4';
|
||||||
|
const cesiumConfig = {
|
||||||
|
sceneMode: Cesium.SceneMode.SCENE2D,
|
||||||
|
// 主页按钮
|
||||||
|
homeButton: false,
|
||||||
|
// 场景模式选择器
|
||||||
|
sceneModePicker: false,
|
||||||
|
// 全屏按钮
|
||||||
|
fullscreenButton: false,
|
||||||
|
// 是否显示点击要素之后显示的信息
|
||||||
|
infoBox: false,
|
||||||
|
// 要素选中框
|
||||||
|
selectionIndicator: false,
|
||||||
|
// 影像切换
|
||||||
|
baseLayerPicker: false,
|
||||||
|
// 启用了阴影效果
|
||||||
|
shadows: true,
|
||||||
|
// 启用动画
|
||||||
|
shouldAnimate: true,
|
||||||
|
// 是否显示动画控件
|
||||||
|
animation: false,
|
||||||
|
// 是否显示时间线控件
|
||||||
|
timeline: false,
|
||||||
|
// 是否显示地名查找控件
|
||||||
|
geocoder: false,
|
||||||
|
// 是否显示帮助信息控件
|
||||||
|
navigationHelpButton: false,
|
||||||
|
contextOptions: {
|
||||||
|
contextType: 2, // Webgl2:2 ; WebGPU:3
|
||||||
|
},
|
||||||
|
// 版权信息
|
||||||
|
creditContainer: document.createElement('div')
|
||||||
|
}
|
||||||
|
|
||||||
|
const initMap = () => {
|
||||||
|
viewer = new Cesium.Viewer('cesiumContainer',cesiumConfig)
|
||||||
|
viewer._cesiumWidget._creditContainer.style.display = 'none'
|
||||||
|
viewer.scene.sun.show = false
|
||||||
|
viewer.scene.moon.show = false
|
||||||
|
viewer.scene.fog.enabled = false
|
||||||
|
viewer.scene.skyAtmosphere.show = false
|
||||||
|
viewer.scene.sun.show = false
|
||||||
|
viewer.scene.skyBox.show = false
|
||||||
|
viewer.scene.globe.enableLighting = false
|
||||||
|
viewer.shadowMap.darkness = 0.8
|
||||||
|
viewer.scene._sunBloom = false
|
||||||
|
viewer.scene.globe.showGroundAtmosphere = false
|
||||||
|
viewer.scene.postProcessStages.fxaa.enabled = true
|
||||||
|
viewer.camera.setView({
|
||||||
|
// 指定相机初始位置
|
||||||
|
destination: Cesium.Cartesian3.fromDegrees(112, 18, 4000000)
|
||||||
|
});
|
||||||
|
// 改写 Cesium 原生鼠标滚轮事件,地图缩放改为按下 Ctrl + 鼠标滚轮
|
||||||
|
modifyCesiumMouseWheel(viewer);
|
||||||
|
|
||||||
|
// 沉船摄影拼接图
|
||||||
|
const shipline = new Cesium.WebMapTileServiceImageryProvider(
|
||||||
|
{
|
||||||
|
url: CesiumHostAddr.value + 'geoserver/gwc/service/wmts/rest/ougp:正射影像图_GM输出_20240516/{TileMatrixSet}/{TileMatrixSet}:{TileMatrix}/{TileRow}/{TileCol}?format=image/png',
|
||||||
|
layer: 'ougp:正射影像图_GM输出_20240516',
|
||||||
|
style: 'default',
|
||||||
|
format: 'image/png',
|
||||||
|
tileMatrixSetID: 'EPSG:4326',
|
||||||
|
tilingScheme: new Cesium.GeographicTilingScheme()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
viewer.imageryLayers.addImageryProvider(shipline)
|
||||||
|
// 增加6000米等深线
|
||||||
|
const contour6000 = new Cesium.WebMapTileServiceImageryProvider(
|
||||||
|
{
|
||||||
|
url: CesiumHostAddr.value + 'geoserver/gwc/service/wmts/rest/ougp:contour6000/{TileMatrixSet}/{TileMatrixSet}:{TileMatrix}/{TileRow}/{TileCol}?format=image/png',
|
||||||
|
layer: 'ougp:contour6000',
|
||||||
|
style: 'default',
|
||||||
|
format: 'image/png',
|
||||||
|
tileMatrixSetID: 'EPSG:4326',
|
||||||
|
tilingScheme: new Cesium.GeographicTilingScheme()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
viewer.imageryLayers.addImageryProvider(contour6000)
|
||||||
|
|
||||||
|
const point_options = {
|
||||||
|
show: true, // 是否展示
|
||||||
|
pixelSize: 10, // 点的大小
|
||||||
|
color: Cesium.Color.RED, // 颜色
|
||||||
|
outlineColor: Cesium.Color.SKYBLUE, // 边框颜色
|
||||||
|
outlineWidth: 2 // 边框宽度
|
||||||
|
};
|
||||||
|
|
||||||
|
viewer.entities.add({
|
||||||
|
position: Cesium.Cartesian3.fromDegrees(113.27, 18.13),
|
||||||
|
point: point_options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getParamsList()
|
||||||
|
getDataList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
height: 820px;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
width: 450px;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 30px 30px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.fold {
|
||||||
|
position: absolute;
|
||||||
|
top: 350px;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-text {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
|
||||||
|
.input-bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
:deep(.el-input-group__append) {
|
||||||
|
background-color: #409eff;
|
||||||
|
color: white;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-bar {
|
||||||
|
margin-top: 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
margin-top: 10px;
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
color: #333;
|
||||||
|
height: 620px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
-ms-overflow-style: none; /* IE/Edge */
|
||||||
|
|
||||||
|
.item {
|
||||||
|
padding: 30px 0;
|
||||||
|
border-bottom: 1px solid #dddddd;
|
||||||
|
|
||||||
|
&:hover .name {
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
padding-bottom: 5px;
|
||||||
|
color: #000;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
padding: 5px 0;
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
.col {
|
||||||
|
flex: 1;
|
||||||
|
color: #999;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.map {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.unfold {
|
||||||
|
position: absolute;
|
||||||
|
top: 350px;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
margin: 10px 0 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,325 @@
|
||||||
|
<template>
|
||||||
|
<div class="task-list">
|
||||||
|
<div class="search-bar">
|
||||||
|
<div class="left">
|
||||||
|
<el-input
|
||||||
|
v-model="searchQuery"
|
||||||
|
placeholder="请输入任务关键词"
|
||||||
|
class="search-input"
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon><Search /></el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
|
||||||
|
<el-select v-model="rescueType" placeholder="请选择" class="filter-select" size="large">
|
||||||
|
<template #prefix>任务类型:</template>
|
||||||
|
<el-option label="全部" value="" />
|
||||||
|
<el-option label="应急搜捞" value="SAR" />
|
||||||
|
<el-option label="专项调查" value="SI" />
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<el-select v-model="rescueResponseLevel" placeholder="请选择" class="filter-select" size="large">
|
||||||
|
<template #prefix>快速响应等级:</template>
|
||||||
|
<el-option label="全部" value="" />
|
||||||
|
<el-option label="一级" value="一级" />
|
||||||
|
<el-option label="二级" value="二级" />
|
||||||
|
<el-option label="三级" value="三级" />
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<el-select v-model="rescueSecrecy" placeholder="请选择" class="filter-select" size="large">
|
||||||
|
<template #prefix>是否涉密:</template>
|
||||||
|
<el-option label="全部" value="" />
|
||||||
|
<el-option label="是" value="是" />
|
||||||
|
<el-option label="否" value="否" />
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<el-button size="large" :icon="RefreshLeft" @click="handleReset">重置</el-button>
|
||||||
|
<el-button size="large" :icon="Search" type="primary" @click="handleSearch" style="margin-left: 0;">检索</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<el-button size="large" type="success" @click="handleInput">任务录入</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table">
|
||||||
|
<div class="row" v-for="(item,index) in dataList" :key="index" @click="onDetail(item.tsy_id)">
|
||||||
|
<div class="info">
|
||||||
|
<div class="top">
|
||||||
|
<div class="tag" :class="{tag2: item.rescue_type === 'SI'}">{{ item.rescue_type === 'SAR' ? '应急搜捞' : '专项调查' }}</div>
|
||||||
|
<div class="code">{{ item.rescue_code }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="bottom">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="2">
|
||||||
|
<div class="label">状态:</div>
|
||||||
|
<div class="value">{{ item.rescue_status }}</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="3">
|
||||||
|
<div class="label">搜寻处置目标:</div>
|
||||||
|
<div class="value">{{ item.rescue_target_name }}</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="3">
|
||||||
|
<div class="label">快速响应等级:</div>
|
||||||
|
<div class="value">{{ item.rescue_response_level }}</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="2">
|
||||||
|
<div class="label">是否涉密:</div>
|
||||||
|
<div class="value">{{ item.rescue_secrecy }}</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="2">
|
||||||
|
<div class="label">联系人:</div>
|
||||||
|
<div class="value">{{ item.fill_person }}</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="3">
|
||||||
|
<div class="label">填报时间:</div>
|
||||||
|
<div class="value">{{ item.fill_time }}</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="3">
|
||||||
|
<div class="label">搜寻处置目标位置:</div>
|
||||||
|
<div class="value">{{ item.rescue_target_lat }},{{ item.rescue_target_lon }}</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="3">
|
||||||
|
<div class="label">建议搜寻区域范围:</div>
|
||||||
|
<div class="value">{{ item.rescue_target_radius }}</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="3">
|
||||||
|
<div class="label">搜寻处置目标价值:</div>
|
||||||
|
<div class="value">{{ item.rescue_target_price }}</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<el-icon><ArrowRightBold /></el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pagination">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="currentPage"
|
||||||
|
v-model:page-size="pageSize"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
:total="total"
|
||||||
|
background
|
||||||
|
@size-change="handleSizeChange"
|
||||||
|
@current-change="handleCurrentChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { Search, RefreshLeft } from '@element-plus/icons-vue'
|
||||||
|
import { regPage } from '@/api/task.js'
|
||||||
|
|
||||||
|
interface TaskItem {
|
||||||
|
tsy_id: number
|
||||||
|
rescue_type: string
|
||||||
|
rescue_code: string
|
||||||
|
rescue_status: string
|
||||||
|
rescue_target_name: string
|
||||||
|
rescue_response_level: string
|
||||||
|
rescue_secrecy: string
|
||||||
|
fill_person: string
|
||||||
|
fill_time: string
|
||||||
|
rescue_target_lat: string
|
||||||
|
rescue_target_lon: string
|
||||||
|
rescue_target_radius: string
|
||||||
|
rescue_target_price: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const searchQuery = ref('')
|
||||||
|
const rescueType = ref('')
|
||||||
|
const rescueResponseLevel = ref('')
|
||||||
|
const rescueSecrecy = ref('')
|
||||||
|
const currentPage = ref(1)
|
||||||
|
const pageSize = ref(10)
|
||||||
|
const start = ref(0)
|
||||||
|
const total = ref(0)
|
||||||
|
|
||||||
|
const dataList = ref<TaskItem[]>([])
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
start.value = 0
|
||||||
|
currentPage.value = 1
|
||||||
|
getDataList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
searchQuery.value = ''
|
||||||
|
rescueType.value = ''
|
||||||
|
rescueResponseLevel.value = ''
|
||||||
|
rescueSecrecy.value = ''
|
||||||
|
start.value = 0
|
||||||
|
currentPage.value = 1
|
||||||
|
getDataList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleInput = () => {
|
||||||
|
router.push('/input')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSizeChange = (val: number) => {
|
||||||
|
pageSize.value = val
|
||||||
|
start.value = 0
|
||||||
|
currentPage.value = 1
|
||||||
|
getDataList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCurrentChange = (val: number) => {
|
||||||
|
currentPage.value = val
|
||||||
|
start.value = pageSize.value * (val -1)
|
||||||
|
getDataList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取列表数据
|
||||||
|
const getDataList = () => {
|
||||||
|
;(regPage({
|
||||||
|
start: start.value,
|
||||||
|
limit: pageSize.value,
|
||||||
|
query: searchQuery.value,
|
||||||
|
rescue_type: rescueType.value,
|
||||||
|
rescue_response_level: rescueResponseLevel.value,
|
||||||
|
rescue_secrecy: rescueSecrecy.value,
|
||||||
|
}) as unknown as Promise<{ page: { records: TaskItem[]; total: number } }>).then((res) => {
|
||||||
|
dataList.value = res.page.records
|
||||||
|
total.value = res.page.total
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳转任务详情页
|
||||||
|
const onDetail = (id: number) => {
|
||||||
|
router.push(`/input/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getDataList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.task-list {
|
||||||
|
padding: 20px 8%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-height: calc(100vh - 220px);
|
||||||
|
background-color: rgb(241,244,251);
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-select {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
color: #242424;
|
||||||
|
|
||||||
|
.row {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* 阴影效果 */
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
border: 2px solid rgb(18,150,219);
|
||||||
|
|
||||||
|
.right {
|
||||||
|
border: 2px solid transparent;
|
||||||
|
background-color: rgb(0,160,243);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.top {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
margin-right: 12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 0 8px;
|
||||||
|
color: #fdbb13;
|
||||||
|
border: 2px solid #fdbb13;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag2 {
|
||||||
|
color: rgb(18,150,219);
|
||||||
|
border: 2px solid rgb(18,150,219);
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
color: #000;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #242424;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid rgb(0,160,243);
|
||||||
|
color: rgb(0,160,243);
|
||||||
|
font-size: 26px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
margin: 50px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,279 @@
|
||||||
|
<template>
|
||||||
|
<div class="resource-panel">
|
||||||
|
<div class="toolbar" v-if="showToolbar">
|
||||||
|
<el-button type="primary" :icon="Upload" @click="openUploadDialog">上传文件</el-button>
|
||||||
|
<el-button type="success" :icon="Link" @click="openCloudDialog">指定云资源路径</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-table :data="fileData" border style="width: 100%">
|
||||||
|
<el-table-column prop="file_name" label="资源名称" min-width="180" />
|
||||||
|
<el-table-column prop="source_type" label="来源类型" width="110">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag :type="scope.row.source_type === 'cloud' ? 'success' : 'primary'" size="small">
|
||||||
|
{{ scope.row.source_type === 'cloud' ? '云资源' : '本地上传' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="file_path" label="路径" min-width="260" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="file_size" label="文件大小" width="120">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ formatFileSize(scope.row.file_size) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="create_time" label="添加时间" width="170" />
|
||||||
|
<el-table-column label="操作" width="100" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-popconfirm
|
||||||
|
confirm-button-text="确定"
|
||||||
|
cancel-button-text="取消"
|
||||||
|
title="是否确认删除?"
|
||||||
|
@confirm="handleDelete(scope.row.id)"
|
||||||
|
>
|
||||||
|
<template #reference>
|
||||||
|
<el-button link type="danger" size="small">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-popconfirm>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<el-empty v-if="!fileData.length" description="暂无数据资源,请上传文件或指定云资源路径" />
|
||||||
|
|
||||||
|
<!-- 上传文件弹窗 -->
|
||||||
|
<el-dialog v-model="uploadDialogVisible" title="上传文件" width="520" destroy-on-close>
|
||||||
|
<el-form label-width="90px">
|
||||||
|
<el-form-item label="选择文件">
|
||||||
|
<el-upload
|
||||||
|
ref="uploadRef"
|
||||||
|
drag
|
||||||
|
:auto-upload="false"
|
||||||
|
:limit="1"
|
||||||
|
:on-change="handleFileChange"
|
||||||
|
:on-remove="handleFileRemove"
|
||||||
|
>
|
||||||
|
<el-icon class="upload-icon"><Upload /></el-icon>
|
||||||
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||||
|
<template #tip>
|
||||||
|
<div class="el-upload__tip">支持 raw、sgy、nc、csv 等地球物理数据格式</div>
|
||||||
|
</template>
|
||||||
|
</el-upload>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="备注">
|
||||||
|
<el-input v-model="uploadRemark" type="textarea" :rows="2" placeholder="可选,填写文件说明" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="uploadDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="uploadLoading" @click="confirmUpload">确认上传</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 云资源路径弹窗 -->
|
||||||
|
<el-dialog v-model="cloudDialogVisible" title="指定云资源路径" width="560" destroy-on-close>
|
||||||
|
<el-form ref="cloudFormRef" :model="cloudForm" :rules="cloudRules" label-width="110px">
|
||||||
|
<el-form-item label="云资源路径" prop="cloud_path">
|
||||||
|
<el-input
|
||||||
|
v-model="cloudForm.cloud_path"
|
||||||
|
placeholder="例如:s3://geodata-bucket/seismic/2024/data.sgy"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="资源名称" prop="resource_name">
|
||||||
|
<el-input v-model="cloudForm.resource_name" placeholder="可选,默认取路径末尾文件名" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="存储类型">
|
||||||
|
<el-select v-model="cloudForm.storage_type" style="width: 100%">
|
||||||
|
<el-option label="对象存储 (S3/OSS)" value="object" />
|
||||||
|
<el-option label="NAS 共享目录" value="nas" />
|
||||||
|
<el-option label="HDFS" value="hdfs" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="说明">
|
||||||
|
<el-input v-model="cloudForm.remark" type="textarea" :rows="2" placeholder="可选,填写云资源说明" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="cloudDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="cloudLoading" @click="confirmCloudPath">确认添加</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, watch, onMounted } from 'vue'
|
||||||
|
import { Upload, Link } from '@element-plus/icons-vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { dataFileList, dataFileUpload, dataCloudPathAdd, dataFileDelete } from '@/api/data.js'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
dataId: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
showToolbar: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['updated'])
|
||||||
|
|
||||||
|
const fileData = ref([])
|
||||||
|
const uploadDialogVisible = ref(false)
|
||||||
|
const cloudDialogVisible = ref(false)
|
||||||
|
const uploadLoading = ref(false)
|
||||||
|
const cloudLoading = ref(false)
|
||||||
|
const uploadRemark = ref('')
|
||||||
|
const selectedFile = ref(null)
|
||||||
|
const cloudFormRef = ref(null)
|
||||||
|
|
||||||
|
const cloudForm = reactive({
|
||||||
|
cloud_path: '',
|
||||||
|
resource_name: '',
|
||||||
|
storage_type: 'object',
|
||||||
|
remark: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const cloudRules = {
|
||||||
|
cloud_path: [{ required: true, message: '请输入云资源路径', trigger: 'blur' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化文件大小
|
||||||
|
* @param {number} bytes
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
const formatFileSize = (bytes) => {
|
||||||
|
if (!bytes) return '—'
|
||||||
|
const k = 1024
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||||
|
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadFileList = () => {
|
||||||
|
if (!props.dataId) {
|
||||||
|
fileData.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dataFileList({ data_id: props.dataId }).then((res) => {
|
||||||
|
fileData.value = res.array || []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const ensureDataId = () => {
|
||||||
|
if (!props.dataId) {
|
||||||
|
ElMessage.warning('请先保存数据基本信息')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const openUploadDialog = () => {
|
||||||
|
if (!ensureDataId()) return
|
||||||
|
selectedFile.value = null
|
||||||
|
uploadRemark.value = ''
|
||||||
|
uploadDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const openCloudDialog = () => {
|
||||||
|
if (!ensureDataId()) return
|
||||||
|
cloudForm.cloud_path = ''
|
||||||
|
cloudForm.resource_name = ''
|
||||||
|
cloudForm.storage_type = 'object'
|
||||||
|
cloudForm.remark = ''
|
||||||
|
cloudDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFileChange = (file) => {
|
||||||
|
selectedFile.value = file.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFileRemove = () => {
|
||||||
|
selectedFile.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmUpload = () => {
|
||||||
|
if (!selectedFile.value) {
|
||||||
|
ElMessage.warning('请选择要上传的文件')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uploadLoading.value = true
|
||||||
|
dataFileUpload({
|
||||||
|
data_id: props.dataId,
|
||||||
|
file_name: selectedFile.value.name,
|
||||||
|
file_size: selectedFile.value.size
|
||||||
|
}).then((res) => {
|
||||||
|
uploadLoading.value = false
|
||||||
|
if (res.success) {
|
||||||
|
ElMessage.success('文件上传成功')
|
||||||
|
uploadDialogVisible.value = false
|
||||||
|
loadFileList()
|
||||||
|
emit('updated')
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message || '上传失败')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmCloudPath = async () => {
|
||||||
|
if (!cloudFormRef.value) return
|
||||||
|
await cloudFormRef.value.validate((valid) => {
|
||||||
|
if (!valid) return
|
||||||
|
cloudLoading.value = true
|
||||||
|
dataCloudPathAdd({
|
||||||
|
data_id: props.dataId,
|
||||||
|
cloud_path: cloudForm.cloud_path,
|
||||||
|
resource_name: cloudForm.resource_name
|
||||||
|
}).then((res) => {
|
||||||
|
cloudLoading.value = false
|
||||||
|
if (res.success) {
|
||||||
|
ElMessage.success('云资源路径添加成功')
|
||||||
|
cloudDialogVisible.value = false
|
||||||
|
loadFileList()
|
||||||
|
emit('updated')
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message || '添加失败')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = (fileId) => {
|
||||||
|
dataFileDelete({ data_id: props.dataId, file_id: fileId }).then(() => {
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
loadFileList()
|
||||||
|
emit('updated')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.dataId, () => {
|
||||||
|
loadFileList()
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadFileList()
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
openUploadDialog,
|
||||||
|
openCloudDialog,
|
||||||
|
loadFileList
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.resource-panel {
|
||||||
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
color: #909399;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="upload-btn">
|
||||||
|
<el-upload
|
||||||
|
v-model:file-list="fileList"
|
||||||
|
class="upload-demo"
|
||||||
|
:action="uploadUrl"
|
||||||
|
:show-file-list="false"
|
||||||
|
:data="{
|
||||||
|
rescue_id: props.rescueId
|
||||||
|
}"
|
||||||
|
:on-success="onSuccess"
|
||||||
|
>
|
||||||
|
<el-button type="primary">上传附件</el-button>
|
||||||
|
</el-upload>
|
||||||
|
</div>
|
||||||
|
<el-table :data="fileData" border style="width: 100%">
|
||||||
|
<el-table-column property="file_org_name" label="文件名称" />
|
||||||
|
<el-table-column property="file_length" label="文件大小" width="150">
|
||||||
|
<template #default="scope">
|
||||||
|
<span class="file-size">{{ formatFileSize(scope.row.file_length) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="150" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button link type="primary" size="small" @click="handleDownload(scope.row.tsy_id)">下载</el-button>
|
||||||
|
<el-popconfirm
|
||||||
|
confirm-button-text="确定"
|
||||||
|
cancel-button-text="取消"
|
||||||
|
:icon="InfoFilled"
|
||||||
|
icon-color="#626AEF"
|
||||||
|
title="是否确认删除?"
|
||||||
|
@confirm="handleDelete(scope.row.tsy_id)"
|
||||||
|
>
|
||||||
|
<template #reference>
|
||||||
|
<el-button link type="primary" size="small">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-popconfirm>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import {regFileDelete, regFileList} from "@/api/task.js"
|
||||||
|
import {InfoFilled} from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
const uploadUrl = import.meta.env.VITE_APP_BASE_API + '/rs/rescue/reg/file/upload.htm'
|
||||||
|
|
||||||
|
const fileList = ref([])
|
||||||
|
const fileData = ref([])
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
rescueId: {
|
||||||
|
type: String, // 根据实际情况定义类型
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 查看附件列表
|
||||||
|
const getFileList = () => {
|
||||||
|
regFileList({rescue_id: props.rescueId}).then(res => {
|
||||||
|
fileData.value = res.array
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化文件大小
|
||||||
|
const formatFileSize = (bytes) => {
|
||||||
|
if (bytes === 0) return '0 B';
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 文件上传成功回调
|
||||||
|
const onSuccess = () => {
|
||||||
|
regFileList({rescue_id: props.rescueId}).then(res => {
|
||||||
|
fileData.value = res.array
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件下载
|
||||||
|
const handleDownload = async (fileId) => {
|
||||||
|
window.open(`${import.meta.env.VITE_APP_BASE_API}/rs/rescue/reg/file/download.htm?file_id=${fileId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件删除
|
||||||
|
const handleDelete = (fileId) => {
|
||||||
|
regFileDelete({
|
||||||
|
file_id: fileId
|
||||||
|
}).then(res => {
|
||||||
|
regFileList({rescue_id: props.rescueId}).then(res => {
|
||||||
|
fileData.value = res.array
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.rescueId) {
|
||||||
|
getFileList()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.upload-btn {
|
||||||
|
text-align: right;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,255 @@
|
||||||
|
<template>
|
||||||
|
<el-form
|
||||||
|
ref="ruleFormRef"
|
||||||
|
:model="ruleForm"
|
||||||
|
:rules="rules"
|
||||||
|
label-width="120px"
|
||||||
|
class="ruleForm"
|
||||||
|
:size="formSize"
|
||||||
|
status-icon
|
||||||
|
>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="数据编号" prop="data_code">
|
||||||
|
<el-input v-model="ruleForm.data_code" placeholder="如 MB-SH-2024-001" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="数据类型" prop="data_type">
|
||||||
|
<el-select v-model="ruleForm.data_type" placeholder="请选择" style="width: 100%">
|
||||||
|
<el-option
|
||||||
|
v-for="item in dataTypeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="入库状态" prop="data_status">
|
||||||
|
<el-select v-model="ruleForm.data_status" placeholder="请选择" style="width: 100%">
|
||||||
|
<el-option label="待审核" value="待审核" />
|
||||||
|
<el-option label="处理中" value="处理中" />
|
||||||
|
<el-option label="已入库" value="已入库" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="数据名称" prop="data_name">
|
||||||
|
<el-input v-model="ruleForm.data_name" placeholder="请输入数据名称" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="航次名称" prop="voyage_name">
|
||||||
|
<el-input v-model="ruleForm.voyage_name" placeholder="请输入航次名称" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="经度" prop="data_lon">
|
||||||
|
<el-input v-model="ruleForm.data_lon" placeholder="如 113.52" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="纬度" prop="data_lat">
|
||||||
|
<el-input v-model="ruleForm.data_lat" placeholder="如 18.25" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="调查区域" prop="survey_area">
|
||||||
|
<el-input v-model="ruleForm.survey_area" placeholder="如 120 km²" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="数据量" prop="file_size">
|
||||||
|
<el-input v-model="ruleForm.file_size" placeholder="如 2.8 GB" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="是否涉密" prop="data_secrecy">
|
||||||
|
<el-select v-model="ruleForm.data_secrecy" style="width: 100%">
|
||||||
|
<el-option label="是" value="是" />
|
||||||
|
<el-option label="否" value="否" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="入库时间" prop="fill_time">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="ruleForm.fill_time"
|
||||||
|
type="datetime"
|
||||||
|
placeholder="请选择"
|
||||||
|
format="YYYY/MM/DD HH:mm:ss"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="录入人" prop="fill_person">
|
||||||
|
<el-input v-model="ruleForm.fill_person" placeholder="请输入录入人" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="联系电话" prop="fill_tel">
|
||||||
|
<el-input v-model="ruleForm.fill_tel" placeholder="请输入联系电话" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="数据来源" prop="data_source">
|
||||||
|
<el-input v-model="ruleForm.data_source" placeholder="如 船载测量系统" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="数据说明" prop="data_note">
|
||||||
|
<el-input v-model="ruleForm.data_note" type="textarea" :rows="3" placeholder="请输入数据说明、处理状态等补充信息" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<div class="btn-bar">
|
||||||
|
<el-button type="primary" @click="submitForm(ruleFormRef)">保存</el-button>
|
||||||
|
<el-button @click="resetForm">取消</el-button>
|
||||||
|
<el-button type="primary" plain :icon="Upload" @click="handleUpload">上传文件</el-button>
|
||||||
|
<el-button type="success" plain :icon="Link" @click="handleCloudPath">指定云资源路径</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref, onMounted } from 'vue'
|
||||||
|
import type { ComponentSize, FormInstance } from 'element-plus'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { Upload, Link } from '@element-plus/icons-vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { dataEdit, dataFind } from '@/api/data.js'
|
||||||
|
import { getFlatDataTypeOptions } from '@/constants/dataTypes.js'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const formSize = ref<ComponentSize>('default')
|
||||||
|
const ruleFormRef = ref<FormInstance>()
|
||||||
|
const dataTypeOptions = getFlatDataTypeOptions()
|
||||||
|
|
||||||
|
const ruleForm = ref({
|
||||||
|
id: '',
|
||||||
|
data_code: '',
|
||||||
|
data_type: '',
|
||||||
|
data_name: '',
|
||||||
|
voyage_name: '',
|
||||||
|
data_status: '待审核',
|
||||||
|
data_lon: '',
|
||||||
|
data_lat: '',
|
||||||
|
survey_area: '',
|
||||||
|
file_size: '',
|
||||||
|
data_secrecy: '否',
|
||||||
|
fill_time: '',
|
||||||
|
fill_person: '',
|
||||||
|
fill_tel: '',
|
||||||
|
data_source: '',
|
||||||
|
data_note: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules = reactive({
|
||||||
|
data_code: [{ required: true, message: '请输入数据编号', trigger: 'blur' }],
|
||||||
|
data_type: [{ required: true, message: '请选择数据类型', trigger: 'change' }],
|
||||||
|
data_name: [{ required: true, message: '请输入数据名称', trigger: 'blur' }],
|
||||||
|
voyage_name: [{ required: true, message: '请输入航次名称', trigger: 'blur' }],
|
||||||
|
data_lon: [{ required: true, message: '请输入经度', trigger: 'blur' }],
|
||||||
|
data_lat: [{ required: true, message: '请输入纬度', trigger: 'blur' }],
|
||||||
|
data_secrecy: [{ required: true, message: '请选择是否涉密', trigger: 'change' }],
|
||||||
|
fill_time: [{ required: true, message: '请选择入库时间', trigger: 'change' }],
|
||||||
|
fill_person: [{ required: true, message: '请输入录入人', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
dataId: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['saved', 'upload', 'cloud-path'])
|
||||||
|
|
||||||
|
const submitForm = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return
|
||||||
|
await formEl.validate((valid) => {
|
||||||
|
if (!valid) return
|
||||||
|
const payload = { ...ruleForm.value }
|
||||||
|
if (props.dataId) {
|
||||||
|
payload.id = String(props.dataId)
|
||||||
|
}
|
||||||
|
dataEdit(payload).then((res: { success: boolean; entity?: Record<string, unknown>; message?: string }) => {
|
||||||
|
if (res.success) {
|
||||||
|
ruleForm.value = { ...ruleForm.value, ...res.entity }
|
||||||
|
ElMessage.success('保存成功')
|
||||||
|
emit('saved', res.entity)
|
||||||
|
if (!props.dataId && res.entity?.id) {
|
||||||
|
router.replace(`/input/${res.entity.id}`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message || '保存失败')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
router.push('/search')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUpload = () => {
|
||||||
|
emit('upload')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCloudPath = () => {
|
||||||
|
emit('cloud-path')
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDetail = () => {
|
||||||
|
dataFind({ data_id: props.dataId }).then((res: { success: boolean; entity?: Record<string, unknown> }) => {
|
||||||
|
if (res.success && res.entity) {
|
||||||
|
ruleForm.value = {
|
||||||
|
...ruleForm.value,
|
||||||
|
...res.entity,
|
||||||
|
fill_tel: String(res.entity.fill_tel || ''),
|
||||||
|
data_source: String(res.entity.data_source || ''),
|
||||||
|
data_note: String(res.entity.data_note || '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.dataId) {
|
||||||
|
getDetail()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.ruleForm {
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
.btn-bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,208 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="upload-btn">
|
||||||
|
<el-button type="primary" @click="handleVoyage">关联航次</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table :data="tableData" border style="width: 100%">
|
||||||
|
<el-table-column prop="voyage_name" label="航次名称" />
|
||||||
|
<el-table-column prop="diving_sn" label="潜次号" />
|
||||||
|
<el-table-column prop="dataset_name" label="样品集名称" />
|
||||||
|
<el-table-column prop="sample_name" label="样品名称" />
|
||||||
|
<el-table-column prop="conver_area" label="覆盖面积" />
|
||||||
|
<el-table-column label="操作" width="150" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-popconfirm
|
||||||
|
confirm-button-text="确定"
|
||||||
|
cancel-button-text="取消"
|
||||||
|
:icon="InfoFilled"
|
||||||
|
icon-color="#626AEF"
|
||||||
|
title="是否确认删除?"
|
||||||
|
@confirm="handleDelete(scope.row.tsy_id)"
|
||||||
|
>
|
||||||
|
<template #reference>
|
||||||
|
<el-button link type="primary" size="small">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-popconfirm>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<el-dialog v-model="dialogFormVisible" title="关联航次" width="600">
|
||||||
|
<el-cascader
|
||||||
|
v-model="selectedValues"
|
||||||
|
:options="cascadeData"
|
||||||
|
:props="{ expandTrigger: 'hover' }"
|
||||||
|
@change="handleChange"
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="dialogFormVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="onConfirm">确定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import {getParam, sampleAdd, sampleDelete, sampleList, voyageReg} from "@/api/task.js"
|
||||||
|
import {InfoFilled} from "@element-plus/icons-vue";
|
||||||
|
import {ElMessage} from "element-plus";
|
||||||
|
|
||||||
|
const dialogFormVisible = ref(false)
|
||||||
|
const props = defineProps({
|
||||||
|
rescueId: {
|
||||||
|
type: String, // 根据实际情况定义类型
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const VoyageSocketUrl = ref('')
|
||||||
|
|
||||||
|
// 获取系统参数列表
|
||||||
|
const getParamsList = () => {
|
||||||
|
// 判断当前环境
|
||||||
|
if (import.meta.env.VITE_APP_ENV === 'development') {
|
||||||
|
// 开发环境:直接使用环境变量中的值
|
||||||
|
VoyageSocketUrl.value = import.meta.env.VITE_APP_DS_API
|
||||||
|
voyageList()
|
||||||
|
} else {
|
||||||
|
// 生产环境:通过接口获取参数
|
||||||
|
getParam().then(res => {
|
||||||
|
const data = res.array.find(item => item.param_name === 'VoyageSocketUrl')
|
||||||
|
|
||||||
|
if (data?.param_value) {
|
||||||
|
VoyageSocketUrl.value = data.param_value
|
||||||
|
} else {
|
||||||
|
// 备用方案:基于当前窗口位置构建URL[1](@ref)
|
||||||
|
VoyageSocketUrl.value = `${window.location.protocol}//${window.location.hostname}:${window.location.port}/ds/`
|
||||||
|
}
|
||||||
|
|
||||||
|
voyageList()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const entity = ref()
|
||||||
|
const allVoyage = ref()
|
||||||
|
const tableData = ref([])
|
||||||
|
// 获取所有航次列表数据
|
||||||
|
const voyageList = () => {
|
||||||
|
voyageReg({ baseURL: VoyageSocketUrl.value }).then(res => {
|
||||||
|
entity.value = res.entity
|
||||||
|
allVoyage.value = res.entity.VoyageArray
|
||||||
|
cascadeData.value = convertToCascadeData(res.entity)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询已关联航次数据
|
||||||
|
const dataList = () => {
|
||||||
|
sampleList({ rescue_id: props.rescueId }).then(res => {
|
||||||
|
tableData.value = res.array
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加
|
||||||
|
const handleVoyage = () => {
|
||||||
|
dialogFormVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
const handleDelete = (id) => {
|
||||||
|
sampleDelete({ tsy_id: id }).then(() => {
|
||||||
|
ElMessage({
|
||||||
|
message: '删除成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
dataList()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedValues = ref('')
|
||||||
|
const cascadeData = ref([])
|
||||||
|
// 处理航次数据
|
||||||
|
const convertToCascadeData = (entity) => {
|
||||||
|
const cascadeData = [];
|
||||||
|
|
||||||
|
// 处理一级数据:VoyageArray
|
||||||
|
if (entity.VoyageArray && Array.isArray(entity.VoyageArray)) {
|
||||||
|
entity.VoyageArray.forEach(voyage => {
|
||||||
|
const voyageName = voyage.voyage_name;
|
||||||
|
|
||||||
|
// 创建一级选项
|
||||||
|
const firstLevelItem = {
|
||||||
|
value: voyageName,
|
||||||
|
label: `${voyageName} (${voyage.ship_name})`,
|
||||||
|
children: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理二级数据:对应voyage_name下的数据集
|
||||||
|
if (entity[voyageName] && Array.isArray(entity[voyageName])) {
|
||||||
|
entity[voyageName].forEach(dataset => {
|
||||||
|
const secondLevelItem = {
|
||||||
|
value: dataset.dataset_id.toString(),
|
||||||
|
label: dataset.dataset_name
|
||||||
|
};
|
||||||
|
firstLevelItem.children.push(secondLevelItem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cascadeData.push(firstLevelItem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return cascadeData;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedDataset = ref('')
|
||||||
|
|
||||||
|
// 选择样品数据
|
||||||
|
const handleChange = (value) => {
|
||||||
|
if (value && value.length === 2) {
|
||||||
|
const [voyageName, datasetId] = value;
|
||||||
|
selectedDataset.value = findDatasetById(entity.value, voyageName, datasetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找原始数据获取完整对象
|
||||||
|
const findDatasetById = (entity, voyageName, datasetId) => {
|
||||||
|
if (!entity[voyageName]) return null;
|
||||||
|
|
||||||
|
return entity[voyageName].find(dataset =>
|
||||||
|
dataset.dataset_id.toString() === datasetId.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交
|
||||||
|
const onConfirm = () => {
|
||||||
|
if (selectedDataset.value) {
|
||||||
|
selectedDataset.value.rescue_id = props.rescueId
|
||||||
|
sampleAdd(selectedDataset.value).then(() => {
|
||||||
|
ElMessage({
|
||||||
|
message: '操作成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
dataList()
|
||||||
|
dialogFormVisible.value = false
|
||||||
|
selectedValues.value = ''
|
||||||
|
selectedDataset.value = null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.rescueId) {
|
||||||
|
getParamsList()
|
||||||
|
dataList()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.upload-btn {
|
||||||
|
text-align: right;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare module '*.vue' {
|
||||||
|
import type { DefineComponent } from 'vue'
|
||||||
|
const component: DefineComponent<object, object, unknown>
|
||||||
|
export default component
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '*.js' {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const mod: any
|
||||||
|
export = mod
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"],
|
||||||
|
"@components/*": ["src/components/*"]
|
||||||
|
},
|
||||||
|
"allowJs": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": false,
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "src/**/*.d.ts"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"root":["./src/main.ts","./src/vite-env.d.ts","./src/router/index.ts","./src/app.vue","./src/components/helloworld.vue","./src/components/dataspace/filetypeicon.vue","./src/components/home/icondataset.vue","./src/components/home/icongrid.vue","./src/components/home/iconmagnetic.vue","./src/components/home/iconsonar.vue","./src/components/home/iconwave.vue","./src/components/home/staticon.vue","./src/views/contact.vue","./src/views/dataimport.vue","./src/views/datasearch.vue","./src/views/home.vue","./src/views/login.vue","./src/views/statistics.vue","./src/views/taskdata.vue","./src/views/taskdemand.vue","./src/views/tasksearch.vue","./src/views/common/dataresourcepanel.vue","./src/views/common/filelist.vue","./src/views/common/formdata.vue","./src/views/common/relatedvoyage.vue"],"version":"5.9.3"}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"noEmitOnError": false, // 即使 TS 报错也继续打包
|
||||||
|
"skipLibCheck": true, // 跳过库类型检查
|
||||||
|
"strict": false // 关闭严格模式(可选)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"types": ["node"],
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": false,
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"root":["./vite.config.ts"],"version":"5.9.3"}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import cesium from 'vite-plugin-cesium';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
base: './',
|
||||||
|
plugins: [vue(),cesium()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': resolve(__dirname, 'src'),
|
||||||
|
'@components': resolve(__dirname, 'src/components'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||