添加大屏
8
datav/.editorconfig
Normal file
@@ -0,0 +1,8 @@
|
||||
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
|
||||
charset = utf-8
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
end_of_line = lf
|
||||
max_line_length = 100
|
||||
1
datav/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
30
datav/.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
55
datav/DASHBOARD_COLOR_UPDATE.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Dashboard颜色修改报告
|
||||
|
||||
## 修改概述
|
||||
本次修改将数据大屏Dashboard组件的颜色方案统一调整为基于Map3D组件的基础颜色,移除了所有渐变色,使用单一颜色方案。
|
||||
|
||||
## 颜色方案
|
||||
基于Map3D组件的以下颜色:
|
||||
|
||||
1. **主色调**: `#84acf0` - 地图顶部材质颜色
|
||||
2. **辅助色**: `#7af4ff` - 地图光源颜色
|
||||
3. **强调色**: `#00F6FF` - 养殖场标签颜色
|
||||
4. **深色**: `#123024` - 地图侧面材质颜色
|
||||
5. **背景色**: `#0c1426` - 深色背景
|
||||
|
||||
## 主要修改内容
|
||||
|
||||
### 1. 背景和容器
|
||||
- 移除了复杂的渐变背景,使用纯色 `#0c1426`
|
||||
- 简化背景装饰效果,只保留基础的径向渐变
|
||||
|
||||
### 2. 顶部标题栏
|
||||
- 标题颜色改为 `#84acf0`
|
||||
- 移除彩虹渐变文字效果
|
||||
- 边框和装饰线使用单色
|
||||
|
||||
### 3. 面板和卡片
|
||||
- 所有面板背景使用 `rgba(132, 172, 240, 0.05)`
|
||||
- 边框颜色统一为 `rgba(132, 172, 240, 0.3)`
|
||||
- 移除顶部装饰渐变线,使用单色
|
||||
|
||||
### 4. 数据展示
|
||||
- 统计数值颜色改为 `#84acf0`
|
||||
- 进度条背景使用 `rgba(18, 48, 36, 0.3)`
|
||||
- 进度条填充使用单色 `#84acf0`
|
||||
|
||||
### 5. 图表配置
|
||||
- 饼图使用四种基础颜色:`#84acf0`, `#7af4ff`, `#00F6FF`, `#123024`
|
||||
- 柱状图使用单色 `#84acf0`
|
||||
- 折线图使用 `#7af4ff` 或其他单色
|
||||
- 移除所有LinearGradient渐变效果
|
||||
|
||||
## 修改文件
|
||||
- `d:\1212\nxzhihui\website\src\components\Dashboard.vue`
|
||||
|
||||
## 验证方法
|
||||
1. 启动website项目: `cd website && npm run dev`
|
||||
2. 访问数据大屏页面
|
||||
3. 检查颜色是否与Map3D组件协调统一
|
||||
4. 确认没有渐变色残留
|
||||
|
||||
## 注意事项
|
||||
- 保持了原有的视觉层次和可读性
|
||||
- 所有交互效果和动画保持不变
|
||||
- 响应式布局保持不变
|
||||
- 只修改了颜色方案,未改变布局结构
|
||||
238
datav/README.md
Normal file
@@ -0,0 +1,238 @@
|
||||
# 宁夏智慧畜牧大数据平台 - 官网展示系统
|
||||
|
||||
## 项目概述
|
||||
|
||||
宁夏智慧畜牧大数据平台官网是一个现代化的数据可视化展示系统,基于Vue 3和Three.js技术栈构建。该系统提供3D地图展示、实时数据监控、养殖场管理和数据大屏可视化等功能,为宁夏地区的智慧畜牧业发展提供数字化支撑。
|
||||
|
||||
## 核心功能
|
||||
|
||||
### 🗺️ 3D地图可视化
|
||||
- 基于Three.js的3D地球和地图展示
|
||||
- 宁夏回族自治区地理边界可视化
|
||||
- 养殖场地理位置标注和信息展示
|
||||
- 交互式地图操作(缩放、旋转、点击)
|
||||
|
||||
### 📊 数据大屏展示
|
||||
- 实时数据监控大屏
|
||||
- 多维度数据图表展示
|
||||
- 养殖业态势感知
|
||||
- 预警信息集中展示
|
||||
|
||||
### 🏭 养殖场管理
|
||||
- 养殖场基本信息展示
|
||||
- 养殖规模和类型统计
|
||||
- 联系方式和建立时间管理
|
||||
- 点击弹窗详情查看
|
||||
|
||||
### 🎨 现代化UI设计
|
||||
- 科技感十足的深色主题
|
||||
- 流畅的动画效果
|
||||
- 响应式布局设计
|
||||
- 多页面导航系统
|
||||
|
||||
## 技术栈
|
||||
|
||||
### 前端框架
|
||||
- **Vue 3** - 渐进式JavaScript框架
|
||||
- **Vite** - 现代化构建工具
|
||||
- **Vue Router** - 官方路由管理器
|
||||
- **Pinia** - 状态管理库
|
||||
|
||||
### 3D可视化
|
||||
- **Three.js** - 3D图形库
|
||||
- **CSS2DRenderer** - 2D标签渲染器
|
||||
- **WebGL** - 硬件加速图形渲染
|
||||
|
||||
### 数据可视化
|
||||
- **ECharts** - 数据图表库
|
||||
- **Vue-ECharts** - Vue集成组件
|
||||
- **@jiaminghi/data-view** - 数据大屏组件
|
||||
|
||||
### 地图服务
|
||||
- **Mapbox GL** - 地图渲染引擎
|
||||
- **Cesium** - 3D地球可视化
|
||||
- **GeoJSON** - 地理数据格式
|
||||
|
||||
### 开发工具
|
||||
- **ESLint** - 代码质量检查
|
||||
- **Oxlint** - 高性能代码检查
|
||||
- **TypeScript** - 类型定义支持
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
website/
|
||||
├── public/ # 静态资源
|
||||
│ ├── data/ # 地图数据文件
|
||||
│ └── texture/ # 纹理贴图
|
||||
├── src/
|
||||
│ ├── components/ # Vue组件
|
||||
│ │ ├── Map3D.vue # 3D地图主组件
|
||||
│ │ ├── Home.vue # 首页组件
|
||||
│ │ ├── Alert.vue # 预警监测组件
|
||||
│ │ └── FarmPopup.vue # 养殖场弹窗组件
|
||||
│ ├── hooks/ # 组合式API钩子
|
||||
│ │ ├── useCoord.js # 坐标转换
|
||||
│ │ ├── useFileLoader.js # 文件加载
|
||||
│ │ └── useCSS2DRender.js # 2D渲染
|
||||
│ ├── utils/ # 工具函数
|
||||
│ ├── assets/ # 静态资源
|
||||
│ ├── App.vue # 根组件
|
||||
│ └── main.js # 应用入口
|
||||
├── index.html # HTML模板
|
||||
├── package.json # 项目配置
|
||||
├── vite.config.js # Vite配置
|
||||
└── README.md # 项目文档
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 环境要求
|
||||
- Node.js >= 20.19.0 或 >= 22.12.0
|
||||
- npm 或 yarn 包管理器
|
||||
|
||||
### 安装依赖
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### 开发环境运行
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
访问 http://localhost:5173 查看应用
|
||||
|
||||
### 生产环境构建
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
### 预览构建结果
|
||||
```bash
|
||||
npm run preview
|
||||
```
|
||||
|
||||
### 代码质量检查
|
||||
```bash
|
||||
# 运行所有代码检查
|
||||
npm run lint
|
||||
|
||||
# 仅运行ESLint检查
|
||||
npm run lint:eslint
|
||||
|
||||
# 仅运行Oxlint检查
|
||||
npm run lint:oxlint
|
||||
```
|
||||
|
||||
## 主要特性
|
||||
|
||||
### 3D地图交互
|
||||
- 鼠标拖拽旋转地球
|
||||
- 滚轮缩放地图视角
|
||||
- 点击养殖场标记查看详情
|
||||
- 平滑的相机动画过渡
|
||||
|
||||
### 数据展示
|
||||
- 实时养殖场数据更新
|
||||
- 多种图表类型支持
|
||||
- 响应式数据大屏
|
||||
- 自定义主题配置
|
||||
|
||||
### 用户体验
|
||||
- 流畅的页面切换动画
|
||||
- 现代化的UI设计
|
||||
- 移动端适配支持
|
||||
- 无障碍访问优化
|
||||
|
||||
## 开发指南
|
||||
|
||||
### 添加新的养殖场数据
|
||||
在 `src/components/Map3D.vue` 文件中的 `farmData` 数组中添加新的养殖场信息:
|
||||
|
||||
```javascript
|
||||
const farmData = [
|
||||
{
|
||||
id: 6,
|
||||
name: '新养殖场',
|
||||
position: [106.2081, 38.4681], // [经度, 纬度]
|
||||
livestock: 15000,
|
||||
area: '800亩',
|
||||
type: '肉羊养殖',
|
||||
established: '2023年',
|
||||
contact: '张经理 138****1234'
|
||||
}
|
||||
];
|
||||
```
|
||||
|
||||
### 自定义地图样式
|
||||
修改 `src/components/Map3D.vue` 中的材质和颜色配置:
|
||||
|
||||
```javascript
|
||||
// 修改地图边界线颜色
|
||||
const lineMaterial = new THREE.LineBasicMaterial({
|
||||
color: 0x00ffff, // 青色
|
||||
linewidth: 2
|
||||
});
|
||||
|
||||
// 修改养殖场标记颜色
|
||||
const markerMaterial = new THREE.MeshBasicMaterial({
|
||||
color: 0xff6b35 // 橙色
|
||||
});
|
||||
```
|
||||
|
||||
### 添加新的页面
|
||||
1. 在 `src/components/` 目录下创建新的Vue组件
|
||||
2. 在 `src/App.vue` 中注册组件和导航
|
||||
3. 更新页面组件映射和导航菜单
|
||||
|
||||
## 部署说明
|
||||
|
||||
### 静态部署
|
||||
构建完成后,将 `dist` 目录部署到任何静态文件服务器即可。
|
||||
|
||||
### Nginx配置示例
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name your-domain.com;
|
||||
root /path/to/dist;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 贡献指南
|
||||
|
||||
1. Fork 项目仓库
|
||||
2. 创建功能分支 (`git checkout -b feature/AmazingFeature`)
|
||||
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
|
||||
4. 推送到分支 (`git push origin feature/AmazingFeature`)
|
||||
5. 创建 Pull Request
|
||||
|
||||
## 许可证
|
||||
|
||||
本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情。
|
||||
|
||||
## 联系方式
|
||||
|
||||
- 项目维护者:开发团队
|
||||
- 邮箱:dev@example.com
|
||||
- 项目地址:https://github.com/your-org/nx-smart-livestock
|
||||
|
||||
## 更新日志
|
||||
|
||||
### v1.0.0 (2025-01-18)
|
||||
- ✨ 初始版本发布
|
||||
- 🗺️ 3D地图可视化功能
|
||||
- 📊 数据大屏展示
|
||||
- 🏭 养殖场管理系统
|
||||
- 🎨 现代化UI设计
|
||||
- 📱 响应式布局支持
|
||||
26
datav/eslint.config.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||
import globals from 'globals'
|
||||
import js from '@eslint/js'
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
import pluginOxlint from 'eslint-plugin-oxlint'
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
name: 'app/files-to-lint',
|
||||
files: ['**/*.{js,mjs,jsx,vue}'],
|
||||
},
|
||||
|
||||
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
|
||||
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
js.configs.recommended,
|
||||
...pluginVue.configs['flat/essential'],
|
||||
...pluginOxlint.configs['flat/recommended'],
|
||||
])
|
||||
13
datav/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.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.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
8
datav/jsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
5417
datav/package-lock.json
generated
Normal file
42
datav/package.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "nx",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"lint:oxlint": "oxlint . --fix -D correctness --ignore-path .gitignore",
|
||||
"lint:eslint": "eslint . --fix",
|
||||
"lint": "run-s lint:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jiaminghi/data-view": "^2.10.0",
|
||||
"@types/three": "^0.179.0",
|
||||
"axios": "^1.11.0",
|
||||
"cesium": "^1.133.0",
|
||||
"echarts": "^5.6.0",
|
||||
"mapbox-gl": "^3.14.0",
|
||||
"pinia": "^3.0.3",
|
||||
"three": "^0.179.1",
|
||||
"vue": "^3.5.18",
|
||||
"vue-echarts": "^7.0.3",
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.31.0",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"eslint": "^9.31.0",
|
||||
"eslint-plugin-oxlint": "~1.8.0",
|
||||
"eslint-plugin-vue": "~10.3.0",
|
||||
"globals": "^16.3.0",
|
||||
"npm-run-all2": "^8.0.4",
|
||||
"oxlint": "~1.8.0",
|
||||
"vite": "^7.0.6",
|
||||
"vite-plugin-vue-devtools": "^8.0.0"
|
||||
}
|
||||
}
|
||||
BIN
datav/public/data/map/1.png
Normal file
|
After Width: | Height: | Size: 482 KiB |
BIN
datav/public/data/map/bg.jpg
Normal file
|
After Width: | Height: | Size: 452 KiB |
BIN
datav/public/data/map/circle-point.png
Normal file
|
After Width: | Height: | Size: 470 KiB |
BIN
datav/public/data/map/dbg.webp
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
datav/public/data/map/gz-map-fx.jpg
Normal file
|
After Width: | Height: | Size: 806 KiB |
BIN
datav/public/data/map/gz-map.jpg
Normal file
|
After Width: | Height: | Size: 3.6 MiB |
BIN
datav/public/data/map/rotating-point2.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
datav/public/data/map/rotatingAperture.png
Normal file
|
After Width: | Height: | Size: 277 KiB |
BIN
datav/public/data/map/sc-map.jpg
Normal file
|
After Width: | Height: | Size: 450 KiB |
BIN
datav/public/data/map/scene-bg2.png
Normal file
|
After Width: | Height: | Size: 708 KiB |
BIN
datav/public/data/map/sidebg.png
Normal file
|
After Width: | Height: | Size: 489 KiB |
BIN
datav/public/data/map/上升粒子1.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
1
datav/public/data/map/中华人民共和国.json
Normal file
1
datav/public/data/map/宁夏回族自治区.json
Normal file
BIN
datav/public/data/map/牛.png
Normal file
|
After Width: | Height: | Size: 200 KiB |
BIN
datav/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
32
datav/public/texture/earth.svg
Normal file
@@ -0,0 +1,32 @@
|
||||
<svg width="512" height="256" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<radialGradient id="earthGradient" cx="50%" cy="30%" r="70%">
|
||||
<stop offset="0%" style="stop-color:#4a90e2;stop-opacity:1" />
|
||||
<stop offset="30%" style="stop-color:#2c5aa0;stop-opacity:1" />
|
||||
<stop offset="70%" style="stop-color:#1a365d;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#0f2027;stop-opacity:1" />
|
||||
</radialGradient>
|
||||
<pattern id="continents" patternUnits="userSpaceOnUse" width="512" height="256">
|
||||
<rect width="512" height="256" fill="url(#earthGradient)"/>
|
||||
<!-- 简化的大陆轮廓 -->
|
||||
<path d="M50 80 Q100 60 150 80 Q200 100 250 80 Q300 60 350 80 Q400 100 450 80"
|
||||
stroke="#2d5016" stroke-width="3" fill="none" opacity="0.6"/>
|
||||
<path d="M30 120 Q80 100 130 120 Q180 140 230 120 Q280 100 330 120 Q380 140 430 120"
|
||||
stroke="#2d5016" stroke-width="2" fill="none" opacity="0.4"/>
|
||||
<path d="M70 160 Q120 140 170 160 Q220 180 270 160 Q320 140 370 160 Q420 180 470 160"
|
||||
stroke="#2d5016" stroke-width="2" fill="none" opacity="0.4"/>
|
||||
<!-- 添加一些点状细节 -->
|
||||
<circle cx="100" cy="90" r="2" fill="#2d5016" opacity="0.3"/>
|
||||
<circle cx="200" cy="110" r="1.5" fill="#2d5016" opacity="0.3"/>
|
||||
<circle cx="300" cy="130" r="2" fill="#2d5016" opacity="0.3"/>
|
||||
<circle cx="400" cy="100" r="1.5" fill="#2d5016" opacity="0.3"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect width="512" height="256" fill="url(#continents)"/>
|
||||
|
||||
<!-- 添加一些云层效果 -->
|
||||
<ellipse cx="150" cy="60" rx="40" ry="15" fill="white" opacity="0.1"/>
|
||||
<ellipse cx="350" cy="80" rx="50" ry="20" fill="white" opacity="0.1"/>
|
||||
<ellipse cx="250" cy="180" rx="45" ry="18" fill="white" opacity="0.1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
BIN
datav/public/texture/光柱.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
33
datav/public/texture/光柱.svg
Normal file
@@ -0,0 +1,33 @@
|
||||
<svg width="32" height="256" viewBox="0 0 32 256" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="beamGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#00ffff;stop-opacity:0" />
|
||||
<stop offset="10%" style="stop-color:#00ffff;stop-opacity:0.8" />
|
||||
<stop offset="50%" style="stop-color:#0088ff;stop-opacity:1" />
|
||||
<stop offset="90%" style="stop-color:#00ffff;stop-opacity:0.8" />
|
||||
<stop offset="100%" style="stop-color:#00ffff;stop-opacity:0" />
|
||||
</linearGradient>
|
||||
<radialGradient id="beamRadial" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" style="stop-color:#ffffff;stop-opacity:0.8" />
|
||||
<stop offset="30%" style="stop-color:#00ffff;stop-opacity:0.6" />
|
||||
<stop offset="70%" style="stop-color:#0088ff;stop-opacity:0.4" />
|
||||
<stop offset="100%" style="stop-color:#004488;stop-opacity:0" />
|
||||
</radialGradient>
|
||||
<filter id="beamGlow">
|
||||
<feGaussianBlur stdDeviation="2" result="coloredBlur"/>
|
||||
<feMerge>
|
||||
<feMergeNode in="coloredBlur"/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- 主光柱 -->
|
||||
<rect x="8" y="0" width="16" height="256" fill="url(#beamGradient)" filter="url(#beamGlow)"/>
|
||||
|
||||
<!-- 中心亮线 -->
|
||||
<rect x="14" y="0" width="4" height="256" fill="url(#beamRadial)" opacity="0.8"/>
|
||||
|
||||
<!-- 外发光效果 -->
|
||||
<rect x="4" y="0" width="24" height="256" fill="url(#beamGradient)" opacity="0.3" filter="url(#beamGlow)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
44
datav/public/texture/地形法线.svg
Normal file
@@ -0,0 +1,44 @@
|
||||
<svg width="256" height="256" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="normalGradient1" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#8080ff;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#8080c0;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#8080a0;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="normalGradient2" x1="100%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#ff8080;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#c08080;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#a08080;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<radialGradient id="normalRadial" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" style="stop-color:#8080ff;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#8080c0;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#808080;stop-opacity:1" />
|
||||
</radialGradient>
|
||||
<filter id="normalNoise">
|
||||
<feTurbulence baseFrequency="0.05" numOctaves="3" result="noise"/>
|
||||
<feColorMatrix in="noise" type="matrix" values="0.5 0 0 0 0.5 0 0.5 0 0 0.5 0 0 0.5 0 0.5 0 0 0 1 0"/>
|
||||
<feComposite operator="multiply" in2="SourceGraphic"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- 基础法线颜色 -->
|
||||
<rect width="256" height="256" fill="#8080ff"/>
|
||||
|
||||
<!-- 法线变化区域1 -->
|
||||
<polygon points="0,0 128,64 256,0 256,128 128,192 0,128" fill="url(#normalGradient1)" opacity="0.6"/>
|
||||
|
||||
<!-- 法线变化区域2 -->
|
||||
<polygon points="0,128 128,64 256,128 256,256 128,192 0,256" fill="url(#normalGradient2)" opacity="0.5"/>
|
||||
|
||||
<!-- 中心法线区域 -->
|
||||
<circle cx="128" cy="128" r="80" fill="url(#normalRadial)" opacity="0.7"/>
|
||||
|
||||
<!-- 添加细节噪声 -->
|
||||
<rect width="256" height="256" fill="#8080c0" opacity="0.4" filter="url(#normalNoise)"/>
|
||||
|
||||
<!-- 法线细节 -->
|
||||
<rect x="60" y="60" width="40" height="40" fill="#9090ff" opacity="0.3"/>
|
||||
<rect x="156" y="80" width="30" height="30" fill="#7070ff" opacity="0.4"/>
|
||||
<rect x="100" y="180" width="35" height="35" fill="#a0a0ff" opacity="0.3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
56
datav/public/texture/地形高度图.svg
Normal file
@@ -0,0 +1,56 @@
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<radialGradient id="heightGradient1" cx="20%" cy="30%" r="25%">
|
||||
<stop offset="0%" style="stop-color:#ffffff;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#cccccc;stop-opacity:0.8" />
|
||||
<stop offset="100%" style="stop-color:#888888;stop-opacity:0.6" />
|
||||
</radialGradient>
|
||||
<radialGradient id="heightGradient2" cx="70%" cy="20%" r="20%">
|
||||
<stop offset="0%" style="stop-color:#ffffff;stop-opacity:0.9" />
|
||||
<stop offset="50%" style="stop-color:#bbbbbb;stop-opacity:0.7" />
|
||||
<stop offset="100%" style="stop-color:#777777;stop-opacity:0.5" />
|
||||
</radialGradient>
|
||||
<radialGradient id="heightGradient3" cx="30%" cy="80%" r="30%">
|
||||
<stop offset="0%" style="stop-color:#eeeeee;stop-opacity:0.8" />
|
||||
<stop offset="50%" style="stop-color:#aaaaaa;stop-opacity:0.6" />
|
||||
<stop offset="100%" style="stop-color:#666666;stop-opacity:0.4" />
|
||||
</radialGradient>
|
||||
<radialGradient id="heightGradient4" cx="80%" cy="70%" r="15%">
|
||||
<stop offset="0%" style="stop-color:#dddddd;stop-opacity:0.7" />
|
||||
<stop offset="50%" style="stop-color:#999999;stop-opacity:0.5" />
|
||||
<stop offset="100%" style="stop-color:#555555;stop-opacity:0.3" />
|
||||
</radialGradient>
|
||||
<filter id="noise">
|
||||
<feTurbulence baseFrequency="0.02" numOctaves="4" result="noise"/>
|
||||
<feColorMatrix in="noise" type="saturate" values="0"/>
|
||||
<feComponentTransfer>
|
||||
<feFuncA type="discrete" tableValues="0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1"/>
|
||||
</feComponentTransfer>
|
||||
<feComposite operator="multiply" in2="SourceGraphic"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- 基础地形 -->
|
||||
<rect width="512" height="512" fill="#333333"/>
|
||||
|
||||
<!-- 高度区域1 -->
|
||||
<ellipse cx="100" cy="150" rx="120" ry="80" fill="url(#heightGradient1)" opacity="0.8"/>
|
||||
|
||||
<!-- 高度区域2 -->
|
||||
<ellipse cx="360" cy="100" rx="100" ry="60" fill="url(#heightGradient2)" opacity="0.7"/>
|
||||
|
||||
<!-- 高度区域3 -->
|
||||
<ellipse cx="150" cy="400" rx="150" ry="100" fill="url(#heightGradient3)" opacity="0.6"/>
|
||||
|
||||
<!-- 高度区域4 -->
|
||||
<ellipse cx="400" cy="350" rx="80" ry="50" fill="url(#heightGradient4)" opacity="0.5"/>
|
||||
|
||||
<!-- 添加噪声纹理 -->
|
||||
<rect width="512" height="512" fill="#666666" opacity="0.3" filter="url(#noise)"/>
|
||||
|
||||
<!-- 细节高度点 -->
|
||||
<circle cx="200" cy="200" r="20" fill="#aaaaaa" opacity="0.4"/>
|
||||
<circle cx="300" cy="250" r="15" fill="#bbbbbb" opacity="0.3"/>
|
||||
<circle cx="120" cy="300" r="25" fill="#999999" opacity="0.5"/>
|
||||
<circle cx="450" cy="180" r="18" fill="#cccccc" opacity="0.4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
41
datav/public/texture/旋转环.svg
Normal file
@@ -0,0 +1,41 @@
|
||||
<svg width="128" height="128" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<radialGradient id="ringGradient" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" style="stop-color:#00ffff;stop-opacity:0" />
|
||||
<stop offset="40%" style="stop-color:#00ffff;stop-opacity:0" />
|
||||
<stop offset="45%" style="stop-color:#00ffff;stop-opacity:0.8" />
|
||||
<stop offset="55%" style="stop-color:#0088ff;stop-opacity:1" />
|
||||
<stop offset="60%" style="stop-color:#00ffff;stop-opacity:0.8" />
|
||||
<stop offset="100%" style="stop-color:#00ffff;stop-opacity:0" />
|
||||
</radialGradient>
|
||||
<linearGradient id="ringLinear" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#00ffff;stop-opacity:0.3" />
|
||||
<stop offset="25%" style="stop-color:#0088ff;stop-opacity:0.8" />
|
||||
<stop offset="50%" style="stop-color:#00ffff;stop-opacity:1" />
|
||||
<stop offset="75%" style="stop-color:#0088ff;stop-opacity:0.8" />
|
||||
<stop offset="100%" style="stop-color:#00ffff;stop-opacity:0.3" />
|
||||
</linearGradient>
|
||||
<filter id="ringGlow">
|
||||
<feGaussianBlur stdDeviation="2" result="coloredBlur"/>
|
||||
<feMerge>
|
||||
<feMergeNode in="coloredBlur"/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- 外环 -->
|
||||
<circle cx="64" cy="64" r="58" fill="none" stroke="url(#ringLinear)" stroke-width="4" opacity="0.6" filter="url(#ringGlow)"/>
|
||||
|
||||
<!-- 中环 -->
|
||||
<circle cx="64" cy="64" r="48" fill="none" stroke="url(#ringLinear)" stroke-width="3" opacity="0.8" filter="url(#ringGlow)"/>
|
||||
|
||||
<!-- 内环 -->
|
||||
<circle cx="64" cy="64" r="38" fill="none" stroke="url(#ringLinear)" stroke-width="2" opacity="1" filter="url(#ringGlow)"/>
|
||||
|
||||
<!-- 装饰点 -->
|
||||
<circle cx="122" cy="64" r="3" fill="#00ffff" opacity="0.8"/>
|
||||
<circle cx="64" cy="6" r="2" fill="#0088ff" opacity="0.6"/>
|
||||
<circle cx="6" cy="64" r="2" fill="#00ffff" opacity="0.7"/>
|
||||
<circle cx="64" cy="122" r="2" fill="#0088ff" opacity="0.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
81
datav/public/texture/星空.svg
Normal file
@@ -0,0 +1,81 @@
|
||||
<svg width="1024" height="1024" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<radialGradient id="starGradient" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" style="stop-color:#ffffff;stop-opacity:1" />
|
||||
<stop offset="30%" style="stop-color:#ccddff;stop-opacity:0.8" />
|
||||
<stop offset="70%" style="stop-color:#8899cc;stop-opacity:0.4" />
|
||||
<stop offset="100%" style="stop-color:#445566;stop-opacity:0" />
|
||||
</radialGradient>
|
||||
<radialGradient id="nebulaGradient" cx="30%" cy="40%" r="60%">
|
||||
<stop offset="0%" style="stop-color:#ff6699;stop-opacity:0.3" />
|
||||
<stop offset="40%" style="stop-color:#6699ff;stop-opacity:0.2" />
|
||||
<stop offset="80%" style="stop-color:#99ff66;stop-opacity:0.1" />
|
||||
<stop offset="100%" style="stop-color:#000033;stop-opacity:0" />
|
||||
</radialGradient>
|
||||
<filter id="starGlow">
|
||||
<feGaussianBlur stdDeviation="2" result="coloredBlur"/>
|
||||
<feMerge>
|
||||
<feMergeNode in="coloredBlur"/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
<filter id="twinkle">
|
||||
<feTurbulence baseFrequency="0.01" numOctaves="2" result="noise"/>
|
||||
<feColorMatrix in="noise" type="saturate" values="0"/>
|
||||
<feComponentTransfer>
|
||||
<feFuncA type="discrete" tableValues="0 0.2 0.4 0.6 0.8 1"/>
|
||||
</feComponentTransfer>
|
||||
<feComposite operator="multiply" in2="SourceGraphic"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- 深空背景 -->
|
||||
<rect width="1024" height="1024" fill="#000011"/>
|
||||
|
||||
<!-- 星云效果 -->
|
||||
<ellipse cx="300" cy="200" rx="200" ry="150" fill="url(#nebulaGradient)" opacity="0.4"/>
|
||||
<ellipse cx="700" cy="600" rx="180" ry="120" fill="url(#nebulaGradient)" opacity="0.3"/>
|
||||
<ellipse cx="150" cy="800" rx="160" ry="100" fill="url(#nebulaGradient)" opacity="0.2"/>
|
||||
|
||||
<!-- 大星星 -->
|
||||
<circle cx="100" cy="100" r="3" fill="url(#starGradient)" filter="url(#starGlow)"/>
|
||||
<circle cx="300" cy="150" r="2.5" fill="url(#starGradient)" filter="url(#starGlow)"/>
|
||||
<circle cx="500" cy="80" r="3.5" fill="url(#starGradient)" filter="url(#starGlow)"/>
|
||||
<circle cx="700" cy="200" r="2" fill="url(#starGradient)" filter="url(#starGlow)"/>
|
||||
<circle cx="900" cy="120" r="2.8" fill="url(#starGradient)" filter="url(#starGlow)"/>
|
||||
|
||||
<!-- 中等星星 -->
|
||||
<circle cx="150" cy="300" r="2" fill="#ffffff" opacity="0.8" filter="url(#starGlow)"/>
|
||||
<circle cx="350" cy="400" r="1.8" fill="#ccddff" opacity="0.7" filter="url(#starGlow)"/>
|
||||
<circle cx="550" cy="350" r="2.2" fill="#ffffff" opacity="0.9" filter="url(#starGlow)"/>
|
||||
<circle cx="750" cy="450" r="1.5" fill="#ddccff" opacity="0.6" filter="url(#starGlow)"/>
|
||||
<circle cx="850" cy="380" r="1.9" fill="#ffffff" opacity="0.8" filter="url(#starGlow)"/>
|
||||
|
||||
<!-- 小星星群 -->
|
||||
<circle cx="80" cy="500" r="1" fill="#ffffff" opacity="0.6"/>
|
||||
<circle cx="120" cy="520" r="0.8" fill="#ccddff" opacity="0.5"/>
|
||||
<circle cx="200" cy="480" r="1.2" fill="#ffffff" opacity="0.7"/>
|
||||
<circle cx="280" cy="550" r="0.9" fill="#ddccff" opacity="0.4"/>
|
||||
<circle cx="320" cy="580" r="1.1" fill="#ffffff" opacity="0.6"/>
|
||||
|
||||
<circle cx="450" cy="600" r="1" fill="#ffffff" opacity="0.5"/>
|
||||
<circle cx="480" cy="650" r="0.7" fill="#ccddff" opacity="0.4"/>
|
||||
<circle cx="520" cy="620" r="1.3" fill="#ffffff" opacity="0.8"/>
|
||||
<circle cx="580" cy="680" r="0.8" fill="#ddccff" opacity="0.3"/>
|
||||
<circle cx="620" cy="700" r="1" fill="#ffffff" opacity="0.6"/>
|
||||
|
||||
<circle cx="200" cy="750" r="1.1" fill="#ffffff" opacity="0.7"/>
|
||||
<circle cx="250" cy="800" r="0.9" fill="#ccddff" opacity="0.5"/>
|
||||
<circle cx="300" cy="780" r="1.2" fill="#ffffff" opacity="0.6"/>
|
||||
<circle cx="380" cy="820" r="0.8" fill="#ddccff" opacity="0.4"/>
|
||||
<circle cx="420" cy="850" r="1" fill="#ffffff" opacity="0.5"/>
|
||||
|
||||
<circle cx="700" cy="800" r="1" fill="#ffffff" opacity="0.6"/>
|
||||
<circle cx="750" cy="850" r="0.7" fill="#ccddff" opacity="0.4"/>
|
||||
<circle cx="800" cy="820" r="1.3" fill="#ffffff" opacity="0.8"/>
|
||||
<circle cx="880" cy="880" r="0.9" fill="#ddccff" opacity="0.5"/>
|
||||
<circle cx="920" cy="900" r="1.1" fill="#ffffff" opacity="0.7"/>
|
||||
|
||||
<!-- 闪烁效果层 -->
|
||||
<rect width="1024" height="1024" fill="#ffffff" opacity="0.05" filter="url(#twinkle)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
BIN
datav/public/texture/标注.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
28
datav/public/texture/标注.svg
Normal file
@@ -0,0 +1,28 @@
|
||||
<svg width="64" height="64" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<radialGradient id="pointGradient" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" style="stop-color:#00ffff;stop-opacity:1" />
|
||||
<stop offset="70%" style="stop-color:#0088ff;stop-opacity:0.8" />
|
||||
<stop offset="100%" style="stop-color:#004488;stop-opacity:0.3" />
|
||||
</radialGradient>
|
||||
<filter id="glow">
|
||||
<feGaussianBlur stdDeviation="3" result="coloredBlur"/>
|
||||
<feMerge>
|
||||
<feMergeNode in="coloredBlur"/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- 外圈发光 -->
|
||||
<circle cx="32" cy="32" r="28" fill="url(#pointGradient)" opacity="0.3" filter="url(#glow)"/>
|
||||
|
||||
<!-- 主体圆点 -->
|
||||
<circle cx="32" cy="32" r="16" fill="url(#pointGradient)" filter="url(#glow)"/>
|
||||
|
||||
<!-- 内部亮点 -->
|
||||
<circle cx="32" cy="32" r="8" fill="#ffffff" opacity="0.8"/>
|
||||
|
||||
<!-- 中心点 -->
|
||||
<circle cx="32" cy="32" r="3" fill="#00ffff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1022 B |
BIN
datav/public/texture/标注光圈.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
26
datav/public/texture/标注光圈.svg
Normal file
@@ -0,0 +1,26 @@
|
||||
<svg width="128" height="128" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<radialGradient id="haloGradient" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" style="stop-color:#00ffff;stop-opacity:0" />
|
||||
<stop offset="30%" style="stop-color:#00ffff;stop-opacity:0.2" />
|
||||
<stop offset="60%" style="stop-color:#00ffff;stop-opacity:0.8" />
|
||||
<stop offset="80%" style="stop-color:#0088ff;stop-opacity:0.6" />
|
||||
<stop offset="100%" style="stop-color:#004488;stop-opacity:0" />
|
||||
</radialGradient>
|
||||
<filter id="blur">
|
||||
<feGaussianBlur stdDeviation="2" result="coloredBlur"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- 外圈光晕 -->
|
||||
<circle cx="64" cy="64" r="60" fill="none" stroke="url(#haloGradient)" stroke-width="8" opacity="0.4" filter="url(#blur)"/>
|
||||
|
||||
<!-- 中圈光晕 -->
|
||||
<circle cx="64" cy="64" r="45" fill="none" stroke="url(#haloGradient)" stroke-width="6" opacity="0.6" filter="url(#blur)"/>
|
||||
|
||||
<!-- 内圈光晕 -->
|
||||
<circle cx="64" cy="64" r="30" fill="none" stroke="url(#haloGradient)" stroke-width="4" opacity="0.8" filter="url(#blur)"/>
|
||||
|
||||
<!-- 主体光圈 -->
|
||||
<circle cx="64" cy="64" r="50" fill="url(#haloGradient)" opacity="0.3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
26
datav/public/texture/粒子.svg
Normal file
@@ -0,0 +1,26 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<radialGradient id="particleGradient" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" style="stop-color:#ffffff;stop-opacity:1" />
|
||||
<stop offset="30%" style="stop-color:#00ffff;stop-opacity:0.8" />
|
||||
<stop offset="70%" style="stop-color:#0088ff;stop-opacity:0.4" />
|
||||
<stop offset="100%" style="stop-color:#004488;stop-opacity:0" />
|
||||
</radialGradient>
|
||||
<filter id="particleGlow">
|
||||
<feGaussianBlur stdDeviation="1" result="coloredBlur"/>
|
||||
<feMerge>
|
||||
<feMergeNode in="coloredBlur"/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- 主粒子 -->
|
||||
<circle cx="8" cy="8" r="6" fill="url(#particleGradient)" filter="url(#particleGlow)"/>
|
||||
|
||||
<!-- 内核 -->
|
||||
<circle cx="8" cy="8" r="3" fill="#ffffff" opacity="0.9"/>
|
||||
|
||||
<!-- 中心点 -->
|
||||
<circle cx="8" cy="8" r="1" fill="#00ffff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 975 B |
1086
datav/src/App.vue
Normal file
70
datav/src/assets/ningxia.json
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "银川市",
|
||||
"cp": [106.2324, 38.4663]
|
||||
},
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [[
|
||||
[105.8, 38.8], [106.6, 38.8], [106.6, 38.1], [105.8, 38.1], [105.8, 38.8]
|
||||
]]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "石嘴山市",
|
||||
"cp": [106.3586, 39.0133]
|
||||
},
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [[
|
||||
[105.9, 39.4], [106.8, 39.4], [106.8, 38.6], [105.9, 38.6], [105.9, 39.4]
|
||||
]]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "吴忠市",
|
||||
"cp": [106.1993, 37.9972]
|
||||
},
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [[
|
||||
[105.5, 38.2], [107.2, 38.2], [107.2, 37.4], [105.5, 37.4], [105.5, 38.2]
|
||||
]]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "固原市",
|
||||
"cp": [106.2853, 36.0046]
|
||||
},
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [[
|
||||
[105.4, 36.8], [107.1, 36.8], [107.1, 35.2], [105.4, 35.2], [105.4, 36.8]
|
||||
]]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "中卫市",
|
||||
"cp": [105.1896, 37.5149]
|
||||
},
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [[
|
||||
[104.2, 38.0], [106.0, 38.0], [106.0, 36.8], [104.2, 36.8], [104.2, 38.0]
|
||||
]]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
1698
datav/src/components/Alert.vue
Normal file
689
datav/src/components/Dashboard.vue
Normal file
@@ -0,0 +1,689 @@
|
||||
<template>
|
||||
<div class="dashboard-container">
|
||||
<!-- 顶部标题栏 -->
|
||||
<div class="header">
|
||||
<div class="title">
|
||||
<span class="main-title">宁夏智慧畜牧可视化大屏</span>
|
||||
<span class="sub-title">宁夏回族自治区</span>
|
||||
</div>
|
||||
<div class="time-info">
|
||||
<span class="date">{{ currentDate }}</span>
|
||||
<span class="time">{{ currentTime }}</span>
|
||||
<span class="weather">22°C 晴朗</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主体内容区域 -->
|
||||
<div class="main-content">
|
||||
<!-- 左侧面板 -->
|
||||
<div class="left-panel">
|
||||
<!-- 畜牧产业基础分析 -->
|
||||
<div class="panel-section">
|
||||
<div class="section-title">
|
||||
<span class="title-icon">📊</span>
|
||||
畜牧产业基础分析
|
||||
</div>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">牛存栏</div>
|
||||
<div class="stat-value">200 <span class="unit">万</span></div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">羊存栏</div>
|
||||
<div class="stat-value">200 <span class="unit">万</span></div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">猪存栏</div>
|
||||
<div class="stat-value">200 <span class="unit">万</span></div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">合作社数量</div>
|
||||
<div class="stat-value">200 <span class="unit">个</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 养殖品种分布 -->
|
||||
<div class="panel-section">
|
||||
<div class="section-title">
|
||||
<span class="title-icon">🐄</span>
|
||||
养殖品种分布
|
||||
</div>
|
||||
<div class="chart-container" ref="breedChart"></div>
|
||||
</div>
|
||||
|
||||
<!-- 全年文档管理统计 -->
|
||||
<div class="panel-section">
|
||||
<div class="section-title">
|
||||
<span class="title-icon">📋</span>
|
||||
全年文档管理统计
|
||||
</div>
|
||||
<div class="chart-container" ref="docChart"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 中央地图区域 -->
|
||||
<div class="center-panel">
|
||||
<Map3D />
|
||||
</div>
|
||||
|
||||
<!-- 右侧面板 -->
|
||||
<div class="right-panel">
|
||||
<!-- 流动资源分析 -->
|
||||
<div class="panel-section">
|
||||
<div class="section-title">
|
||||
<span class="title-icon">💰</span>
|
||||
流动资源分析
|
||||
</div>
|
||||
<div class="resource-list">
|
||||
<div class="resource-item">
|
||||
<span class="resource-name">资金</span>
|
||||
<div class="progress-bar">
|
||||
<div class="progress" style="width: 75%;"></div>
|
||||
</div>
|
||||
<span class="resource-value">7500000</span>
|
||||
</div>
|
||||
<div class="resource-item">
|
||||
<span class="resource-name">饲料</span>
|
||||
<div class="progress-bar">
|
||||
<div class="progress" style="width: 60%;"></div>
|
||||
</div>
|
||||
<span class="resource-value">6000000</span>
|
||||
</div>
|
||||
<div class="resource-item">
|
||||
<span class="resource-name">设备</span>
|
||||
<div class="progress-bar">
|
||||
<div class="progress" style="width: 85%;"></div>
|
||||
</div>
|
||||
<span class="resource-value">8500000</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 每日资源统计 -->
|
||||
<div class="panel-section">
|
||||
<div class="section-title">
|
||||
<span class="title-icon">📈</span>
|
||||
每日资源统计
|
||||
</div>
|
||||
<div class="chart-container" ref="dailyChart"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部图表区域 -->
|
||||
<div class="bottom-panel">
|
||||
<div class="chart-section">
|
||||
<div class="section-title">
|
||||
<span class="title-icon">📊</span>
|
||||
全年文档管理统计
|
||||
</div>
|
||||
<div class="chart-container" ref="yearChart"></div>
|
||||
</div>
|
||||
<div class="chart-section">
|
||||
<div class="section-title">
|
||||
<span class="title-icon">📈</span>
|
||||
近七日资源统计
|
||||
</div>
|
||||
<div class="chart-container" ref="weekChart"></div>
|
||||
</div>
|
||||
<div class="chart-section">
|
||||
<div class="section-title">
|
||||
<span class="title-icon">📊</span>
|
||||
每日资源流入
|
||||
</div>
|
||||
<div class="chart-container" ref="inflowChart"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import Map3D from './Map3D.vue'
|
||||
|
||||
// 响应式数据
|
||||
const currentDate = ref('')
|
||||
const currentTime = ref('')
|
||||
|
||||
// 图表引用
|
||||
const breedChart = ref(null)
|
||||
const docChart = ref(null)
|
||||
const dailyChart = ref(null)
|
||||
const yearChart = ref(null)
|
||||
const weekChart = ref(null)
|
||||
const inflowChart = ref(null)
|
||||
|
||||
// 定时器
|
||||
let timeInterval = null
|
||||
|
||||
// 更新时间
|
||||
const updateTime = () => {
|
||||
const now = new Date()
|
||||
currentDate.value = now.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
}).replace(/\//g, '-')
|
||||
currentTime.value = now.toLocaleTimeString('zh-CN', {
|
||||
hour12: false
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化图表
|
||||
const initCharts = () => {
|
||||
// 养殖品种分布饼图
|
||||
if (breedChart.value) {
|
||||
const chart = echarts.init(breedChart.value)
|
||||
chart.setOption({
|
||||
backgroundColor: 'transparent',
|
||||
series: [{
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
data: [
|
||||
{ value: 35, name: '牛', itemStyle: { color: '#84acf0' } }, /* 地图顶部材质颜色 */
|
||||
{ value: 25, name: '羊', itemStyle: { color: '#7af4ff' } }, /* 地图光源颜色 */
|
||||
{ value: 20, name: '猪', itemStyle: { color: '#00F6FF' } }, /* 养殖场标签颜色 */
|
||||
{ value: 20, name: '其他', itemStyle: { color: '#123024' } } /* 地图侧面材质颜色 */
|
||||
],
|
||||
label: {
|
||||
color: '#fff',
|
||||
fontSize: 12
|
||||
}
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
// 全年文档管理统计图表
|
||||
if (docChart.value) {
|
||||
const chart = echarts.init(docChart.value)
|
||||
chart.setOption({
|
||||
backgroundColor: 'transparent',
|
||||
grid: {
|
||||
top: 20,
|
||||
right: 20,
|
||||
bottom: 30,
|
||||
left: 40
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月'],
|
||||
axisLabel: { color: '#fff', fontSize: 10 },
|
||||
axisLine: { lineStyle: { color: '#333' } }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: { color: '#fff', fontSize: 10 },
|
||||
axisLine: { lineStyle: { color: '#333' } },
|
||||
splitLine: { lineStyle: { color: '#333' } }
|
||||
},
|
||||
series: [{
|
||||
type: 'line',
|
||||
data: [50, 80, 65, 90, 120, 100],
|
||||
itemStyle: {
|
||||
color: '#7af4ff'
|
||||
},
|
||||
lineStyle: {
|
||||
color: '#7af4ff'
|
||||
}
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
// 每日资源统计柱状图
|
||||
if (dailyChart.value) {
|
||||
const chart = echarts.init(dailyChart.value)
|
||||
chart.setOption({
|
||||
backgroundColor: 'transparent',
|
||||
grid: {
|
||||
top: 20,
|
||||
right: 20,
|
||||
bottom: 30,
|
||||
left: 40
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
|
||||
axisLabel: { color: '#fff', fontSize: 10 },
|
||||
axisLine: { lineStyle: { color: '#333' } }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: { color: '#fff', fontSize: 10 },
|
||||
axisLine: { lineStyle: { color: '#333' } },
|
||||
splitLine: { lineStyle: { color: '#333' } }
|
||||
},
|
||||
series: [{
|
||||
type: 'bar',
|
||||
data: [120, 200, 150, 80, 70, 110, 130],
|
||||
itemStyle: {
|
||||
color: '#84acf0' /* 使用地图顶部材质颜色,移除渐变 */
|
||||
}
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
// 底部年度图表
|
||||
if (yearChart.value) {
|
||||
const chart = echarts.init(yearChart.value)
|
||||
chart.setOption({
|
||||
backgroundColor: 'transparent',
|
||||
grid: {
|
||||
top: 20,
|
||||
right: 20,
|
||||
bottom: 30,
|
||||
left: 40
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['Q1', 'Q2', 'Q3', 'Q4'],
|
||||
axisLabel: { color: '#fff', fontSize: 10 },
|
||||
axisLine: { lineStyle: { color: '#333' } }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: { color: '#fff', fontSize: 10 },
|
||||
axisLine: { lineStyle: { color: '#333' } },
|
||||
splitLine: { lineStyle: { color: '#333' } }
|
||||
},
|
||||
series: [{
|
||||
type: 'bar',
|
||||
data: [300, 450, 320, 520],
|
||||
itemStyle: {
|
||||
color: '#00F6FF'
|
||||
}
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
// 近七日资源统计
|
||||
if (weekChart.value) {
|
||||
const chart = echarts.init(weekChart.value)
|
||||
chart.setOption({
|
||||
backgroundColor: 'transparent',
|
||||
grid: {
|
||||
top: 20,
|
||||
right: 20,
|
||||
bottom: 30,
|
||||
left: 40
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1日', '2日', '3日', '4日', '5日', '6日', '7日'],
|
||||
axisLabel: { color: '#fff', fontSize: 10 },
|
||||
axisLine: { lineStyle: { color: '#333' } }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: { color: '#fff', fontSize: 10 },
|
||||
axisLine: { lineStyle: { color: '#333' } },
|
||||
splitLine: { lineStyle: { color: '#333' } }
|
||||
},
|
||||
series: [{
|
||||
type: 'line',
|
||||
data: [80, 95, 110, 90, 120, 85, 130],
|
||||
itemStyle: {
|
||||
color: '#123024'
|
||||
},
|
||||
lineStyle: {
|
||||
color: '#123024'
|
||||
}
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
// 每日资源流入
|
||||
if (inflowChart.value) {
|
||||
const chart = echarts.init(inflowChart.value)
|
||||
chart.setOption({
|
||||
backgroundColor: 'transparent',
|
||||
series: [{
|
||||
type: 'pie',
|
||||
radius: ['30%', '60%'],
|
||||
data: [
|
||||
{ value: 40, name: '资金流入', itemStyle: { color: '#84acf0' } },
|
||||
{ value: 30, name: '物资流入', itemStyle: { color: '#7af4ff' } },
|
||||
{ value: 20, name: '设备流入', itemStyle: { color: '#00F6FF' } },
|
||||
{ value: 10, name: '其他', itemStyle: { color: '#123024' } }
|
||||
],
|
||||
label: {
|
||||
color: '#fff',
|
||||
fontSize: 10
|
||||
}
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
updateTime()
|
||||
timeInterval = setInterval(updateTime, 1000)
|
||||
|
||||
// 延迟初始化图表,确保DOM已渲染
|
||||
setTimeout(() => {
|
||||
initCharts()
|
||||
}, 100)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timeInterval) {
|
||||
clearInterval(timeInterval)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dashboard-container {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: #0c1426; /* 移除渐变,使用地图基础深色 */
|
||||
color: #fff;
|
||||
font-family: 'Microsoft YaHei', sans-serif;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 科技感背景效果 */
|
||||
.dashboard-container::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background:
|
||||
rgba(132, 172, 240, 0.05),
|
||||
rgba(18, 48, 36, 0.05);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 顶部标题栏 */
|
||||
.header {
|
||||
height: 80px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 40px;
|
||||
background: rgba(132, 172, 240, 0.1); /* 使用地图顶部材质颜色 */
|
||||
border-bottom: 2px solid #84acf0; /* 地图顶部材质颜色 */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: #84acf0; /* 单色替代渐变 */
|
||||
animation: glow 2s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes glow {
|
||||
from { box-shadow: 0 0 5px rgba(132, 172, 240, 0.5); }
|
||||
to { box-shadow: 0 0 20px rgba(132, 172, 240, 0.8); }
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
color: #84acf0; /* 使用地图顶部材质颜色 */
|
||||
text-shadow: 0 0 20px rgba(132, 172, 240, 0.5);
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
font-size: 16px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.time-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 30px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.date, .time {
|
||||
color: #84acf0; /* 使用地图顶部材质颜色 */
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.weather {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
/* 主体内容 */
|
||||
.main-content {
|
||||
height: calc(100vh - 240px);
|
||||
display: flex;
|
||||
padding: 20px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.left-panel, .right-panel {
|
||||
width: 350px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.center-panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 面板样式 */
|
||||
.panel-section {
|
||||
background: rgba(132, 172, 240, 0.05); /* 使用地图颜色 */
|
||||
border: 1px solid rgba(132, 172, 240, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
backdrop-filter: blur(10px);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.panel-section::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: #84acf0; /* 地图顶部材质颜色 */
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #84acf0; /* 使用地图顶部材质颜色 */
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid rgba(132, 172, 240, 0.2);
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* 统计数据网格 */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
padding: 15px;
|
||||
background: rgba(18, 48, 36, 0.3); /* 使用地图侧面颜色 */
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(132, 172, 240, 0.2);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #84acf0; /* 使用地图顶部材质颜色 */
|
||||
}
|
||||
|
||||
.unit {
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
/* 图表容器 */
|
||||
.chart-container {
|
||||
height: 200px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 地图容器 */
|
||||
.map-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(18, 48, 36, 0.2); /* 使用地图侧面颜色 */
|
||||
border: 2px solid rgba(132, 172, 240, 0.3);
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.map-title {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #84acf0; /* 使用地图顶部材质颜色 */
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.map-content {
|
||||
height: calc(100% - 80px);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.map-legend {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* 资源列表 */
|
||||
.resource-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.resource-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.resource-name {
|
||||
width: 60px;
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
flex: 1;
|
||||
height: 8px;
|
||||
background: rgba(18, 48, 36, 0.3); /* 使用地图侧面颜色 */
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress {
|
||||
height: 100%;
|
||||
background: #84acf0; /* 单色替代渐变 */
|
||||
border-radius: 4px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.resource-value {
|
||||
font-size: 12px;
|
||||
color: #84acf0; /* 使用地图顶部材质颜色 */
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 底部面板 */
|
||||
.bottom-panel {
|
||||
height: 160px;
|
||||
display: flex;
|
||||
padding: 0 20px 20px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.chart-section {
|
||||
flex: 1;
|
||||
background: rgba(132, 172, 240, 0.05); /* 使用地图颜色 */
|
||||
border: 1px solid rgba(132, 172, 240, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chart-section::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: #84acf0; /* 地图顶部材质颜色 */
|
||||
}
|
||||
|
||||
.chart-section .chart-container {
|
||||
height: calc(100% - 40px);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 1920px) {
|
||||
.main-title { font-size: 28px; }
|
||||
.left-panel, .right-panel { width: 320px; }
|
||||
}
|
||||
|
||||
@media (max-width: 1600px) {
|
||||
.main-title { font-size: 24px; }
|
||||
.left-panel, .right-panel { width: 300px; }
|
||||
.stats-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
218
datav/src/components/FarmPopup.vue
Normal file
@@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<div v-if="visible" class="farm-popup-overlay" @click="closePopup">
|
||||
<div class="farm-popup" @click.stop>
|
||||
<div class="popup-header">
|
||||
<h3>{{ farm.name }}</h3>
|
||||
<button class="close-btn" @click="closePopup">×</button>
|
||||
</div>
|
||||
<div class="popup-content">
|
||||
<div class="farm-info">
|
||||
<div class="info-item">
|
||||
<!-- <span class="label">养殖类型:</span>
|
||||
<span class="value">{{ farm.type }}</span> -->
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">存栏数量:</span>
|
||||
<span class="value highlight">{{ farm.livestock.toLocaleString() }} 头</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">占地面积:</span>
|
||||
<span class="value">{{ farm.area }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">成立时间:</span>
|
||||
<span class="value">{{ farm.established }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">联系方式:</span>
|
||||
<span class="value">{{ farm.contact }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="popup-actions">
|
||||
<!-- <button class="action-btn primary">查看详情</button>
|
||||
<button class="action-btn secondary">联系养殖场</button> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FarmPopup',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
farm: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: ['close'],
|
||||
methods: {
|
||||
closePopup() {
|
||||
this.$emit('close');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.farm-popup-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.farm-popup {
|
||||
background: rgba(15, 25, 45, 0.95);
|
||||
border: 2px solid #00d4ff;
|
||||
border-radius: 12px;
|
||||
padding: 0;
|
||||
min-width: 400px;
|
||||
max-width: 500px;
|
||||
box-shadow:
|
||||
0 0 30px rgba(0, 212, 255, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
animation: popupSlideIn 0.3s ease-out;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@keyframes popupSlideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.8) translateY(-20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
background: #00d4ff;
|
||||
padding: 15px 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid rgba(0, 212, 255, 0.3);
|
||||
}
|
||||
|
||||
.popup-header h3 {
|
||||
margin: 0;
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.farm-info {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid rgba(0, 212, 255, 0.1);
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #a0a8b8;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #ffffff;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.value.highlight {
|
||||
color: #00d4ff;
|
||||
font-size: 16px;
|
||||
text-shadow: 0 0 10px rgba(0, 212, 255, 0.5);
|
||||
}
|
||||
|
||||
.popup-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.action-btn.primary {
|
||||
background: #00d4ff;
|
||||
color: white;
|
||||
box-shadow: 0 4px 15px rgba(0, 212, 255, 0.3);
|
||||
}
|
||||
|
||||
.action-btn.primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(0, 212, 255, 0.4);
|
||||
}
|
||||
|
||||
.action-btn.secondary {
|
||||
background: transparent;
|
||||
color: #00d4ff;
|
||||
border: 2px solid #00d4ff;
|
||||
}
|
||||
|
||||
.action-btn.secondary:hover {
|
||||
background: #00d4ff;
|
||||
color: white;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
</style>
|
||||
1216
datav/src/components/Home.vue
Normal file
935
datav/src/components/Map3D.vue
Normal file
@@ -0,0 +1,935 @@
|
||||
<template>
|
||||
<div class="map-3d-container">
|
||||
<div id="app-32-map"></div>
|
||||
<FarmPopup
|
||||
:visible="showPopup"
|
||||
:farm="selectedFarmData"
|
||||
@close="closePopup"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Earth3d as BaseEarth } from '@/utils';
|
||||
import TWEEN from '@tweenjs/tween.js';
|
||||
import * as THREE from 'three';
|
||||
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
import { random } from '@/utils';
|
||||
import useFileLoader from '@/hooks/useFileLoader.js';
|
||||
import useCountry from '@/hooks/useCountry.js';
|
||||
import useCoord from '@/hooks/useCoord.js';
|
||||
import useConversionStandardData from '@/hooks/useConversionStandardData.js';
|
||||
import useSequenceFrameAnimate from '@/hooks/useSequenceFrameAnimate';
|
||||
import useCSS2DRender from '@/hooks/useCSS2DRenderer';
|
||||
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer';
|
||||
import FarmPopup from './FarmPopup.vue';
|
||||
|
||||
let centerXY = [106.2581, 38.4681]; // 宁夏回族自治区中心坐标
|
||||
|
||||
// 养殖场数据
|
||||
const farmData = [
|
||||
{
|
||||
id: 1,
|
||||
name: '东方养殖场',
|
||||
position: [106.8581, 38.8681], // 银川市附近
|
||||
livestock: 25234,
|
||||
area: '1200亩',
|
||||
type: '肉牛养殖',
|
||||
established: '2018年',
|
||||
contact: '张经理 138****1234'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '西部牧场',
|
||||
position: [106.2581, 38.1181], // 吴忠市附近
|
||||
livestock: 32475,
|
||||
area: '1800亩',
|
||||
type: '奶牛养殖',
|
||||
established: '2016年',
|
||||
contact: '李经理 139****5678'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '南山农场',
|
||||
position: [106.0581, 36.0681], // 固原市附近
|
||||
livestock: 28900,
|
||||
area: '1500亩',
|
||||
type: '肉牛养殖',
|
||||
established: '2019年',
|
||||
contact: '王经理 137****9012'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '北岭牧业',
|
||||
position: [105.1881, 37.5181], // 中卫市附近
|
||||
livestock: 21100,
|
||||
area: '1000亩',
|
||||
type: '混合养殖',
|
||||
established: '2020年',
|
||||
contact: '赵经理 136****3456'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: '中心养殖基地',
|
||||
position: [106.2581, 38.4681], // 保持中心位置(银川市中心)
|
||||
livestock: 19000,
|
||||
area: '900亩',
|
||||
type: '肉牛养殖',
|
||||
established: '2017年',
|
||||
contact: '陈经理 135****7890'
|
||||
}
|
||||
];
|
||||
|
||||
export default {
|
||||
name: '3dMap30',
|
||||
components: {
|
||||
FarmPopup
|
||||
},
|
||||
setup() {
|
||||
let baseEarth = null;
|
||||
let farmMarkers = []; // 存储养殖场标记
|
||||
let selectedFarm = null; // 当前选中的养殖场
|
||||
|
||||
// 弹窗状态管理
|
||||
const showPopup = ref(false);
|
||||
const selectedFarmData = ref(null);
|
||||
|
||||
// 重置
|
||||
const resize = () => {
|
||||
baseEarth.resize();
|
||||
};
|
||||
|
||||
const { requestData } = useFileLoader();
|
||||
const { transfromGeoJSON } = useConversionStandardData();
|
||||
const { getBoundingBox, geoSphereCoord } = useCoord();
|
||||
const { createCountryFlatLine } = useCountry();
|
||||
const { initCSS2DRender, create2DTag } = useCSS2DRender();
|
||||
// 序列帧
|
||||
const { createSequenceFrame } = useSequenceFrameAnimate();
|
||||
|
||||
const texture = new THREE.TextureLoader();
|
||||
const textureMap = texture.load('/data/map/gz-map.jpg');
|
||||
const texturefxMap = texture.load('/data/map/gz-map-fx.jpg');
|
||||
const rotatingApertureTexture = texture.load('/data/map/rotatingAperture.png');
|
||||
const rotatingPointTexture = texture.load('/data/map/rotating-point2.png');
|
||||
const circlePoint = texture.load('/data/map/circle-point.png');
|
||||
const sceneBg = texture.load('/data/map/scene-bg2.png');
|
||||
textureMap.wrapS = texturefxMap.wrapS = THREE.RepeatWrapping;
|
||||
textureMap.wrapT = texturefxMap.wrapT = THREE.RepeatWrapping;
|
||||
textureMap.flipY = texturefxMap.flipY = false;
|
||||
textureMap.rotation = texturefxMap.rotation = THREE.MathUtils.degToRad(45);
|
||||
const scale = 0.128;
|
||||
textureMap.repeat.set(scale, scale);
|
||||
texturefxMap.repeat.set(scale, scale);
|
||||
const topFaceMaterial = new THREE.MeshPhongMaterial({
|
||||
map: textureMap,
|
||||
color: '#84acf0',
|
||||
combine: THREE.MultiplyOperation,
|
||||
transparent: true,
|
||||
opacity: 1,
|
||||
});
|
||||
const sideMaterial = new THREE.MeshLambertMaterial({
|
||||
color: 0x123024,
|
||||
transparent: true,
|
||||
opacity: 0.9,
|
||||
});
|
||||
const bottomZ = -0.2;
|
||||
// 初始化gui
|
||||
// const initGui = () => {
|
||||
// const gui = new GUI();
|
||||
// const guiParams = {
|
||||
// topColor: '84acf0',
|
||||
// sideColor: '#123024',
|
||||
// scale:0.1,
|
||||
// };
|
||||
// gui.addColor(guiParams, 'topColor').onChange((val) => {
|
||||
// topFaceMaterial.color = new THREE.Color(val);
|
||||
// });
|
||||
// gui.addColor(guiParams, 'sideColor').onChange((val) => {
|
||||
// sideMaterial.color = new THREE.Color(val);
|
||||
// });
|
||||
// gui.add(guiParams, 'scale', 0, 1).onChange((val) => {
|
||||
// textureMap.repeat.set(val, val);
|
||||
// texturefxMap.repeat.set(val, val);
|
||||
// });
|
||||
// };
|
||||
// 初始化旋转光圈
|
||||
const initRotatingAperture = (scene, width) => {
|
||||
let plane = new THREE.PlaneGeometry(width, width);
|
||||
let material = new THREE.MeshBasicMaterial({
|
||||
map: rotatingApertureTexture,
|
||||
transparent: true,
|
||||
opacity: 1,
|
||||
depthTest: true,
|
||||
});
|
||||
let mesh = new THREE.Mesh(plane, material);
|
||||
mesh.position.set(...centerXY, 0);
|
||||
mesh.scale.set(1.1, 1.1, 1.1);
|
||||
scene.add(mesh);
|
||||
return mesh;
|
||||
};
|
||||
// 初始化旋转点
|
||||
const initRotatingPoint = (scene, width) => {
|
||||
let plane = new THREE.PlaneGeometry(width, width);
|
||||
let material = new THREE.MeshBasicMaterial({
|
||||
map: rotatingPointTexture,
|
||||
transparent: true,
|
||||
opacity: 1,
|
||||
depthTest: true,
|
||||
});
|
||||
let mesh = new THREE.Mesh(plane, material);
|
||||
mesh.position.set(...centerXY, bottomZ - 0.02);
|
||||
mesh.scale.set(1.1, 1.1, 1.1);
|
||||
scene.add(mesh);
|
||||
return mesh;
|
||||
};
|
||||
// 初始化背景
|
||||
const initSceneBg = (scene, width) => {
|
||||
let plane = new THREE.PlaneGeometry(width * 4, width * 4);
|
||||
let material = new THREE.MeshPhongMaterial({
|
||||
// color: 0x061920,
|
||||
color: '#2AF4FC',
|
||||
map: sceneBg,
|
||||
transparent: true,
|
||||
opacity: 1,
|
||||
depthTest: true,
|
||||
});
|
||||
|
||||
let mesh = new THREE.Mesh(plane, material);
|
||||
mesh.position.set(...centerXY, bottomZ - 0.2);
|
||||
scene.add(mesh);
|
||||
};
|
||||
// 初始化原点
|
||||
const initCirclePoint = (scene, width) => {
|
||||
let plane = new THREE.PlaneGeometry(width, width);
|
||||
let material = new THREE.MeshPhongMaterial({
|
||||
color: 0x00ffff,
|
||||
map: circlePoint,
|
||||
transparent: true,
|
||||
opacity: 1,
|
||||
// depthTest: false,
|
||||
});
|
||||
let mesh = new THREE.Mesh(plane, material);
|
||||
mesh.position.set(...centerXY, bottomZ - 0.1);
|
||||
// let mesh2 = mesh.clone()
|
||||
// mesh2.position.set(...centerXY, bottomZ - 0.001)
|
||||
scene.add(mesh);
|
||||
};
|
||||
// 初始化粒子
|
||||
const initParticle = (scene, bound) => {
|
||||
// 获取中心点和中间地图大小
|
||||
let { center, size } = bound;
|
||||
// 构建范围,中间地图的2倍
|
||||
let minX = center.x - size.x;
|
||||
let maxX = center.x + size.x;
|
||||
let minY = center.y - size.y;
|
||||
let maxY = center.y + size.y;
|
||||
let minZ = -6;
|
||||
let maxZ = 6;
|
||||
|
||||
let particleArr = [];
|
||||
for (let i = 0; i < 16; i++) {
|
||||
const particle = createSequenceFrame({
|
||||
image: './data/map/上升粒子1.png',
|
||||
width: 180,
|
||||
height: 189,
|
||||
frame: 9,
|
||||
column: 9,
|
||||
row: 1,
|
||||
speed: 0.5,
|
||||
});
|
||||
let particleScale = random(5, 10) / 1000;
|
||||
particle.scale.set(particleScale, particleScale, particleScale);
|
||||
particle.rotation.x = Math.PI / 2;
|
||||
let x = random(minX, maxX);
|
||||
let y = random(minY, maxY);
|
||||
let z = random(minZ, maxZ);
|
||||
particle.position.set(x, y, z);
|
||||
particleArr.push(particle);
|
||||
}
|
||||
scene.add(...particleArr);
|
||||
return particleArr;
|
||||
};
|
||||
// 创建顶部底部边线
|
||||
const initBorderLine = (data, mapGroup) => {
|
||||
let lineTop = createCountryFlatLine(
|
||||
data,
|
||||
{
|
||||
color: 0xffffff,
|
||||
linewidth: 0.0015,
|
||||
transparent: true,
|
||||
depthTest: false,
|
||||
},
|
||||
'Line2'
|
||||
);
|
||||
lineTop.position.z += 0.305;
|
||||
let lineBottom = createCountryFlatLine(
|
||||
data,
|
||||
{
|
||||
color: 0x61fbfd,
|
||||
linewidth: 0.002,
|
||||
// transparent: true,
|
||||
depthTest: false,
|
||||
},
|
||||
'Line2'
|
||||
);
|
||||
lineBottom.position.z -= 0.1905;
|
||||
// 添加边线
|
||||
mapGroup.add(lineTop);
|
||||
mapGroup.add(lineBottom);
|
||||
};
|
||||
|
||||
// 创建各市区边界线
|
||||
const initCityBorderLines = (data, mapGroup) => {
|
||||
// 为不同城市定义不同颜色
|
||||
const cityColors = {
|
||||
'银川市': 0x00ffff, // 青色
|
||||
'石嘴山市': 0xff6600, // 橙色
|
||||
'吴忠市': 0x0066ff, // 蓝色
|
||||
'固原市': 0xff0066, // 粉红色
|
||||
'中卫市': 0xffff00 // 黄色
|
||||
};
|
||||
|
||||
// console.log('开始创建城市边界线,数据:', data);
|
||||
|
||||
if (!data || !data.features) {
|
||||
// console.log('没有找到城市数据');
|
||||
return;
|
||||
}
|
||||
|
||||
data.features.forEach((feature, index) => {
|
||||
if (feature.geometry && feature.geometry.coordinates && feature.properties) {
|
||||
const cityName = feature.properties.name;
|
||||
const color = 0xffffff; // 统一设置为白色
|
||||
|
||||
// console.log(`创建城市边界线: ${cityName}`);
|
||||
|
||||
try {
|
||||
// 手动创建边界线,使用与地图相同的坐标处理方式
|
||||
const borderGroup = new THREE.Group();
|
||||
const coordinates = feature.geometry.coordinates;
|
||||
|
||||
coordinates.forEach((multiPolygon) => {
|
||||
multiPolygon.forEach((polygon) => {
|
||||
const points = [];
|
||||
|
||||
// 提取坐标点,与地图处理方式一致
|
||||
for (let i = 0; i < polygon.length; i++) {
|
||||
let [x, y] = polygon[i];
|
||||
points.push(new THREE.Vector3(x, y, 0.31)); // 设置z坐标稍高于地面
|
||||
}
|
||||
|
||||
if (points.length > 1) {
|
||||
// 创建线条几何体
|
||||
const geometry = new THREE.BufferGeometry().setFromPoints(points);
|
||||
const material = new THREE.LineBasicMaterial({
|
||||
color: color,
|
||||
transparent: true,
|
||||
opacity: 1.0,
|
||||
linewidth: 5
|
||||
});
|
||||
|
||||
const line = new THREE.Line(geometry, material);
|
||||
borderGroup.add(line);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (borderGroup.children.length > 0) {
|
||||
borderGroup.name = `cityBorder_${cityName}`;
|
||||
mapGroup.add(borderGroup);
|
||||
// console.log(`已添加城市边界线: ${cityName}, 线条数量: ${borderGroup.children.length}`);
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error(`创建城市边界线时出错: ${cityName}`, error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// console.log('城市边界线创建完成,mapGroup子对象数量:', mapGroup.children.length);
|
||||
};
|
||||
// 已移除光柱功能,使用养殖场标记点替代
|
||||
|
||||
// 初始化养殖场标记
|
||||
const initFarmMarkers = (mapGroup) => {
|
||||
console.log('开始初始化养殖场标记,数据:', farmData);
|
||||
|
||||
farmData.forEach((farm, index) => {
|
||||
const [lng, lat] = farm.position;
|
||||
console.log(`创建养殖场标记 ${index + 1}: ${farm.name}, 坐标: [${lng}, ${lat}]`);
|
||||
|
||||
// 创建球体几何体作为标记点
|
||||
const geometry = new THREE.SphereGeometry(0.1, 32, 32); // 显著增大球体尺寸
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
|
||||
color: 0x00F6FF, // 红色
|
||||
transparent: false,
|
||||
side: THREE.DoubleSide // 确保双面渲染
|
||||
});
|
||||
|
||||
const farmMarker = new THREE.Mesh(geometry, material);
|
||||
|
||||
// 使用与边界线相同的坐标处理方式
|
||||
farmMarker.position.set(lng, lat, 0.5); // 设置较高的z值确保可见
|
||||
|
||||
// 设置用户数据
|
||||
farmMarker.userData = { ...farm, type: 'farmMarker' };
|
||||
|
||||
// 添加到场景
|
||||
mapGroup.add(farmMarker);
|
||||
farmMarkers.push(farmMarker);
|
||||
|
||||
// 创建养殖场名称标签
|
||||
const labelDiv = document.createElement('div');
|
||||
labelDiv.className = 'farm-label';
|
||||
labelDiv.textContent = farm.name;
|
||||
labelDiv.style.cssText = `
|
||||
font-size: 18px;
|
||||
color: #00F6FF;
|
||||
font-weight: bold;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #00F6FF;
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
z-index: 10000;
|
||||
position: absolute;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
transform: translate(-50%, -100%);
|
||||
`;
|
||||
|
||||
const label = new CSS2DObject(labelDiv);
|
||||
label.position.copy(farmMarker.position);
|
||||
label.position.z += 0.3; // 使用Z轴提升标签高度
|
||||
label.visible = true;
|
||||
|
||||
mapGroup.add(label);
|
||||
|
||||
console.log(`养殖场标签 ${index + 1} 创建:`, {
|
||||
name: farm.name,
|
||||
position: label.position,
|
||||
visible: label.visible,
|
||||
element: labelDiv,
|
||||
elementStyle: labelDiv.style.cssText
|
||||
});
|
||||
|
||||
console.log(`养殖场标记点和标签 ${index + 1} 已添加: ${farm.name},位置: [${lng}, ${lat}, 0.5]`);
|
||||
});
|
||||
|
||||
console.log(`养殖场标记初始化完成,共创建 ${farmData.length} 个标记点`);
|
||||
console.log('farmMarkers数组:', farmMarkers);
|
||||
|
||||
// 强制触发一次CSS2D渲染
|
||||
if (baseEarth && baseEarth.css2dRender && baseEarth.scene && baseEarth.camera) {
|
||||
console.log('强制触发CSS2D渲染器渲染');
|
||||
baseEarth.css2dRender.render(baseEarth.scene, baseEarth.camera);
|
||||
}
|
||||
|
||||
// 创建一个测试CSS2D标签
|
||||
const testLabelDiv = document.createElement('div');
|
||||
testLabelDiv.textContent = '测试标签';
|
||||
testLabelDiv.style.cssText = `
|
||||
font-size: 20px;
|
||||
color: #ff0000;
|
||||
font-weight: bold;
|
||||
background: rgba(255,255,255,0.9);
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
border: 2px solid #ff0000;
|
||||
pointer-events: none;
|
||||
`;
|
||||
|
||||
const testLabel = new CSS2DObject(testLabelDiv);
|
||||
testLabel.position.set(0, 0, 2); // 设置在场景中心上方
|
||||
testLabel.visible = true;
|
||||
|
||||
if (baseEarth && baseEarth.scene) {
|
||||
baseEarth.scene.add(testLabel);
|
||||
console.log('测试标签已添加到场景');
|
||||
}
|
||||
|
||||
// 在页面上显示调试信息
|
||||
const debugInfo = document.createElement('div');
|
||||
debugInfo.style.cssText = `
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background: rgba(0,0,0,0.8);
|
||||
color: white;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
font-size: 12px;
|
||||
z-index: 20000;
|
||||
max-width: 300px;
|
||||
`;
|
||||
// debugInfo.innerHTML = `
|
||||
// <div>养殖场标记数量: ${farmData.length}</div>
|
||||
// <div>mapGroup子对象数量: ${mapGroup.children.length}</div>
|
||||
// <div>CSS2D渲染器: ${baseEarth && baseEarth.css2dRender ? '已初始化' : '未初始化'}</div>
|
||||
// <div>测试标签已添加</div>
|
||||
// `;
|
||||
document.body.appendChild(debugInfo);
|
||||
|
||||
// 10秒后移除调试信息和测试标签
|
||||
setTimeout(() => {
|
||||
if (debugInfo.parentNode) {
|
||||
debugInfo.parentNode.removeChild(debugInfo);
|
||||
}
|
||||
if (baseEarth && baseEarth.scene && testLabel) {
|
||||
baseEarth.scene.remove(testLabel);
|
||||
}
|
||||
}, 10000);
|
||||
};
|
||||
|
||||
// 初始化养殖场点击事件处理
|
||||
const initFarmClickHandler = () => {
|
||||
console.log('开始初始化点击事件处理器');
|
||||
console.log('baseEarth:', baseEarth);
|
||||
console.log('baseEarth.container:', baseEarth.container);
|
||||
console.log('farmMarkers数量:', farmMarkers.length);
|
||||
|
||||
const raycaster = new THREE.Raycaster();
|
||||
const mouse = new THREE.Vector2();
|
||||
|
||||
const onMouseClick = (event) => {
|
||||
console.log('点击事件触发');
|
||||
|
||||
// 计算鼠标位置
|
||||
const rect = baseEarth.container.getBoundingClientRect();
|
||||
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
|
||||
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
|
||||
|
||||
console.log('鼠标坐标:', mouse.x, mouse.y);
|
||||
|
||||
// 设置射线
|
||||
raycaster.setFromCamera(mouse, baseEarth.camera);
|
||||
|
||||
// 检测与养殖场标记的交集
|
||||
const intersects = raycaster.intersectObjects(farmMarkers);
|
||||
|
||||
console.log('射线检测结果数量:', intersects.length);
|
||||
|
||||
if (intersects.length > 0) {
|
||||
const clickedMarker = intersects[0].object;
|
||||
console.log('点击了养殖场标记:', clickedMarker.userData.name);
|
||||
|
||||
if (clickedMarker.userData && clickedMarker.userData.type === 'farmMarker') {
|
||||
selectedFarm = clickedMarker.userData;
|
||||
showFarmPopup(clickedMarker.userData);
|
||||
}
|
||||
} else {
|
||||
console.log('未检测到养殖场标记点击');
|
||||
}
|
||||
};
|
||||
|
||||
baseEarth.container.addEventListener('click', onMouseClick);
|
||||
console.log('点击事件监听器已添加');
|
||||
};
|
||||
|
||||
// 显示养殖场弹窗
|
||||
const showFarmPopup = (farm) => {
|
||||
console.log('显示养殖场弹窗:', farm.name);
|
||||
selectedFarmData.value = farm;
|
||||
showPopup.value = true;
|
||||
};
|
||||
|
||||
// 关闭弹窗
|
||||
const closePopup = () => {
|
||||
showPopup.value = false;
|
||||
selectedFarmData.value = null;
|
||||
};
|
||||
// 创建标签
|
||||
const initLabel = (properties, scene) => {
|
||||
if(!properties.centroid && !properties.center && !properties.cp){
|
||||
// console.log('标签创建失败:缺少center、centroid或cp属性', properties.name);
|
||||
return false
|
||||
}
|
||||
// 设置标签的显示内容和位置
|
||||
let labelCenter = properties.center || properties.centroid || properties.cp;
|
||||
// console.log(`创建标签: ${properties.name}, 位置:`, labelCenter);
|
||||
|
||||
// 创建标签(使用简单参数,类似122222项目)
|
||||
var label = create2DTag(properties.name, 'map-32-label');
|
||||
scene.add(label);
|
||||
|
||||
// 调用show方法显示标签(参考122222项目的实现)
|
||||
label.show(properties.name, new THREE.Vector3(...labelCenter, 0.8));
|
||||
|
||||
// console.log(`标签创建并显示成功: ${properties.name}`);
|
||||
};
|
||||
onMounted(async () => {
|
||||
console.log('=== Map3D组件已挂载 ===');
|
||||
console.log('farmData:', farmData);
|
||||
// 等待DOM完全渲染
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
|
||||
// 检查容器尺寸但不阻止初始化
|
||||
const container = document.getElementById('app-32-map');
|
||||
// console.log('容器尺寸:', container ? `${container.offsetWidth}x${container.offsetHeight}` : '容器不存在');
|
||||
|
||||
if (container && (container.offsetWidth === 0 || container.offsetHeight === 0)) {
|
||||
// console.log('容器尺寸为0,但继续初始化...');
|
||||
// 减少等待时间,不阻止初始化
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
// 宁夏回族自治区数据
|
||||
let provinceData;
|
||||
try {
|
||||
provinceData = await requestData('./data/map/宁夏回族自治区.json');
|
||||
provinceData = transfromGeoJSON(provinceData);
|
||||
} catch (error) {
|
||||
// console.error('地图数据加载失败:', error);
|
||||
return; // 如果数据加载失败,直接返回
|
||||
}
|
||||
|
||||
class CurrentEarth extends BaseEarth {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.particleArr = []; // 初始化粒子数组
|
||||
}
|
||||
initCamera() {
|
||||
let { width, height } = this.options;
|
||||
let rate = width / height;
|
||||
// 设置45°的透视相机,更符合人眼观察
|
||||
this.camera = new THREE.PerspectiveCamera(45, rate, 0.001, 90000000);
|
||||
this.camera.up.set(0, 0, 1);
|
||||
// 宁夏
|
||||
this.camera.position.set(106.27777217804006, 35.660260562607277, 8.029548316292933); //相机在Three.js坐标系中的位置
|
||||
this.camera.lookAt(...centerXY, 0);
|
||||
}
|
||||
initModel() {
|
||||
try {
|
||||
// 创建组
|
||||
this.mapGroup = new THREE.Group();
|
||||
// 标签 初始化 - 确保使用有效的尺寸
|
||||
const validOptions = {
|
||||
...this.options,
|
||||
width: Math.max(this.options.width || this.container.offsetWidth || 800, 800),
|
||||
height: Math.max(this.options.height || this.container.offsetHeight || 600, 600)
|
||||
};
|
||||
this.css2dRender = initCSS2DRender(validOptions, this.container);
|
||||
|
||||
// 确保CSS2D渲染器的DOM元素有正确的样式
|
||||
this.css2dRender.domElement.style.zIndex = '10000'; // 确保在WebGL canvas之上
|
||||
this.css2dRender.domElement.style.pointerEvents = 'none';
|
||||
|
||||
console.log('CSS2D渲染器初始化完成:', {
|
||||
renderer: this.css2dRender,
|
||||
domElement: this.css2dRender.domElement,
|
||||
containerChildren: this.container.children.length
|
||||
});
|
||||
|
||||
// console.log('开始处理省份数据,features数量:', provinceData.features.length);
|
||||
provinceData.features.forEach((elem, index) => {
|
||||
// console.log(`处理第${index + 1}个feature:`, elem.properties.name);
|
||||
|
||||
// 定一个省份对象
|
||||
const province = new THREE.Object3D();
|
||||
// 坐标
|
||||
const coordinates = elem.geometry.coordinates;
|
||||
// city 属性
|
||||
const properties = elem.properties;
|
||||
|
||||
|
||||
// 循环坐标
|
||||
coordinates.forEach((multiPolygon) => {
|
||||
multiPolygon.forEach((polygon) => {
|
||||
|
||||
const shape = new THREE.Shape();
|
||||
// 绘制shape
|
||||
for (let i = 0; i < polygon.length; i++) {
|
||||
|
||||
let [x, y] = polygon[i];
|
||||
if (i === 0) {
|
||||
shape.moveTo(x, y);
|
||||
}
|
||||
shape.lineTo(x, y);
|
||||
}
|
||||
// 拉伸设置
|
||||
const extrudeSettings = {
|
||||
depth: 0.2,
|
||||
bevelEnabled: true,
|
||||
bevelSegments: 1,
|
||||
bevelThickness: 0.1,
|
||||
};
|
||||
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
||||
const mesh = new THREE.Mesh(geometry, [topFaceMaterial, sideMaterial]);
|
||||
province.add(mesh);
|
||||
});
|
||||
});
|
||||
this.mapGroup.add(province);
|
||||
// 创建标点和标签
|
||||
|
||||
initLabel(properties, this.scene);
|
||||
});
|
||||
// 创建上下边框
|
||||
initBorderLine(provinceData, this.mapGroup);
|
||||
|
||||
// 创建各市区边界线
|
||||
initCityBorderLines(provinceData, this.mapGroup);
|
||||
|
||||
let earthGroupBound = getBoundingBox(this.mapGroup);
|
||||
centerXY = [earthGroupBound.center.x, earthGroupBound.center.y];
|
||||
let { size } = earthGroupBound;
|
||||
let width = size.x < size.y ? size.y + 1 : size.x + 1;
|
||||
// 添加背景,修饰元素
|
||||
this.rotatingApertureMesh = initRotatingAperture(this.scene, width);
|
||||
this.rotatingPointMesh = initRotatingPoint(this.scene, width - 2);
|
||||
initCirclePoint(this.scene, width);
|
||||
initSceneBg(this.scene, width);
|
||||
|
||||
// 将组添加到场景中
|
||||
// console.log('将mapGroup添加到场景中,mapGroup子对象数量:', this.mapGroup.children.length);
|
||||
this.scene.add(this.mapGroup);
|
||||
// console.log('场景中对象数量:', this.scene.children.length);
|
||||
this.particleArr = initParticle(this.scene, earthGroupBound);
|
||||
// console.log('粒子系统初始化完成');
|
||||
|
||||
// 养殖场标记将在baseEarth.run()完成后初始化
|
||||
|
||||
// 更新相机目标到新的中心点
|
||||
this.camera.lookAt(...centerXY, 0);
|
||||
if (this.controls) {
|
||||
this.controls.target.set(...centerXY, 0);
|
||||
this.controls.update();
|
||||
}
|
||||
|
||||
initGui();
|
||||
} catch (error) {
|
||||
// console.log(error);
|
||||
}
|
||||
}
|
||||
getDataRenderMap() {}
|
||||
|
||||
destroy() {}
|
||||
initControls() {
|
||||
super.initControls();
|
||||
this.controls.target = new THREE.Vector3(...centerXY, 0);
|
||||
}
|
||||
initLight() {
|
||||
// 平行光1 - 主光源
|
||||
let directionalLight1 = new THREE.DirectionalLight(0x7af4ff, 1.2);
|
||||
directionalLight1.position.set(...centerXY, 50);
|
||||
directionalLight1.castShadow = false;
|
||||
|
||||
// 平行光2 - 辅助光源
|
||||
let directionalLight2 = new THREE.DirectionalLight(0x7af4ff, 0.8);
|
||||
directionalLight2.position.set(centerXY[0] + 20, centerXY[1] + 20, 40);
|
||||
directionalLight2.castShadow = false;
|
||||
|
||||
// 平行光3 - 顶部光源,增强边界线可见性
|
||||
let directionalLight3 = new THREE.DirectionalLight(0xffffff, 0.6);
|
||||
directionalLight3.position.set(...centerXY, 80);
|
||||
directionalLight3.castShadow = false;
|
||||
|
||||
// 环境光 - 增强整体亮度
|
||||
let ambientLight = new THREE.AmbientLight(0x7af4ff, 1.5);
|
||||
|
||||
// 将光源添加到场景中
|
||||
this.addObject(directionalLight1);
|
||||
this.addObject(directionalLight2);
|
||||
this.addObject(directionalLight3);
|
||||
this.addObject(ambientLight);
|
||||
}
|
||||
initRenderer() {
|
||||
super.initRenderer();
|
||||
// this.renderer.outputEncoding = THREE.sRGBEncoding
|
||||
}
|
||||
loop() {
|
||||
this.animationStop = window.requestAnimationFrame(() => {
|
||||
this.loop();
|
||||
});
|
||||
// 检查渲染器是否存在
|
||||
if (!this.renderer || !this.scene || !this.camera) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查渲染器尺寸是否有效(放宽检查条件)
|
||||
if (this.renderer.domElement && (this.renderer.domElement.width === 0 || this.renderer.domElement.height === 0)) {
|
||||
// 只在连续多帧都是0尺寸时才跳过渲染
|
||||
if (!this.zeroSizeFrameCount) this.zeroSizeFrameCount = 0;
|
||||
this.zeroSizeFrameCount++;
|
||||
if (this.zeroSizeFrameCount > 10) {
|
||||
// console.warn('渲染器canvas尺寸持续为0,跳过渲染');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this.zeroSizeFrameCount = 0;
|
||||
}
|
||||
|
||||
// 检查CSS2D渲染器是否有效(只检查是否存在,不检查尺寸)
|
||||
// CSS2DRenderer的domElement可能不会有正确的offsetWidth/offsetHeight
|
||||
|
||||
// 这里是你自己业务上需要的code
|
||||
this.renderer.render(this.scene, this.camera);
|
||||
// 控制相机旋转缩放的更新
|
||||
if (this.options.controls.visibel && this.controls) {
|
||||
this.controls.update();
|
||||
}
|
||||
// 统计更新 - 添加更严格的检查
|
||||
if (this.options.statsVisibel && this.stats && this.stats.dom) {
|
||||
// 检查Stats内部canvas是否有效
|
||||
const canvas = this.stats.dom.querySelector('canvas');
|
||||
if (canvas && canvas.width > 0 && canvas.height > 0) {
|
||||
this.stats.update();
|
||||
}
|
||||
}
|
||||
if (this.rotatingApertureMesh) {
|
||||
this.rotatingApertureMesh.rotation.z += 0.0005;
|
||||
}
|
||||
if (this.rotatingPointMesh) {
|
||||
this.rotatingPointMesh.rotation.z -= 0.0005;
|
||||
}
|
||||
// 渲染标签 - 使用CSS2D渲染器
|
||||
if (this.css2dRender && this.scene && this.camera) {
|
||||
this.css2dRender.render(this.scene, this.camera);
|
||||
// 每100帧输出一次调试信息
|
||||
if (this.frameCount === undefined) this.frameCount = 0;
|
||||
this.frameCount++;
|
||||
if (this.frameCount % 100 === 0) {
|
||||
console.log('CSS2D渲染器状态:', {
|
||||
renderer: !!this.css2dRender,
|
||||
scene: !!this.scene,
|
||||
camera: !!this.camera,
|
||||
sceneChildren: this.scene.children.length
|
||||
});
|
||||
}
|
||||
}
|
||||
// 粒子上升
|
||||
if (this.particleArr.length) {
|
||||
for (let i = 0; i < this.particleArr.length; i++) {
|
||||
this.particleArr[i].updateSequenceFrame();
|
||||
this.particleArr[i].position.z += 0.01;
|
||||
if (this.particleArr[i].position.z >= 6) {
|
||||
this.particleArr[i].position.z = -6;
|
||||
}
|
||||
}
|
||||
}
|
||||
TWEEN.update();
|
||||
}
|
||||
resize() {
|
||||
super.resize();
|
||||
|
||||
// 确保尺寸有效
|
||||
const validWidth = Math.max(this.options.width || 800, 800);
|
||||
const validHeight = Math.max(this.options.height || 600, 600);
|
||||
|
||||
// 更新options中的尺寸
|
||||
this.options.width = validWidth;
|
||||
this.options.height = validHeight;
|
||||
|
||||
// 确保渲染器已准备就绪再执行渲染
|
||||
if (this.renderer && this.scene && this.camera) {
|
||||
// 重新设置渲染器尺寸
|
||||
this.renderer.setSize(validWidth, validHeight);
|
||||
this.renderer.setPixelRatio(window.devicePixelRatio);
|
||||
// 这里是你自己业务上需要的code
|
||||
this.renderer.render(this.scene, this.camera);
|
||||
}
|
||||
|
||||
if (this.css2dRender) {
|
||||
this.css2dRender.setSize(validWidth, validHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
// console.log('开始创建Earth实例...');
|
||||
baseEarth = new CurrentEarth({
|
||||
container: '#app-32-map',
|
||||
axesVisibel: false,
|
||||
controls: {
|
||||
enableDamping: true, // 阻尼
|
||||
maxPolarAngle: (Math.PI / 2) * 0.98,
|
||||
},
|
||||
});
|
||||
// console.log('Earth实例创建完成,开始运行...');
|
||||
baseEarth.run();
|
||||
// console.log('Earth实例运行完成');
|
||||
|
||||
// 将CSS2D渲染器赋值给baseEarth实例,确保渲染循环中能正确访问
|
||||
if (baseEarth && baseEarth.css2dRender) {
|
||||
// CSS2D渲染器已经在CurrentEarth类中初始化了
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 初始化养殖场标记
|
||||
console.log('准备调用initFarmMarkers,mapGroup:', baseEarth.mapGroup);
|
||||
initFarmMarkers(baseEarth.mapGroup);
|
||||
console.log('initFarmMarkers调用完成,mapGroup子对象数量:', baseEarth.mapGroup.children.length);
|
||||
|
||||
// 初始化养殖场点击事件监听器
|
||||
initFarmClickHandler();
|
||||
|
||||
window.addEventListener('resize', resize);
|
||||
});
|
||||
|
||||
return {
|
||||
showPopup,
|
||||
selectedFarmData,
|
||||
closePopup,
|
||||
initFarmMarkers
|
||||
};
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', resize);
|
||||
if (baseEarth) {
|
||||
// 清理Three.js资源
|
||||
if (baseEarth.renderer) {
|
||||
baseEarth.renderer.dispose();
|
||||
}
|
||||
if (baseEarth.scene) {
|
||||
baseEarth.scene.clear();
|
||||
}
|
||||
// 停止动画循环
|
||||
if (baseEarth.animationStop) {
|
||||
cancelAnimationFrame(baseEarth.animationStop);
|
||||
}
|
||||
baseEarth = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.map-3d-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#app-32-map {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.map-32-label {
|
||||
font-size: 10px;
|
||||
color: #fff;
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.farm-label {
|
||||
font-size: 16px;
|
||||
color: #00F6FF;
|
||||
font-weight: bold;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
border: 2px solid #00F6FF;
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
z-index: 1000;
|
||||
position: relative;
|
||||
display: block;
|
||||
visibility: visible !important;
|
||||
}
|
||||
</style>
|
||||
223
datav/src/hooks/map/useMapMarkedLightPillar.js
Normal file
@@ -0,0 +1,223 @@
|
||||
import * as THREE from 'three'
|
||||
import TWEEN from '@tweenjs/tween.js'
|
||||
import useCoord from '@/hooks/useCoord'
|
||||
import { deepMerge, random } from '@/utils'
|
||||
/**
|
||||
*
|
||||
* @param {object} {
|
||||
* pointTextureUrl:标记点的图片url
|
||||
* lightHaloTextureUrl:光圈的URL
|
||||
* lightPillarUrl:光柱的URL
|
||||
* scaleFactor:1 缩放系数,用来调整标记点和光圈的缩放大小
|
||||
* }
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
export default function useMarkedLightPillar(options) {
|
||||
const { geoSphereCoord } = useCoord()
|
||||
// 默认参数
|
||||
let defaultOptions = {
|
||||
pointTextureUrl: './assets/texture/标注.png',
|
||||
lightHaloTextureUrl: './assets/texture/标注光圈.png',
|
||||
lightPillarUrl: './assets/texture/光柱.png',
|
||||
scaleFactor: 1, // 缩放系数
|
||||
}
|
||||
defaultOptions = deepMerge(defaultOptions, options)
|
||||
// 纹理加载器
|
||||
const textureLoader = new THREE.TextureLoader()
|
||||
// 射线拾取对象
|
||||
const raycaster = new THREE.Raycaster()
|
||||
let containerWidth = window.width
|
||||
let containerHeight = window.height
|
||||
// 对象属性
|
||||
let getBoundingClientRect = null
|
||||
/**
|
||||
* 创建标记点
|
||||
* @param {*} R 地球半径,根据R来进行缩放
|
||||
* @returns
|
||||
*/
|
||||
const createPointMesh = () => {
|
||||
// 标记点:几何体,材质,
|
||||
const geometry = new THREE.PlaneBufferGeometry(1, 1)
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
map: textureLoader.load(defaultOptions.pointTextureUrl),
|
||||
color: 0x00ffff,
|
||||
side: THREE.DoubleSide,
|
||||
transparent: true,
|
||||
depthWrite: false, //禁止写入深度缓冲区数据
|
||||
})
|
||||
let mesh = new THREE.Mesh(geometry, material)
|
||||
mesh.name = 'createPointMesh'
|
||||
// 缩放
|
||||
const scale = 0.15 * defaultOptions.scaleFactor
|
||||
mesh.scale.set(scale, scale, scale)
|
||||
return mesh
|
||||
}
|
||||
/**
|
||||
* 创建光圈
|
||||
* @param {*} R 地球半径,根据R来进行缩放
|
||||
* @returns
|
||||
*/
|
||||
const createLightHalo = () => {
|
||||
// 标记点:几何体,材质,
|
||||
const geometry = new THREE.PlaneBufferGeometry(1, 1)
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
map: textureLoader.load(defaultOptions.lightHaloTextureUrl),
|
||||
color: 0x00ffff,
|
||||
side: THREE.DoubleSide,
|
||||
opacity: 0,
|
||||
transparent: true,
|
||||
depthWrite: false, //禁止写入深度缓冲区数据
|
||||
})
|
||||
let mesh = new THREE.Mesh(geometry, material)
|
||||
mesh.name = 'createLightHalo'
|
||||
// 缩放
|
||||
const scale = 0.3 * defaultOptions.scaleFactor
|
||||
mesh.scale.set(scale, scale, scale)
|
||||
// 动画延迟时间
|
||||
const delay = random(0, 2000)
|
||||
// 动画:透明度缩放动画
|
||||
mesh.tween1 = new TWEEN.Tween({ scale: scale, opacity: 0 })
|
||||
.to({ scale: scale * 1.5, opacity: 1 }, 1000)
|
||||
.delay(delay)
|
||||
.onUpdate(params => {
|
||||
let { scale, opacity } = params
|
||||
mesh.scale.set(scale, scale, scale)
|
||||
mesh.material.opacity = opacity
|
||||
})
|
||||
mesh.tween2 = new TWEEN.Tween({ scale: scale * 1.5, opacity: 1 })
|
||||
.to({ scale: scale * 2, opacity: 0 }, 1000)
|
||||
.onUpdate(params => {
|
||||
let { scale, opacity } = params
|
||||
mesh.scale.set(scale, scale, scale)
|
||||
mesh.material.opacity = opacity
|
||||
})
|
||||
mesh.tween1.chain(mesh.tween2)
|
||||
mesh.tween2.chain(mesh.tween1)
|
||||
mesh.tween1.start()
|
||||
return mesh
|
||||
}
|
||||
/**
|
||||
* 创建光柱
|
||||
* @param {*} lon
|
||||
* @param {*} lat
|
||||
* @param {*} heightScaleFactor 光柱高度的缩放系数
|
||||
* @returns
|
||||
*/
|
||||
const createLightPillar = (lon, lat, heightScaleFactor = 1) => {
|
||||
let group = new THREE.Group()
|
||||
// 柱体高度
|
||||
const height = heightScaleFactor
|
||||
// 柱体的geo,6.19=柱体图片高度/宽度的倍数
|
||||
const geometry = new THREE.PlaneBufferGeometry(height / 6.219, height)
|
||||
// 柱体旋转90度,垂直于Y轴
|
||||
geometry.rotateX(Math.PI / 2)
|
||||
// 柱体的z轴移动高度一半对齐中心点
|
||||
geometry.translate(0, 0, height / 2)
|
||||
// 柱子材质
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
map: textureLoader.load(defaultOptions.lightPillarUrl),
|
||||
color: 0x00ffff,
|
||||
transparent: true,
|
||||
depthWrite: false,
|
||||
side: THREE.DoubleSide,
|
||||
})
|
||||
// 光柱01
|
||||
let light01 = new THREE.Mesh(geometry, material)
|
||||
light01.name = 'createLightPillar01'
|
||||
// 光柱02:复制光柱01
|
||||
let light02 = light01.clone()
|
||||
light02.name = 'createLightPillar02'
|
||||
// 光柱02,旋转90°,跟 光柱01交叉
|
||||
light02.rotateZ(Math.PI / 2)
|
||||
// 创建底部标点
|
||||
const bottomMesh = createPointMesh()
|
||||
// 创建光圈
|
||||
const lightHalo = createLightHalo()
|
||||
// 将光柱和标点添加到组里
|
||||
group.add(bottomMesh, lightHalo, light01, light02)
|
||||
// 设置组对象的姿态
|
||||
// group = setMeshQuaternion(group, R, lon, lat)
|
||||
group.position.set(lon, lat, 0)
|
||||
return group
|
||||
}
|
||||
/**
|
||||
* 设置光柱颜色
|
||||
* @param {*} group
|
||||
* @param {*} color
|
||||
*/
|
||||
const setLightPillarColor = (group, color) => {
|
||||
group.children.forEach(item => {
|
||||
item.material.color = new THREE.Color(color)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 设置网格的位置及姿态
|
||||
* @param {*} mesh
|
||||
* @param {*} R
|
||||
* @param {*} lon
|
||||
* @param {*} lat
|
||||
* @returns
|
||||
*/
|
||||
const setMeshQuaternion = (mesh, R, lon, lat) => {
|
||||
const { x, y, z } = geoSphereCoord(R, lon, lat)
|
||||
mesh.position.set(x, y, z)
|
||||
// 姿态设置
|
||||
// mesh在球面上的法线方向(球心和球面坐标构成的方向向量)
|
||||
let meshVector = new THREE.Vector3(x, y, z).normalize()
|
||||
// mesh默认在XOY平面上,法线方向沿着z轴new THREE.Vector3(0, 0, 1)
|
||||
let normal = new THREE.Vector3(0, 0, 1)
|
||||
// 四元数属性.quaternion表示mesh的角度状态
|
||||
//.setFromUnitVectors();计算两个向量之间构成的四元数值
|
||||
mesh.quaternion.setFromUnitVectors(normal, meshVector)
|
||||
return mesh
|
||||
}
|
||||
/**
|
||||
* 射线拾取,返回选中的mesh
|
||||
* @param {*} event
|
||||
* @param {*} container
|
||||
* @param {*} camera
|
||||
* @param {*} mesh // 光柱group
|
||||
* @returns
|
||||
*/
|
||||
const getRaycasterObj = (event, container, camera, mesh) => {
|
||||
//屏幕坐标转WebGL标准设备坐标
|
||||
if (!getBoundingClientRect) {
|
||||
getBoundingClientRect = container.getBoundingClientRect()
|
||||
containerWidth = container.offsetWidth
|
||||
containerHeight = container.offsetHeight
|
||||
}
|
||||
var x = ((event.clientX - getBoundingClientRect.left) / containerWidth) * 2 - 1
|
||||
var y = -((event.clientY - getBoundingClientRect.top) / containerHeight) * 2 + 1
|
||||
|
||||
//通过鼠标单击位置标准设备坐标和相机参数计算射线投射器`Raycaster`的射线属性.ray
|
||||
raycaster.setFromCamera(new THREE.Vector2(x, y), camera)
|
||||
//返回.intersectObjects()参数中射线选中的网格模型对象
|
||||
// 未选中对象返回空数组[],选中一个数组1个元素,选中两个数组两个元素
|
||||
var intersects = raycaster.intersectObjects(mesh)
|
||||
return intersects
|
||||
}
|
||||
/**
|
||||
* 选择光柱,返回选择的对象
|
||||
* @param {*} event
|
||||
* @param {*} container
|
||||
* @param {*} camera
|
||||
* @param {*} mesh
|
||||
* @returns 返回选择的对象
|
||||
*/
|
||||
const chooseLightPillar = (event, container, camera, mesh) => {
|
||||
event.preventDefault()
|
||||
// 获取拾取的对象数组
|
||||
let intersects = getRaycasterObj(event, container, camera, mesh)
|
||||
// 大于0说明,说明选中了mesh,返回对象
|
||||
if (intersects.length > 0) {
|
||||
return intersects[0]
|
||||
}
|
||||
return null
|
||||
}
|
||||
return {
|
||||
createLightPillar,
|
||||
setLightPillarColor,
|
||||
chooseLightPillar,
|
||||
}
|
||||
}
|
||||
80
datav/src/hooks/useCSS2DRenderer.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import { CSS2DObject, CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer'
|
||||
|
||||
export default function useCSS2DRender() {
|
||||
/**
|
||||
* 初始化CSS2D渲染器
|
||||
* @param {Object} options - 配置选项
|
||||
* @param {HTMLElement} container - 容器元素
|
||||
* @returns {CSS2DRenderer} CSS2D渲染器实例
|
||||
*/
|
||||
const initCSS2DRender = (options, container) => {
|
||||
const { width, height } = options
|
||||
|
||||
// 确保尺寸有效,避免Canvas尺寸为0的错误
|
||||
const validWidth = Math.max(width || 800, 1)
|
||||
const validHeight = Math.max(height || 600, 1)
|
||||
|
||||
let css2dRender = new CSS2DRenderer() // 实例化css2d渲染器
|
||||
css2dRender.setSize(validWidth, validHeight) // 设置渲染器的尺寸
|
||||
css2dRender.domElement.style.position = 'absolute' // 设置定位位置
|
||||
css2dRender.domElement.style.left = '0px'
|
||||
css2dRender.domElement.style.top = '0px'
|
||||
css2dRender.domElement.style.width = validWidth + 'px'
|
||||
css2dRender.domElement.style.height = validHeight + 'px'
|
||||
css2dRender.domElement.style.zIndex = '10000' // 确保在WebGL canvas之上
|
||||
css2dRender.domElement.style.pointerEvents = 'none' // 设置不能背选中
|
||||
css2dRender.domElement.style.overflow = 'visible' // 确保内容可见
|
||||
container.appendChild(css2dRender.domElement) // 插入到容器当中
|
||||
console.log('CSS2D渲染器DOM元素已插入容器');
|
||||
console.log('容器尺寸:', validWidth, 'x', validHeight);
|
||||
console.log('DOM元素样式:', css2dRender.domElement.style.cssText);
|
||||
return css2dRender
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建2d标签
|
||||
* @param {*} name 标签内容
|
||||
* @param {*} className 标签class
|
||||
* @returns
|
||||
*/
|
||||
const create2DTag = (name = '', className = '') => {
|
||||
let tag = document.createElement('div')
|
||||
tag.innerHTML = name
|
||||
tag.className = className
|
||||
tag.style.pointerEvents = 'none'
|
||||
tag.style.visibility = 'hidden'
|
||||
tag.style.position = 'absolute'
|
||||
// 如果className不存在,用以下样式
|
||||
if (!className) {
|
||||
tag.style.padding = '10px'
|
||||
tag.style.color = '#fff'
|
||||
tag.style.fontSize = '12px'
|
||||
tag.style.textAlign = 'center'
|
||||
tag.style.background = 'rgba(0,0,0,0.6)'
|
||||
tag.style.borderRadius = '4px'
|
||||
}
|
||||
let label = new CSS2DObject(tag)
|
||||
/**
|
||||
* 标签显示,
|
||||
* @param {*} name 显示内容
|
||||
* @param {*} point 显示坐标
|
||||
*/
|
||||
label.show = (name, point) => {
|
||||
label.element.innerHTML = name
|
||||
label.element.style.visibility = 'visible'
|
||||
label.position.copy(point)
|
||||
}
|
||||
/**
|
||||
* 隐藏
|
||||
*/
|
||||
label.hide = () => {
|
||||
label.element.style.visibility = 'hidden'
|
||||
}
|
||||
return label
|
||||
}
|
||||
|
||||
return {
|
||||
initCSS2DRender,
|
||||
create2DTag
|
||||
}
|
||||
}
|
||||
68
datav/src/hooks/useConversionStandardData.js
Normal file
@@ -0,0 +1,68 @@
|
||||
export default function useConversionStandardData() {
|
||||
/**
|
||||
* 转换GeoJSON数据格式
|
||||
* 将Polygon类型转换为MultiPolygon格式
|
||||
* @param {Object} geoData - GeoJSON数据
|
||||
* @returns {Object} 转换后的数据
|
||||
*/
|
||||
const transfromGeoJSON = (geoData) => {
|
||||
if (!geoData || !geoData.features) {
|
||||
return geoData;
|
||||
}
|
||||
|
||||
const transformedFeatures = geoData.features.map(feature => {
|
||||
if (feature.geometry && feature.geometry.type === 'Polygon') {
|
||||
// 将Polygon转换为MultiPolygon格式
|
||||
return {
|
||||
...feature,
|
||||
geometry: {
|
||||
...feature.geometry,
|
||||
coordinates: [feature.geometry.coordinates] // 包装在数组中
|
||||
}
|
||||
};
|
||||
}
|
||||
return feature;
|
||||
});
|
||||
|
||||
return {
|
||||
...geoData,
|
||||
features: transformedFeatures
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 转换道路GeoJSON数据格式
|
||||
* 将LineString类型转换为MultiLineString格式
|
||||
* @param {Object} geoData - GeoJSON道路数据
|
||||
* @returns {Object} 转换后的数据
|
||||
*/
|
||||
const transformGeoRoad = (geoData) => {
|
||||
if (!geoData || !geoData.features) {
|
||||
return geoData;
|
||||
}
|
||||
|
||||
const transformedFeatures = geoData.features.map(feature => {
|
||||
if (feature.geometry && feature.geometry.type === 'LineString') {
|
||||
// 将LineString转换为MultiLineString格式
|
||||
return {
|
||||
...feature,
|
||||
geometry: {
|
||||
...feature.geometry,
|
||||
coordinates: [feature.geometry.coordinates] // 包装在数组中
|
||||
}
|
||||
};
|
||||
}
|
||||
return feature;
|
||||
});
|
||||
|
||||
return {
|
||||
...geoData,
|
||||
features: transformedFeatures
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
transfromGeoJSON,
|
||||
transformGeoRoad
|
||||
};
|
||||
}
|
||||
92
datav/src/hooks/useCoord.js
Normal file
@@ -0,0 +1,92 @@
|
||||
import * as THREE from 'three'
|
||||
const useCoord = () => {
|
||||
/**
|
||||
* 生成墨卡托坐标
|
||||
* @param {*} longitude 经度
|
||||
* @param {*} latitude 纬度
|
||||
* @returns
|
||||
*/
|
||||
// 墨卡托坐标系:展开地球,赤道作为x轴,向东为x轴正方,本初子午线作为y轴,向北为y轴正方向。
|
||||
// 数字20037508.34是地球赤道周长的一半:地球半径6378137米,赤道周长2*PI*r = 2 * 20037508.3427892,墨卡托坐标x轴区间[-20037508.3427892,20037508.3427892]
|
||||
const geoMercatorCoord = (longitude, latitude) => {
|
||||
var E = longitude
|
||||
var N = latitude
|
||||
var x = (E * 20037508.34) / 180
|
||||
var y = Math.log(Math.tan(((90 + N) * Math.PI) / 360)) / (Math.PI / 180)
|
||||
y = (y * 20037508.34) / 180
|
||||
return {
|
||||
x: x, //墨卡托x坐标——对应经度
|
||||
y: y, //墨卡托y坐标——对应维度
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 生成球面坐标
|
||||
* @param {*} R 球半径
|
||||
* @param {*} longitude 经度
|
||||
* @param {*} latitude 纬度
|
||||
* @returns
|
||||
*/
|
||||
const geoSphereCoord = (R, longitude, latitude) => {
|
||||
var lon = (longitude * Math.PI) / 180 //转弧度值
|
||||
var lat = (latitude * Math.PI) / 180 //转弧度值
|
||||
lon = -lon // three.js坐标系z坐标轴对应经度-90度,而不是90度
|
||||
|
||||
// 经纬度坐标转球面坐标计算公式
|
||||
var x = R * Math.cos(lat) * Math.cos(lon)
|
||||
var y = R * Math.sin(lat)
|
||||
var z = R * Math.cos(lat) * Math.sin(lon)
|
||||
// 返回球面坐标
|
||||
return {
|
||||
x: x,
|
||||
y: y,
|
||||
z: z,
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 计算包围盒
|
||||
* @param {*} group
|
||||
* @returns
|
||||
*/
|
||||
const getBoundingBox = group => {
|
||||
// 包围盒计算模型对象的大小和位置
|
||||
var box3 = new THREE.Box3()
|
||||
box3.expandByObject(group) // 计算模型包围盒
|
||||
var size = new THREE.Vector3()
|
||||
box3.getSize(size) // 计算包围盒尺寸
|
||||
var center = new THREE.Vector3()
|
||||
box3.getCenter(center) // 计算一个层级模型对应包围盒的几何体中心坐标
|
||||
return {
|
||||
box3,
|
||||
center,
|
||||
size,
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 设置网格的位置及姿态
|
||||
* @param {*} mesh
|
||||
* @param {*} R
|
||||
* @param {*} lon
|
||||
* @param {*} lat
|
||||
* @returns
|
||||
*/
|
||||
const setMeshQuaternion = (mesh, R, lon, lat) => {
|
||||
const { x, y, z } = geoSphereCoord(R, lon, lat)
|
||||
mesh.position.set(x, y, z)
|
||||
// 姿态设置
|
||||
// mesh在球面上的法线方向(球心和球面坐标构成的方向向量)
|
||||
let meshVector = new THREE.Vector3(x, y, z).normalize()
|
||||
// mesh默认在XOY平面上,法线方向沿着z轴new THREE.Vector3(0, 0, 1)
|
||||
let normal = new THREE.Vector3(0, 0, 1)
|
||||
// 四元数属性.quaternion表示mesh的角度状态
|
||||
//.setFromUnitVectors();计算两个向量之间构成的四元数值
|
||||
mesh.quaternion.setFromUnitVectors(normal, meshVector)
|
||||
return mesh
|
||||
}
|
||||
return {
|
||||
geoMercatorCoord,
|
||||
geoSphereCoord,
|
||||
getBoundingBox,
|
||||
setMeshQuaternion,
|
||||
}
|
||||
}
|
||||
export default useCoord
|
||||
143
datav/src/hooks/useCountry.js
Normal file
@@ -0,0 +1,143 @@
|
||||
import * as THREE from 'three';
|
||||
import { Line2 } from 'three/examples/jsm/lines/Line2.js';
|
||||
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js';
|
||||
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js';
|
||||
|
||||
export default function useCountry() {
|
||||
/**
|
||||
* 创建国家边界线
|
||||
* @param {Object} data - GeoJSON数据
|
||||
* @param {Object} options - 线条样式选项
|
||||
* @param {string} lineType - 线条类型 ('Line', 'LineLoop', 'LineSegments', 'Line2')
|
||||
* @returns {THREE.Object3D} 线条对象
|
||||
*/
|
||||
const createCountryFlatLine = (data, options = {}, lineType = 'Line') => {
|
||||
const group = new THREE.Group();
|
||||
|
||||
if (!data || !data.features) {
|
||||
return group;
|
||||
}
|
||||
|
||||
data.features.forEach(feature => {
|
||||
if (feature.geometry && feature.geometry.coordinates) {
|
||||
const coordinates = feature.geometry.coordinates;
|
||||
|
||||
coordinates.forEach(multiPolygon => {
|
||||
multiPolygon.forEach(polygon => {
|
||||
const points = [];
|
||||
|
||||
// 提取坐标点
|
||||
polygon.forEach(coord => {
|
||||
if (Array.isArray(coord) && coord.length >= 2) {
|
||||
points.push(new THREE.Vector3(coord[0], coord[1], 0));
|
||||
}
|
||||
});
|
||||
|
||||
if (points.length > 0) {
|
||||
const line = createLine(points, options, lineType);
|
||||
if (line) {
|
||||
group.add(line);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return group;
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建线条对象
|
||||
* @param {Array<THREE.Vector3>} points - 点数组
|
||||
* @param {Object} options - 样式选项
|
||||
* @param {string} lineType - 线条类型
|
||||
* @returns {THREE.Object3D} 线条对象
|
||||
*/
|
||||
const createLine = (points, options = {}, lineType = 'Line') => {
|
||||
if (!points || points.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const defaultOptions = {
|
||||
color: 0xffffff,
|
||||
linewidth: 0.001,
|
||||
transparent: true,
|
||||
opacity: 1,
|
||||
depthTest: true
|
||||
};
|
||||
|
||||
const finalOptions = { ...defaultOptions, ...options };
|
||||
|
||||
try {
|
||||
switch (lineType) {
|
||||
case 'Line2': {
|
||||
// 使用Line2创建更高质量的线条
|
||||
const geometry = new LineGeometry();
|
||||
const positions = [];
|
||||
|
||||
points.forEach(point => {
|
||||
positions.push(point.x, point.y, point.z);
|
||||
});
|
||||
|
||||
geometry.setPositions(positions);
|
||||
|
||||
const material = new LineMaterial({
|
||||
color: finalOptions.color,
|
||||
linewidth: finalOptions.linewidth,
|
||||
transparent: finalOptions.transparent,
|
||||
opacity: finalOptions.opacity,
|
||||
depthTest: finalOptions.depthTest
|
||||
});
|
||||
|
||||
// 设置分辨率
|
||||
material.resolution.set(window.innerWidth, window.innerHeight);
|
||||
|
||||
return new Line2(geometry, material);
|
||||
}
|
||||
|
||||
case 'LineLoop': {
|
||||
const geometry = new THREE.BufferGeometry().setFromPoints(points);
|
||||
const material = new THREE.LineBasicMaterial({
|
||||
color: finalOptions.color,
|
||||
transparent: finalOptions.transparent,
|
||||
opacity: finalOptions.opacity,
|
||||
depthTest: finalOptions.depthTest
|
||||
});
|
||||
return new THREE.LineLoop(geometry, material);
|
||||
}
|
||||
|
||||
case 'LineSegments': {
|
||||
const geometry = new THREE.BufferGeometry().setFromPoints(points);
|
||||
const material = new THREE.LineBasicMaterial({
|
||||
color: finalOptions.color,
|
||||
transparent: finalOptions.transparent,
|
||||
opacity: finalOptions.opacity,
|
||||
depthTest: finalOptions.depthTest
|
||||
});
|
||||
return new THREE.LineSegments(geometry, material);
|
||||
}
|
||||
|
||||
default: // 'Line'
|
||||
case 'Line': {
|
||||
const geometry = new THREE.BufferGeometry().setFromPoints(points);
|
||||
const material = new THREE.LineBasicMaterial({
|
||||
color: finalOptions.color,
|
||||
transparent: finalOptions.transparent,
|
||||
opacity: finalOptions.opacity,
|
||||
depthTest: finalOptions.depthTest
|
||||
});
|
||||
return new THREE.Line(geometry, material);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error creating line:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
createCountryFlatLine,
|
||||
createLine
|
||||
};
|
||||
}
|
||||
60
datav/src/hooks/useFileLoader.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import { ref } from 'vue';
|
||||
import * as THREE from 'three';
|
||||
|
||||
export default function useFileLoader() {
|
||||
const loading = ref(false);
|
||||
const progress = ref(0);
|
||||
const error = ref(null);
|
||||
|
||||
const requestData = async (url) => {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
progress.value = 0;
|
||||
|
||||
try {
|
||||
const loader = new THREE.FileLoader();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
loader.load(
|
||||
url,
|
||||
// onLoad
|
||||
(data) => {
|
||||
try {
|
||||
const jsonData = JSON.parse(data);
|
||||
loading.value = false;
|
||||
progress.value = 100;
|
||||
resolve(jsonData);
|
||||
} catch (parseError) {
|
||||
error.value = parseError;
|
||||
loading.value = false;
|
||||
reject(parseError);
|
||||
}
|
||||
},
|
||||
// onProgress
|
||||
(progressEvent) => {
|
||||
if (progressEvent.lengthComputable) {
|
||||
progress.value = (progressEvent.loaded / progressEvent.total) * 100;
|
||||
}
|
||||
},
|
||||
// onError
|
||||
(err) => {
|
||||
error.value = err;
|
||||
loading.value = false;
|
||||
reject(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
} catch (err) {
|
||||
error.value = err;
|
||||
loading.value = false;
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
loading,
|
||||
progress,
|
||||
error,
|
||||
requestData
|
||||
};
|
||||
}
|
||||
234
datav/src/hooks/useMapMarkedLightPillar.js
Normal file
@@ -0,0 +1,234 @@
|
||||
import * as THREE from 'three';
|
||||
import TWEEN from '@tweenjs/tween.js';
|
||||
|
||||
export default function useMapMarkedLightPillar(options = {}) {
|
||||
const defaultOptions = {
|
||||
scaleFactor: 1.0,
|
||||
color: 0x00ffff,
|
||||
opacity: 0.8
|
||||
};
|
||||
|
||||
const config = { ...defaultOptions, ...options };
|
||||
|
||||
/**
|
||||
* 创建标记点网格
|
||||
* @param {number} x - X坐标
|
||||
* @param {number} y - Y坐标
|
||||
* @param {number} scaleFactor - 缩放因子
|
||||
* @returns {THREE.Mesh} 标记点网格
|
||||
*/
|
||||
const createPointMesh = (x, y, scaleFactor = 1) => {
|
||||
const geometry = new THREE.SphereGeometry(0.05, 16, 16);
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
color: config.color,
|
||||
transparent: true,
|
||||
opacity: config.opacity
|
||||
});
|
||||
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
mesh.position.set(x, y, 0);
|
||||
mesh.scale.set(scaleFactor, scaleFactor, scaleFactor);
|
||||
|
||||
return mesh;
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建光晕效果
|
||||
* @param {number} x - X坐标
|
||||
* @param {number} y - Y坐标
|
||||
* @param {number} scaleFactor - 缩放因子
|
||||
* @returns {THREE.Mesh} 光晕网格
|
||||
*/
|
||||
const createLightHalo = (x, y, scaleFactor = 1) => {
|
||||
// 创建光晕纹理 - 使用单色替代渐变
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 256;
|
||||
canvas.height = 256;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// 绘制圆形光晕,中心实色,边缘透明
|
||||
ctx.fillStyle = 'rgba(0, 255, 255, 0.6)';
|
||||
ctx.beginPath();
|
||||
ctx.arc(128, 128, 64, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// 外圈较淡的圆
|
||||
ctx.fillStyle = 'rgba(0, 255, 255, 0.2)';
|
||||
ctx.beginPath();
|
||||
ctx.arc(128, 128, 100, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
const texture = new THREE.CanvasTexture(canvas);
|
||||
|
||||
const geometry = new THREE.PlaneGeometry(0.5, 0.5);
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
map: texture,
|
||||
transparent: true,
|
||||
opacity: 0.6,
|
||||
depthTest: false
|
||||
});
|
||||
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
mesh.position.set(x, y, 0.01);
|
||||
mesh.scale.set(scaleFactor, scaleFactor, scaleFactor);
|
||||
|
||||
// 添加缩放动画
|
||||
const animate = () => {
|
||||
new TWEEN.Tween(mesh.scale)
|
||||
.to({ x: scaleFactor * 1.5, y: scaleFactor * 1.5, z: scaleFactor * 1.5 }, 2000)
|
||||
.easing(TWEEN.Easing.Sinusoidal.InOut)
|
||||
.yoyo(true)
|
||||
.repeat(Infinity)
|
||||
.start();
|
||||
};
|
||||
|
||||
animate();
|
||||
|
||||
return mesh;
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建光柱
|
||||
* @param {number} x - X坐标
|
||||
* @param {number} y - Y坐标
|
||||
* @param {number} heightScaleFactor - 高度缩放因子
|
||||
* @returns {THREE.Group} 光柱组
|
||||
*/
|
||||
const createLightPillar = (x, y, heightScaleFactor = 1) => {
|
||||
const group = new THREE.Group();
|
||||
|
||||
// 创建光柱纹理 - 使用单色替代渐变
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 64;
|
||||
canvas.height = 256;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// 绘制实色光柱,中部较亮
|
||||
ctx.fillStyle = 'rgba(0, 255, 255, 0.8)';
|
||||
ctx.fillRect(0, 25, 64, 206); // 中部主体
|
||||
|
||||
// 上下边缘稍淡
|
||||
ctx.fillStyle = 'rgba(0, 255, 255, 0.4)';
|
||||
ctx.fillRect(0, 0, 64, 25); // 顶部
|
||||
ctx.fillRect(0, 231, 64, 25); // 底部
|
||||
|
||||
const texture = new THREE.CanvasTexture(canvas);
|
||||
|
||||
// 创建两个相交的平面形成光柱
|
||||
const height = 2 * heightScaleFactor;
|
||||
const width = 0.1;
|
||||
|
||||
// 第一个平面
|
||||
const geometry1 = new THREE.PlaneGeometry(width, height);
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
map: texture,
|
||||
transparent: true,
|
||||
opacity: 0.8,
|
||||
depthTest: false,
|
||||
side: THREE.DoubleSide
|
||||
});
|
||||
|
||||
const plane1 = new THREE.Mesh(geometry1, material);
|
||||
plane1.position.set(x, y, height / 2);
|
||||
|
||||
// 第二个平面(旋转90度)
|
||||
const plane2 = plane1.clone();
|
||||
plane2.rotation.z = Math.PI / 2;
|
||||
|
||||
// 底部光点
|
||||
const bottomMesh = createPointMesh(x, y, config.scaleFactor);
|
||||
|
||||
// 光晕效果
|
||||
const halo = createLightHalo(x, y, config.scaleFactor);
|
||||
|
||||
group.add(plane1);
|
||||
group.add(plane2);
|
||||
group.add(bottomMesh);
|
||||
group.add(halo);
|
||||
|
||||
return group;
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置光柱颜色
|
||||
* @param {THREE.Group} lightPillar - 光柱组
|
||||
* @param {number} color - 颜色值
|
||||
*/
|
||||
const setLightPillarColor = (lightPillar, color) => {
|
||||
lightPillar.children.forEach(child => {
|
||||
if (child.material) {
|
||||
child.material.color.setHex(color);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置网格四元数(用于球面定位)
|
||||
* @param {THREE.Object3D} mesh - 网格对象
|
||||
* @param {number} lng - 经度
|
||||
* @param {number} lat - 纬度
|
||||
* @param {number} radius - 半径
|
||||
*/
|
||||
const setMeshQuaternion = (mesh, lng, lat, radius = 1) => {
|
||||
const phi = (90 - lat) * (Math.PI / 180);
|
||||
const theta = (lng + 180) * (Math.PI / 180);
|
||||
|
||||
const x = -(radius * Math.sin(phi) * Math.cos(theta));
|
||||
const z = radius * Math.sin(phi) * Math.sin(theta);
|
||||
const y = radius * Math.cos(phi);
|
||||
|
||||
mesh.position.set(x, y, z);
|
||||
mesh.lookAt(0, 0, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取射线检测对象
|
||||
* @param {THREE.Camera} camera - 相机
|
||||
* @param {THREE.Vector2} mouse - 鼠标位置
|
||||
* @param {Array} objects - 检测对象数组
|
||||
* @returns {Array} 相交对象数组
|
||||
*/
|
||||
const getRaycasterObj = (camera, mouse, objects) => {
|
||||
const raycaster = new THREE.Raycaster();
|
||||
raycaster.setFromCamera(mouse, camera);
|
||||
return raycaster.intersectObjects(objects, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* 选择光柱
|
||||
* @param {Event} event - 鼠标事件
|
||||
* @param {THREE.Camera} camera - 相机
|
||||
* @param {Array} lightPillars - 光柱数组
|
||||
* @param {HTMLElement} container - 容器元素
|
||||
* @returns {Object|null} 选中的光柱信息
|
||||
*/
|
||||
const chooseLightPillar = (event, camera, lightPillars, container) => {
|
||||
const rect = container.getBoundingClientRect();
|
||||
const mouse = new THREE.Vector2();
|
||||
|
||||
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
|
||||
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
|
||||
|
||||
const intersects = getRaycasterObj(camera, mouse, lightPillars);
|
||||
|
||||
if (intersects.length > 0) {
|
||||
return {
|
||||
object: intersects[0].object,
|
||||
point: intersects[0].point,
|
||||
distance: intersects[0].distance
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return {
|
||||
createPointMesh,
|
||||
createLightHalo,
|
||||
createLightPillar,
|
||||
setLightPillarColor,
|
||||
setMeshQuaternion,
|
||||
getRaycasterObj,
|
||||
chooseLightPillar
|
||||
};
|
||||
}
|
||||
182
datav/src/hooks/useSequenceFrameAnimate.js
Normal file
@@ -0,0 +1,182 @@
|
||||
import * as THREE from 'three';
|
||||
|
||||
export default function useSequenceFrameAnimate() {
|
||||
/**
|
||||
* 创建序列帧动画
|
||||
* @param {Object} options - 配置选项
|
||||
* @param {string} options.image - 图片路径
|
||||
* @param {number} options.width - 图片宽度
|
||||
* @param {number} options.height - 图片高度
|
||||
* @param {number} options.frame - 总帧数
|
||||
* @param {number} options.column - 列数
|
||||
* @param {number} options.row - 行数
|
||||
* @param {number} options.speed - 播放速度
|
||||
* @returns {THREE.Mesh} 序列帧网格对象
|
||||
*/
|
||||
const createSequenceFrame = (options = {}) => {
|
||||
const {
|
||||
image,
|
||||
width = 256,
|
||||
height = 256,
|
||||
frame = 16,
|
||||
column = 4,
|
||||
row = 4,
|
||||
speed = 1
|
||||
} = options;
|
||||
|
||||
// 创建几何体
|
||||
const geometry = new THREE.PlaneGeometry(1, 1);
|
||||
|
||||
// 加载纹理
|
||||
const texture = new THREE.TextureLoader().load(image);
|
||||
texture.wrapS = THREE.RepeatWrapping;
|
||||
texture.wrapT = THREE.RepeatWrapping;
|
||||
|
||||
// 计算单帧的UV偏移
|
||||
const frameWidth = 1 / column;
|
||||
const frameHeight = 1 / row;
|
||||
|
||||
// 设置初始UV偏移
|
||||
texture.repeat.set(frameWidth, frameHeight);
|
||||
texture.offset.set(0, 1 - frameHeight); // 从顶部开始
|
||||
|
||||
// 创建材质
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
map: texture,
|
||||
transparent: true,
|
||||
alphaTest: 0.1,
|
||||
side: THREE.DoubleSide
|
||||
});
|
||||
|
||||
// 创建网格
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
|
||||
// 添加动画属性
|
||||
mesh.currentFrame = 0;
|
||||
mesh.frameCount = frame;
|
||||
mesh.column = column;
|
||||
mesh.row = row;
|
||||
mesh.speed = speed;
|
||||
mesh.frameWidth = frameWidth;
|
||||
mesh.frameHeight = frameHeight;
|
||||
mesh.frameTimer = 0;
|
||||
|
||||
// 更新序列帧的方法
|
||||
mesh.updateSequenceFrame = function() {
|
||||
this.frameTimer += this.speed;
|
||||
|
||||
if (this.frameTimer >= 60 / this.frameCount) { // 假设60FPS
|
||||
this.frameTimer = 0;
|
||||
this.currentFrame = (this.currentFrame + 1) % this.frameCount;
|
||||
|
||||
// 计算当前帧在纹理中的位置
|
||||
const col = this.currentFrame % this.column;
|
||||
const row = Math.floor(this.currentFrame / this.column);
|
||||
|
||||
// 更新UV偏移
|
||||
this.material.map.offset.set(
|
||||
col * this.frameWidth,
|
||||
1 - (row + 1) * this.frameHeight // 从顶部开始
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// 设置帧的方法
|
||||
mesh.setFrame = function(frameIndex) {
|
||||
if (frameIndex >= 0 && frameIndex < this.frameCount) {
|
||||
this.currentFrame = frameIndex;
|
||||
|
||||
const col = frameIndex % this.column;
|
||||
const row = Math.floor(frameIndex / this.column);
|
||||
|
||||
this.material.map.offset.set(
|
||||
col * this.frameWidth,
|
||||
1 - (row + 1) * this.frameHeight
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// 播放动画的方法
|
||||
mesh.play = function() {
|
||||
this.isPlaying = true;
|
||||
};
|
||||
|
||||
// 暂停动画的方法
|
||||
mesh.pause = function() {
|
||||
this.isPlaying = false;
|
||||
};
|
||||
|
||||
// 停止动画的方法
|
||||
mesh.stop = function() {
|
||||
this.isPlaying = false;
|
||||
this.currentFrame = 0;
|
||||
this.setFrame(0);
|
||||
};
|
||||
|
||||
// 设置播放速度的方法
|
||||
mesh.setSpeed = function(newSpeed) {
|
||||
this.speed = newSpeed;
|
||||
};
|
||||
|
||||
// 默认开始播放
|
||||
mesh.isPlaying = true;
|
||||
|
||||
return mesh;
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建简单的粒子序列帧动画
|
||||
* @param {Object} options - 配置选项
|
||||
* @returns {THREE.Mesh} 粒子网格对象
|
||||
*/
|
||||
const createParticleSequenceFrame = (options = {}) => {
|
||||
const {
|
||||
color = 0x00ffff,
|
||||
size = 0.1,
|
||||
opacity = 0.8
|
||||
} = options;
|
||||
|
||||
// 创建简单的粒子几何体
|
||||
const geometry = new THREE.SphereGeometry(size, 8, 8);
|
||||
|
||||
// 创建材质
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
color: color,
|
||||
transparent: true,
|
||||
opacity: opacity
|
||||
});
|
||||
|
||||
// 创建网格
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
|
||||
// 添加简单的闪烁动画
|
||||
mesh.animationTimer = 0;
|
||||
mesh.baseOpacity = opacity;
|
||||
|
||||
mesh.updateSequenceFrame = function() {
|
||||
this.animationTimer += 0.1;
|
||||
const opacity = this.baseOpacity * (0.5 + 0.5 * Math.sin(this.animationTimer));
|
||||
this.material.opacity = opacity;
|
||||
};
|
||||
|
||||
return mesh;
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量更新序列帧动画
|
||||
* @param {Array<THREE.Mesh>} meshes - 网格数组
|
||||
*/
|
||||
const updateSequenceFrames = (meshes) => {
|
||||
meshes.forEach(mesh => {
|
||||
if (mesh.updateSequenceFrame && mesh.isPlaying !== false) {
|
||||
mesh.updateSequenceFrame();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
createSequenceFrame,
|
||||
createParticleSequenceFrame,
|
||||
updateSequenceFrames
|
||||
};
|
||||
}
|
||||
4
datav/src/main.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).mount('#app')
|
||||
302
datav/src/utils/Earth.js
Normal file
@@ -0,0 +1,302 @@
|
||||
import * as THREE from 'three';
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
|
||||
import Stats from 'three/examples/jsm/libs/stats.module';
|
||||
import TWEEN from '@tweenjs/tween.js';
|
||||
import { deepMerge, isType } from '@/utils';
|
||||
|
||||
export default class Earth3d {
|
||||
constructor(options = {}) {
|
||||
let defaultOptions = {
|
||||
isFull: true,
|
||||
container: null,
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
bgColor: 0x000000,
|
||||
materialColor: 0xff0000,
|
||||
controls: {
|
||||
visibel: true, // 是否开启
|
||||
enableDamping: true, // 阻尼
|
||||
autoRotate: false, // 自动旋转
|
||||
maxPolarAngle: Math.PI, // 相机垂直旋转角度的上限
|
||||
},
|
||||
statsVisibel: true,
|
||||
axesVisibel: true,
|
||||
axesHelperSize: 250, // 左边尺寸
|
||||
};
|
||||
this.options = deepMerge(defaultOptions, options);
|
||||
this.container = document.querySelector(this.options.container);
|
||||
|
||||
// 确保容器尺寸有效,避免Canvas尺寸为0的错误
|
||||
this.options.width = Math.max(this.container.offsetWidth, 800);
|
||||
this.options.height = Math.max(this.container.offsetHeight, 600);
|
||||
|
||||
// 如果容器尺寸仍然为0,使用默认值
|
||||
if (this.options.width === 0 || this.options.height === 0) {
|
||||
this.options.width = 800;
|
||||
this.options.height = 600;
|
||||
}
|
||||
this.scene = new THREE.Scene(); // 场景
|
||||
this.camera = null; // 相机
|
||||
this.renderer = null; // 渲染器
|
||||
this.mesh = null; // 网格
|
||||
this.animationStop = null; // 用于停止动画
|
||||
this.controls = null; // 轨道控制器
|
||||
this.stats = null; // 统计
|
||||
|
||||
this.init();
|
||||
}
|
||||
init() {
|
||||
this.initStats();
|
||||
this.initCamera();
|
||||
this.initModel();
|
||||
this.initRenderer(); // 异步初始化,其他组件在continueInit中初始化
|
||||
}
|
||||
async initModel() {}
|
||||
|
||||
/**
|
||||
* 运行
|
||||
*/
|
||||
run() {
|
||||
// 如果渲染器已经准备好,直接开始循环
|
||||
if (this.renderer) {
|
||||
this.loop();
|
||||
} else {
|
||||
// 否则标记需要启动,等待渲染器创建完成
|
||||
this.shouldStart = true;
|
||||
}
|
||||
}
|
||||
// 循环
|
||||
loop() {
|
||||
// 检查渲染器是否存在
|
||||
if (!this.renderer) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.animationStop = window.requestAnimationFrame(() => {
|
||||
this.loop();
|
||||
});
|
||||
// 这里是你自己业务上需要的code
|
||||
this.renderer.render(this.scene, this.camera);
|
||||
// 控制相机旋转缩放的更新
|
||||
if (this.options.controls.visibel) this.controls.update();
|
||||
// 统计更新
|
||||
if (this.options.statsVisibel) this.stats.update();
|
||||
|
||||
TWEEN.update();
|
||||
}
|
||||
initCamera() {
|
||||
let { width, height } = this.options;
|
||||
let rate = width / height;
|
||||
// 设置45°的透视相机,更符合人眼观察
|
||||
this.camera = new THREE.PerspectiveCamera(45, rate, 0.1, 1500);
|
||||
// this.camera.position.set(-428.88, 861.97, -1438.0)
|
||||
this.camera.position.set(270.27, 173.24, 257.54);
|
||||
// this.camera.position.set(-102, 205, -342)
|
||||
|
||||
this.camera.lookAt(0, 0, 0);
|
||||
}
|
||||
/**
|
||||
* 初始化渲染器
|
||||
*/
|
||||
initRenderer() {
|
||||
let { width, height, bgColor } = this.options;
|
||||
|
||||
// 强制清理所有WebGL上下文
|
||||
if (this.renderer) {
|
||||
this.renderer.dispose();
|
||||
this.renderer.forceContextLoss();
|
||||
this.renderer = null;
|
||||
}
|
||||
|
||||
// 清理容器中现有的所有子元素
|
||||
while (this.container.firstChild) {
|
||||
this.container.removeChild(this.container.firstChild);
|
||||
}
|
||||
|
||||
// 强制垃圾回收
|
||||
if (window.gc) {
|
||||
window.gc();
|
||||
}
|
||||
|
||||
// 延迟创建新的渲染器,确保上下文完全释放
|
||||
setTimeout(() => {
|
||||
try {
|
||||
// 重新获取容器尺寸,确保有效
|
||||
const containerWidth = this.container.offsetWidth || window.innerWidth;
|
||||
const containerHeight = this.container.offsetHeight || window.innerHeight;
|
||||
|
||||
// 确保尺寸有效
|
||||
const validWidth = Math.max(containerWidth, 800);
|
||||
const validHeight = Math.max(containerHeight, 600);
|
||||
|
||||
// 更新options中的尺寸
|
||||
this.options.width = validWidth;
|
||||
this.options.height = validHeight;
|
||||
|
||||
// 创建一个新的canvas元素
|
||||
const canvas = document.createElement('canvas');
|
||||
let renderer = new THREE.WebGLRenderer({
|
||||
canvas: canvas,
|
||||
antialias: true,
|
||||
preserveDrawingBuffer: false,
|
||||
powerPreference: "high-performance"
|
||||
});
|
||||
|
||||
// 设置canvas的分辨率
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
// 设置canvas 的尺寸大小
|
||||
renderer.setSize(validWidth, validHeight);
|
||||
// 设置背景色
|
||||
renderer.setClearColor(bgColor, 1);
|
||||
// 设置canvas的z-index,确保CSS2D渲染器在其之上
|
||||
renderer.domElement.style.zIndex = '1';
|
||||
renderer.domElement.style.position = 'absolute';
|
||||
// 插入到dom中
|
||||
this.container.appendChild(renderer.domElement);
|
||||
this.renderer = renderer;
|
||||
|
||||
// 继续初始化其他组件
|
||||
this.continueInit();
|
||||
} catch (error) {
|
||||
console.error('WebGL渲染器创建失败:', error);
|
||||
// 创建一个错误提示
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.innerHTML = '3D渲染器初始化失败,请刷新页面重试';
|
||||
errorDiv.style.cssText = 'color: #ff6b6b; text-align: center; padding: 50px; font-size: 16px;';
|
||||
this.container.appendChild(errorDiv);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
continueInit() {
|
||||
// 原来在init方法中renderer初始化后的逻辑
|
||||
this.initLight();
|
||||
this.initStats();
|
||||
this.initControls();
|
||||
this.initAxes();
|
||||
|
||||
// 如果之前调用了run方法,现在启动循环
|
||||
if (this.shouldStart) {
|
||||
this.shouldStart = false;
|
||||
this.loop();
|
||||
}
|
||||
}
|
||||
initLight() {
|
||||
// 平行光1
|
||||
let directionalLight1 = new THREE.DirectionalLight(0xffffff, 0.6);
|
||||
directionalLight1.position.set(400, 200, 200);
|
||||
// 平行光2
|
||||
let directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.6);
|
||||
directionalLight2.position.set(-400, -200, -300);
|
||||
// 环境光
|
||||
let ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
|
||||
// 将光源添加到场景中
|
||||
this.addObject(directionalLight1);
|
||||
this.addObject(directionalLight2);
|
||||
this.addObject(ambientLight);
|
||||
}
|
||||
|
||||
initStats() {
|
||||
if (!this.options.statsVisibel) return false;
|
||||
|
||||
// 确保容器有有效的尺寸
|
||||
if (!this.container || this.container.offsetWidth === 0 || this.container.offsetHeight === 0) {
|
||||
console.warn('Container not ready for stats initialization, skipping...');
|
||||
return false;
|
||||
}
|
||||
|
||||
this.stats = new Stats();
|
||||
|
||||
// 确保stats的DOM元素有正确的尺寸
|
||||
if (this.stats.dom) {
|
||||
this.stats.dom.style.position = 'absolute';
|
||||
this.stats.dom.style.top = '0px';
|
||||
this.stats.dom.style.left = '0px';
|
||||
this.stats.dom.style.zIndex = '100';
|
||||
}
|
||||
|
||||
this.container.appendChild(this.stats.dom);
|
||||
}
|
||||
initControls() {
|
||||
try {
|
||||
let {
|
||||
controls: { enableDamping, autoRotate, visibel, maxPolarAngle },
|
||||
} = this.options;
|
||||
if (!visibel) return false;
|
||||
// 轨道控制器,使相机围绕目标进行轨道运动(旋转|缩放|平移)
|
||||
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
|
||||
this.controls.maxPolarAngle = maxPolarAngle;
|
||||
this.controls.autoRotate = autoRotate;
|
||||
this.controls.enableDamping = enableDamping;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
initAxes() {
|
||||
if (!this.options.axesVisibel) return false;
|
||||
var axes = new THREE.AxesHelper(this.options.axesHelperSize);
|
||||
this.addObject(axes);
|
||||
}
|
||||
|
||||
// 清空dom
|
||||
empty(elem) {
|
||||
while (elem && elem.lastChild) elem.removeChild(elem.lastChild);
|
||||
}
|
||||
/**
|
||||
* 添加对象到场景
|
||||
* @param {*} object {} []
|
||||
*/
|
||||
addObject(object) {
|
||||
if (isType('Array', object)) {
|
||||
this.scene.add(...object);
|
||||
} else {
|
||||
this.scene.add(object);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 移除对象
|
||||
* @param {*} object {} []
|
||||
*/
|
||||
removeObject(object) {
|
||||
if (isType('Array', object)) {
|
||||
object.map((item) => {
|
||||
item.geometry.dispose();
|
||||
});
|
||||
this.scene.remove(...object);
|
||||
} else {
|
||||
object.geometry.dispose();
|
||||
this.scene.remove(object);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 重置
|
||||
*/
|
||||
resize() {
|
||||
// 重新设置宽高
|
||||
let newWidth = this.container.offsetWidth || window.innerWidth;
|
||||
let newHeight = this.container.offsetHeight || window.innerHeight;
|
||||
|
||||
// 确保尺寸有效
|
||||
this.options.width = Math.max(newWidth, 800);
|
||||
this.options.height = Math.max(newHeight, 600);
|
||||
|
||||
if (this.renderer) {
|
||||
this.renderer.setSize(this.options.width, this.options.height);
|
||||
}
|
||||
// 重新设置相机的位置
|
||||
let rate = this.options.width / this.options.height;
|
||||
|
||||
// 必須設置相機的比例,重置的時候才不会变形
|
||||
this.camera.aspect = rate;
|
||||
|
||||
// 渲染器执行render方法的时候会读取相机对象的投影矩阵属性projectionMatrix
|
||||
// 但是不会每渲染一帧,就通过相机的属性计算投影矩阵(节约计算资源)
|
||||
// 如果相机的一些属性发生了变化,需要执行updateProjectionMatrix ()方法更新相机的投影矩阵
|
||||
this.camera.updateProjectionMatrix();
|
||||
|
||||
// 如果stats还没有初始化(可能之前容器尺寸为0),现在重新尝试初始化
|
||||
if (this.options.statsVisibel && !this.stats) {
|
||||
this.initStats();
|
||||
}
|
||||
}
|
||||
}
|
||||
121
datav/src/utils/index.js
Normal file
@@ -0,0 +1,121 @@
|
||||
// 引入Three.js
|
||||
export { default as Earth3d } from './Earth.js';
|
||||
|
||||
/**
|
||||
* 经纬度坐标转球面坐标
|
||||
* @param {地球半径} R
|
||||
* @param {经度(角度值)} longitude
|
||||
* @param {维度(角度值)} latitude
|
||||
*/
|
||||
export function lon2xyz(R, longitude, latitude) {
|
||||
var lon = (longitude * Math.PI) / 180; //转弧度值
|
||||
var lat = (latitude * Math.PI) / 180; //转弧度值
|
||||
lon = -lon; // three.js坐标系z坐标轴对应经度-90度,而不是90度
|
||||
|
||||
// 经纬度坐标转球面坐标计算公式
|
||||
var x = R * Math.cos(lat) * Math.cos(lon);
|
||||
var y = R * Math.sin(lat);
|
||||
var z = R * Math.cos(lat) * Math.sin(lon);
|
||||
// 返回球面坐标
|
||||
return {
|
||||
x: x,
|
||||
y: y,
|
||||
z: z,
|
||||
};
|
||||
}
|
||||
|
||||
export const isType = function (type, value) {
|
||||
return Object.prototype.toString.call(value) === `[object ${type}]`;
|
||||
};
|
||||
/**
|
||||
* 判断是否为对象
|
||||
*/
|
||||
export const isObject = function (value) {
|
||||
return isType('Object', value);
|
||||
};
|
||||
/**
|
||||
* @description deepClone() 深拷贝-最终版:解决循环引用的问题
|
||||
* @param {*} target 对象
|
||||
* @example
|
||||
* const obj1 = {
|
||||
* a: 1,
|
||||
* b: ["e", "f", "g"],
|
||||
* c: { h: { i: 2 } },
|
||||
* };
|
||||
* obj1.b.push(obj1.c);
|
||||
* obj1.c.j = obj1.b;
|
||||
*
|
||||
* const obj2 = deepClone(obj1);
|
||||
* obj2.b.push("h");
|
||||
* console.log(obj1, obj2);
|
||||
* console.log(obj2.c === obj1.c);
|
||||
*/
|
||||
export function deepClone(target, map = new Map()) {
|
||||
// target 不能为空,并且是一个对象
|
||||
if (target != null && isObject(target)) {
|
||||
// 在克隆数据前,进行判断是否克隆过,已克隆就返回克隆的值
|
||||
let cache = map.get(target);
|
||||
if (cache) {
|
||||
return cache;
|
||||
}
|
||||
// 判断是否为数组
|
||||
const isArray = Array.isArray(target);
|
||||
let result = isArray ? [] : {};
|
||||
// 将新结果存入缓存中
|
||||
cache = map.set(target, result);
|
||||
// 如果是数组
|
||||
if (isArray) {
|
||||
// 循环数组,
|
||||
target.forEach((item, index) => {
|
||||
// 如果item是对象,再次递归
|
||||
result[index] = deepClone(item, cache);
|
||||
});
|
||||
} else {
|
||||
// 如果是对象
|
||||
Object.keys(target).forEach((key) => {
|
||||
if (isObject(result[key])) {
|
||||
result[key] = deepClone(target[key], cache);
|
||||
} else {
|
||||
result[key] = target[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
export function deepMerge(target, source) {
|
||||
target = deepClone(target);
|
||||
for (let key in source) {
|
||||
if (key in target) {
|
||||
// 对象的处理
|
||||
if (isObject(source[key])) {
|
||||
if (!isObject(target[key])) {
|
||||
target[key] = source[key];
|
||||
} else {
|
||||
target[key] = deepMerge(target[key], source[key]);
|
||||
}
|
||||
} else {
|
||||
target[key] = source[key];
|
||||
}
|
||||
} else {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
// 随机数
|
||||
export const random = function (min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
};
|
||||
/**
|
||||
* 获取样式
|
||||
* @param {*} el
|
||||
* @param {*} ruleName
|
||||
* @returns
|
||||
*/
|
||||
export function getStyle(el, ruleName) {
|
||||
return window.getComputedStyle(el)[ruleName];
|
||||
}
|
||||
7
datav/src/utils/lodash/_DataView.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import getNative from './_getNative.js';
|
||||
import root from './_root.js';
|
||||
|
||||
/* Built-in method references that are verified to be native. */
|
||||
var DataView = getNative(root, 'DataView');
|
||||
|
||||
export default DataView;
|
||||
32
datav/src/utils/lodash/_Hash.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import hashClear from './_hashClear.js';
|
||||
import hashDelete from './_hashDelete.js';
|
||||
import hashGet from './_hashGet.js';
|
||||
import hashHas from './_hashHas.js';
|
||||
import hashSet from './_hashSet.js';
|
||||
|
||||
/**
|
||||
* Creates a hash object.
|
||||
*
|
||||
* @private
|
||||
* @constructor
|
||||
* @param {Array} [entries] The key-value pairs to cache.
|
||||
*/
|
||||
function Hash(entries) {
|
||||
var index = -1,
|
||||
length = entries == null ? 0 : entries.length;
|
||||
|
||||
this.clear();
|
||||
while (++index < length) {
|
||||
var entry = entries[index];
|
||||
this.set(entry[0], entry[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Add methods to `Hash`.
|
||||
Hash.prototype.clear = hashClear;
|
||||
Hash.prototype['delete'] = hashDelete;
|
||||
Hash.prototype.get = hashGet;
|
||||
Hash.prototype.has = hashHas;
|
||||
Hash.prototype.set = hashSet;
|
||||
|
||||
export default Hash;
|
||||
28
datav/src/utils/lodash/_LazyWrapper.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import baseCreate from './_baseCreate.js';
|
||||
import baseLodash from './_baseLodash.js';
|
||||
|
||||
/** Used as references for the maximum length and index of an array. */
|
||||
var MAX_ARRAY_LENGTH = 4294967295;
|
||||
|
||||
/**
|
||||
* Creates a lazy wrapper object which wraps `value` to enable lazy evaluation.
|
||||
*
|
||||
* @private
|
||||
* @constructor
|
||||
* @param {*} value The value to wrap.
|
||||
*/
|
||||
function LazyWrapper(value) {
|
||||
this.__wrapped__ = value;
|
||||
this.__actions__ = [];
|
||||
this.__dir__ = 1;
|
||||
this.__filtered__ = false;
|
||||
this.__iteratees__ = [];
|
||||
this.__takeCount__ = MAX_ARRAY_LENGTH;
|
||||
this.__views__ = [];
|
||||
}
|
||||
|
||||
// Ensure `LazyWrapper` is an instance of `baseLodash`.
|
||||
LazyWrapper.prototype = baseCreate(baseLodash.prototype);
|
||||
LazyWrapper.prototype.constructor = LazyWrapper;
|
||||
|
||||
export default LazyWrapper;
|
||||
32
datav/src/utils/lodash/_ListCache.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import listCacheClear from './_listCacheClear.js';
|
||||
import listCacheDelete from './_listCacheDelete.js';
|
||||
import listCacheGet from './_listCacheGet.js';
|
||||
import listCacheHas from './_listCacheHas.js';
|
||||
import listCacheSet from './_listCacheSet.js';
|
||||
|
||||
/**
|
||||
* Creates an list cache object.
|
||||
*
|
||||
* @private
|
||||
* @constructor
|
||||
* @param {Array} [entries] The key-value pairs to cache.
|
||||
*/
|
||||
function ListCache(entries) {
|
||||
var index = -1,
|
||||
length = entries == null ? 0 : entries.length;
|
||||
|
||||
this.clear();
|
||||
while (++index < length) {
|
||||
var entry = entries[index];
|
||||
this.set(entry[0], entry[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Add methods to `ListCache`.
|
||||
ListCache.prototype.clear = listCacheClear;
|
||||
ListCache.prototype['delete'] = listCacheDelete;
|
||||
ListCache.prototype.get = listCacheGet;
|
||||
ListCache.prototype.has = listCacheHas;
|
||||
ListCache.prototype.set = listCacheSet;
|
||||
|
||||
export default ListCache;
|
||||
22
datav/src/utils/lodash/_LodashWrapper.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import baseCreate from './_baseCreate.js';
|
||||
import baseLodash from './_baseLodash.js';
|
||||
|
||||
/**
|
||||
* The base constructor for creating `lodash` wrapper objects.
|
||||
*
|
||||
* @private
|
||||
* @param {*} value The value to wrap.
|
||||
* @param {boolean} [chainAll] Enable explicit method chain sequences.
|
||||
*/
|
||||
function LodashWrapper(value, chainAll) {
|
||||
this.__wrapped__ = value;
|
||||
this.__actions__ = [];
|
||||
this.__chain__ = !!chainAll;
|
||||
this.__index__ = 0;
|
||||
this.__values__ = undefined;
|
||||
}
|
||||
|
||||
LodashWrapper.prototype = baseCreate(baseLodash.prototype);
|
||||
LodashWrapper.prototype.constructor = LodashWrapper;
|
||||
|
||||
export default LodashWrapper;
|
||||
7
datav/src/utils/lodash/_Map.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import getNative from './_getNative.js';
|
||||
import root from './_root.js';
|
||||
|
||||
/* Built-in method references that are verified to be native. */
|
||||
var Map = getNative(root, 'Map');
|
||||
|
||||
export default Map;
|
||||
32
datav/src/utils/lodash/_MapCache.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import mapCacheClear from './_mapCacheClear.js';
|
||||
import mapCacheDelete from './_mapCacheDelete.js';
|
||||
import mapCacheGet from './_mapCacheGet.js';
|
||||
import mapCacheHas from './_mapCacheHas.js';
|
||||
import mapCacheSet from './_mapCacheSet.js';
|
||||
|
||||
/**
|
||||
* Creates a map cache object to store key-value pairs.
|
||||
*
|
||||
* @private
|
||||
* @constructor
|
||||
* @param {Array} [entries] The key-value pairs to cache.
|
||||
*/
|
||||
function MapCache(entries) {
|
||||
var index = -1,
|
||||
length = entries == null ? 0 : entries.length;
|
||||
|
||||
this.clear();
|
||||
while (++index < length) {
|
||||
var entry = entries[index];
|
||||
this.set(entry[0], entry[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Add methods to `MapCache`.
|
||||
MapCache.prototype.clear = mapCacheClear;
|
||||
MapCache.prototype['delete'] = mapCacheDelete;
|
||||
MapCache.prototype.get = mapCacheGet;
|
||||
MapCache.prototype.has = mapCacheHas;
|
||||
MapCache.prototype.set = mapCacheSet;
|
||||
|
||||
export default MapCache;
|
||||
7
datav/src/utils/lodash/_Promise.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import getNative from './_getNative.js';
|
||||
import root from './_root.js';
|
||||
|
||||
/* Built-in method references that are verified to be native. */
|
||||
var Promise = getNative(root, 'Promise');
|
||||
|
||||
export default Promise;
|
||||
7
datav/src/utils/lodash/_Set.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import getNative from './_getNative.js';
|
||||
import root from './_root.js';
|
||||
|
||||
/* Built-in method references that are verified to be native. */
|
||||
var Set = getNative(root, 'Set');
|
||||
|
||||
export default Set;
|
||||
27
datav/src/utils/lodash/_SetCache.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import MapCache from './_MapCache.js';
|
||||
import setCacheAdd from './_setCacheAdd.js';
|
||||
import setCacheHas from './_setCacheHas.js';
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates an array cache object to store unique values.
|
||||
*
|
||||
* @private
|
||||
* @constructor
|
||||
* @param {Array} [values] The values to cache.
|
||||
*/
|
||||
function SetCache(values) {
|
||||
var index = -1,
|
||||
length = values == null ? 0 : values.length;
|
||||
|
||||
this.__data__ = new MapCache;
|
||||
while (++index < length) {
|
||||
this.add(values[index]);
|
||||
}
|
||||
}
|
||||
|
||||
// Add methods to `SetCache`.
|
||||
SetCache.prototype.add = SetCache.prototype.push = setCacheAdd;
|
||||
SetCache.prototype.has = setCacheHas;
|
||||
|
||||
export default SetCache;
|
||||
27
datav/src/utils/lodash/_Stack.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import ListCache from './_ListCache.js';
|
||||
import stackClear from './_stackClear.js';
|
||||
import stackDelete from './_stackDelete.js';
|
||||
import stackGet from './_stackGet.js';
|
||||
import stackHas from './_stackHas.js';
|
||||
import stackSet from './_stackSet.js';
|
||||
|
||||
/**
|
||||
* Creates a stack cache object to store key-value pairs.
|
||||
*
|
||||
* @private
|
||||
* @constructor
|
||||
* @param {Array} [entries] The key-value pairs to cache.
|
||||
*/
|
||||
function Stack(entries) {
|
||||
var data = this.__data__ = new ListCache(entries);
|
||||
this.size = data.size;
|
||||
}
|
||||
|
||||
// Add methods to `Stack`.
|
||||
Stack.prototype.clear = stackClear;
|
||||
Stack.prototype['delete'] = stackDelete;
|
||||
Stack.prototype.get = stackGet;
|
||||
Stack.prototype.has = stackHas;
|
||||
Stack.prototype.set = stackSet;
|
||||
|
||||
export default Stack;
|
||||
6
datav/src/utils/lodash/_Symbol.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import root from './_root.js';
|
||||
|
||||
/** Built-in value references. */
|
||||
var Symbol = root.Symbol;
|
||||
|
||||
export default Symbol;
|
||||
6
datav/src/utils/lodash/_Uint8Array.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import root from './_root.js';
|
||||
|
||||
/** Built-in value references. */
|
||||
var Uint8Array = root.Uint8Array;
|
||||
|
||||
export default Uint8Array;
|
||||
7
datav/src/utils/lodash/_WeakMap.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import getNative from './_getNative.js';
|
||||
import root from './_root.js';
|
||||
|
||||
/* Built-in method references that are verified to be native. */
|
||||
var WeakMap = getNative(root, 'WeakMap');
|
||||
|
||||
export default WeakMap;
|
||||
21
datav/src/utils/lodash/_apply.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* A faster alternative to `Function#apply`, this function invokes `func`
|
||||
* with the `this` binding of `thisArg` and the arguments of `args`.
|
||||
*
|
||||
* @private
|
||||
* @param {Function} func The function to invoke.
|
||||
* @param {*} thisArg The `this` binding of `func`.
|
||||
* @param {Array} args The arguments to invoke `func` with.
|
||||
* @returns {*} Returns the result of `func`.
|
||||
*/
|
||||
function apply(func, thisArg, args) {
|
||||
switch (args.length) {
|
||||
case 0: return func.call(thisArg);
|
||||
case 1: return func.call(thisArg, args[0]);
|
||||
case 2: return func.call(thisArg, args[0], args[1]);
|
||||
case 3: return func.call(thisArg, args[0], args[1], args[2]);
|
||||
}
|
||||
return func.apply(thisArg, args);
|
||||
}
|
||||
|
||||
export default apply;
|
||||
22
datav/src/utils/lodash/_arrayAggregator.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* A specialized version of `baseAggregator` for arrays.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} [array] The array to iterate over.
|
||||
* @param {Function} setter The function to set `accumulator` values.
|
||||
* @param {Function} iteratee The iteratee to transform keys.
|
||||
* @param {Object} accumulator The initial aggregated object.
|
||||
* @returns {Function} Returns `accumulator`.
|
||||
*/
|
||||
function arrayAggregator(array, setter, iteratee, accumulator) {
|
||||
var index = -1,
|
||||
length = array == null ? 0 : array.length;
|
||||
|
||||
while (++index < length) {
|
||||
var value = array[index];
|
||||
setter(accumulator, value, iteratee(value), array);
|
||||
}
|
||||
return accumulator;
|
||||
}
|
||||
|
||||
export default arrayAggregator;
|
||||
22
datav/src/utils/lodash/_arrayEach.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* A specialized version of `_.forEach` for arrays without support for
|
||||
* iteratee shorthands.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} [array] The array to iterate over.
|
||||
* @param {Function} iteratee The function invoked per iteration.
|
||||
* @returns {Array} Returns `array`.
|
||||
*/
|
||||
function arrayEach(array, iteratee) {
|
||||
var index = -1,
|
||||
length = array == null ? 0 : array.length;
|
||||
|
||||
while (++index < length) {
|
||||
if (iteratee(array[index], index, array) === false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
export default arrayEach;
|
||||
21
datav/src/utils/lodash/_arrayEachRight.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* A specialized version of `_.forEachRight` for arrays without support for
|
||||
* iteratee shorthands.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} [array] The array to iterate over.
|
||||
* @param {Function} iteratee The function invoked per iteration.
|
||||
* @returns {Array} Returns `array`.
|
||||
*/
|
||||
function arrayEachRight(array, iteratee) {
|
||||
var length = array == null ? 0 : array.length;
|
||||
|
||||
while (length--) {
|
||||
if (iteratee(array[length], length, array) === false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
export default arrayEachRight;
|
||||
23
datav/src/utils/lodash/_arrayEvery.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* A specialized version of `_.every` for arrays without support for
|
||||
* iteratee shorthands.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} [array] The array to iterate over.
|
||||
* @param {Function} predicate The function invoked per iteration.
|
||||
* @returns {boolean} Returns `true` if all elements pass the predicate check,
|
||||
* else `false`.
|
||||
*/
|
||||
function arrayEvery(array, predicate) {
|
||||
var index = -1,
|
||||
length = array == null ? 0 : array.length;
|
||||
|
||||
while (++index < length) {
|
||||
if (!predicate(array[index], index, array)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export default arrayEvery;
|
||||
25
datav/src/utils/lodash/_arrayFilter.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* A specialized version of `_.filter` for arrays without support for
|
||||
* iteratee shorthands.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} [array] The array to iterate over.
|
||||
* @param {Function} predicate The function invoked per iteration.
|
||||
* @returns {Array} Returns the new filtered array.
|
||||
*/
|
||||
function arrayFilter(array, predicate) {
|
||||
var index = -1,
|
||||
length = array == null ? 0 : array.length,
|
||||
resIndex = 0,
|
||||
result = [];
|
||||
|
||||
while (++index < length) {
|
||||
var value = array[index];
|
||||
if (predicate(value, index, array)) {
|
||||
result[resIndex++] = value;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export default arrayFilter;
|
||||
17
datav/src/utils/lodash/_arrayIncludes.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import baseIndexOf from './_baseIndexOf.js';
|
||||
|
||||
/**
|
||||
* A specialized version of `_.includes` for arrays without support for
|
||||
* specifying an index to search from.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} [array] The array to inspect.
|
||||
* @param {*} target The value to search for.
|
||||
* @returns {boolean} Returns `true` if `target` is found, else `false`.
|
||||
*/
|
||||
function arrayIncludes(array, value) {
|
||||
var length = array == null ? 0 : array.length;
|
||||
return !!length && baseIndexOf(array, value, 0) > -1;
|
||||
}
|
||||
|
||||
export default arrayIncludes;
|
||||
22
datav/src/utils/lodash/_arrayIncludesWith.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* This function is like `arrayIncludes` except that it accepts a comparator.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} [array] The array to inspect.
|
||||
* @param {*} target The value to search for.
|
||||
* @param {Function} comparator The comparator invoked per element.
|
||||
* @returns {boolean} Returns `true` if `target` is found, else `false`.
|
||||
*/
|
||||
function arrayIncludesWith(array, value, comparator) {
|
||||
var index = -1,
|
||||
length = array == null ? 0 : array.length;
|
||||
|
||||
while (++index < length) {
|
||||
if (comparator(value, array[index])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export default arrayIncludesWith;
|
||||
49
datav/src/utils/lodash/_arrayLikeKeys.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import baseTimes from './_baseTimes.js';
|
||||
import isArguments from './isArguments.js';
|
||||
import isArray from './isArray.js';
|
||||
import isBuffer from './isBuffer.js';
|
||||
import isIndex from './_isIndex.js';
|
||||
import isTypedArray from './isTypedArray.js';
|
||||
|
||||
/** Used for built-in method references. */
|
||||
var objectProto = Object.prototype;
|
||||
|
||||
/** Used to check objects for own properties. */
|
||||
var hasOwnProperty = objectProto.hasOwnProperty;
|
||||
|
||||
/**
|
||||
* Creates an array of the enumerable property names of the array-like `value`.
|
||||
*
|
||||
* @private
|
||||
* @param {*} value The value to query.
|
||||
* @param {boolean} inherited Specify returning inherited property names.
|
||||
* @returns {Array} Returns the array of property names.
|
||||
*/
|
||||
function arrayLikeKeys(value, inherited) {
|
||||
var isArr = isArray(value),
|
||||
isArg = !isArr && isArguments(value),
|
||||
isBuff = !isArr && !isArg && isBuffer(value),
|
||||
isType = !isArr && !isArg && !isBuff && isTypedArray(value),
|
||||
skipIndexes = isArr || isArg || isBuff || isType,
|
||||
result = skipIndexes ? baseTimes(value.length, String) : [],
|
||||
length = result.length;
|
||||
|
||||
for (var key in value) {
|
||||
if ((inherited || hasOwnProperty.call(value, key)) &&
|
||||
!(skipIndexes && (
|
||||
// Safari 9 has enumerable `arguments.length` in strict mode.
|
||||
key == 'length' ||
|
||||
// Node.js 0.10 has enumerable non-index properties on buffers.
|
||||
(isBuff && (key == 'offset' || key == 'parent')) ||
|
||||
// PhantomJS 2 has enumerable non-index properties on typed arrays.
|
||||
(isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) ||
|
||||
// Skip index properties.
|
||||
isIndex(key, length)
|
||||
))) {
|
||||
result.push(key);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export default arrayLikeKeys;
|
||||
21
datav/src/utils/lodash/_arrayMap.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* A specialized version of `_.map` for arrays without support for iteratee
|
||||
* shorthands.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} [array] The array to iterate over.
|
||||
* @param {Function} iteratee The function invoked per iteration.
|
||||
* @returns {Array} Returns the new mapped array.
|
||||
*/
|
||||
function arrayMap(array, iteratee) {
|
||||
var index = -1,
|
||||
length = array == null ? 0 : array.length,
|
||||
result = Array(length);
|
||||
|
||||
while (++index < length) {
|
||||
result[index] = iteratee(array[index], index, array);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export default arrayMap;
|
||||
20
datav/src/utils/lodash/_arrayPush.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Appends the elements of `values` to `array`.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} array The array to modify.
|
||||
* @param {Array} values The values to append.
|
||||
* @returns {Array} Returns `array`.
|
||||
*/
|
||||
function arrayPush(array, values) {
|
||||
var index = -1,
|
||||
length = values.length,
|
||||
offset = array.length;
|
||||
|
||||
while (++index < length) {
|
||||
array[offset + index] = values[index];
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
export default arrayPush;
|
||||
26
datav/src/utils/lodash/_arrayReduce.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* A specialized version of `_.reduce` for arrays without support for
|
||||
* iteratee shorthands.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} [array] The array to iterate over.
|
||||
* @param {Function} iteratee The function invoked per iteration.
|
||||
* @param {*} [accumulator] The initial value.
|
||||
* @param {boolean} [initAccum] Specify using the first element of `array` as
|
||||
* the initial value.
|
||||
* @returns {*} Returns the accumulated value.
|
||||
*/
|
||||
function arrayReduce(array, iteratee, accumulator, initAccum) {
|
||||
var index = -1,
|
||||
length = array == null ? 0 : array.length;
|
||||
|
||||
if (initAccum && length) {
|
||||
accumulator = array[++index];
|
||||
}
|
||||
while (++index < length) {
|
||||
accumulator = iteratee(accumulator, array[index], index, array);
|
||||
}
|
||||
return accumulator;
|
||||
}
|
||||
|
||||
export default arrayReduce;
|
||||
24
datav/src/utils/lodash/_arrayReduceRight.js
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* A specialized version of `_.reduceRight` for arrays without support for
|
||||
* iteratee shorthands.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} [array] The array to iterate over.
|
||||
* @param {Function} iteratee The function invoked per iteration.
|
||||
* @param {*} [accumulator] The initial value.
|
||||
* @param {boolean} [initAccum] Specify using the last element of `array` as
|
||||
* the initial value.
|
||||
* @returns {*} Returns the accumulated value.
|
||||
*/
|
||||
function arrayReduceRight(array, iteratee, accumulator, initAccum) {
|
||||
var length = array == null ? 0 : array.length;
|
||||
if (initAccum && length) {
|
||||
accumulator = array[--length];
|
||||
}
|
||||
while (length--) {
|
||||
accumulator = iteratee(accumulator, array[length], length, array);
|
||||
}
|
||||
return accumulator;
|
||||
}
|
||||
|
||||
export default arrayReduceRight;
|
||||
15
datav/src/utils/lodash/_arraySample.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import baseRandom from './_baseRandom.js';
|
||||
|
||||
/**
|
||||
* A specialized version of `_.sample` for arrays.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} array The array to sample.
|
||||
* @returns {*} Returns the random element.
|
||||
*/
|
||||
function arraySample(array) {
|
||||
var length = array.length;
|
||||
return length ? array[baseRandom(0, length - 1)] : undefined;
|
||||
}
|
||||
|
||||
export default arraySample;
|
||||
17
datav/src/utils/lodash/_arraySampleSize.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import baseClamp from './_baseClamp.js';
|
||||
import copyArray from './_copyArray.js';
|
||||
import shuffleSelf from './_shuffleSelf.js';
|
||||
|
||||
/**
|
||||
* A specialized version of `_.sampleSize` for arrays.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} array The array to sample.
|
||||
* @param {number} n The number of elements to sample.
|
||||
* @returns {Array} Returns the random elements.
|
||||
*/
|
||||
function arraySampleSize(array, n) {
|
||||
return shuffleSelf(copyArray(array), baseClamp(n, 0, array.length));
|
||||
}
|
||||
|
||||
export default arraySampleSize;
|
||||
15
datav/src/utils/lodash/_arrayShuffle.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import copyArray from './_copyArray.js';
|
||||
import shuffleSelf from './_shuffleSelf.js';
|
||||
|
||||
/**
|
||||
* A specialized version of `_.shuffle` for arrays.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} array The array to shuffle.
|
||||
* @returns {Array} Returns the new shuffled array.
|
||||
*/
|
||||
function arrayShuffle(array) {
|
||||
return shuffleSelf(copyArray(array));
|
||||
}
|
||||
|
||||
export default arrayShuffle;
|
||||
23
datav/src/utils/lodash/_arraySome.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* A specialized version of `_.some` for arrays without support for iteratee
|
||||
* shorthands.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} [array] The array to iterate over.
|
||||
* @param {Function} predicate The function invoked per iteration.
|
||||
* @returns {boolean} Returns `true` if any element passes the predicate check,
|
||||
* else `false`.
|
||||
*/
|
||||
function arraySome(array, predicate) {
|
||||
var index = -1,
|
||||
length = array == null ? 0 : array.length;
|
||||
|
||||
while (++index < length) {
|
||||
if (predicate(array[index], index, array)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export default arraySome;
|
||||
12
datav/src/utils/lodash/_asciiSize.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import baseProperty from './_baseProperty.js';
|
||||
|
||||
/**
|
||||
* Gets the size of an ASCII `string`.
|
||||
*
|
||||
* @private
|
||||
* @param {string} string The string inspect.
|
||||
* @returns {number} Returns the string size.
|
||||
*/
|
||||
var asciiSize = baseProperty('length');
|
||||
|
||||
export default asciiSize;
|
||||
12
datav/src/utils/lodash/_asciiToArray.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Converts an ASCII `string` to an array.
|
||||
*
|
||||
* @private
|
||||
* @param {string} string The string to convert.
|
||||
* @returns {Array} Returns the converted array.
|
||||
*/
|
||||
function asciiToArray(string) {
|
||||
return string.split('');
|
||||
}
|
||||
|
||||
export default asciiToArray;
|
||||
15
datav/src/utils/lodash/_asciiWords.js
Normal file
@@ -0,0 +1,15 @@
|
||||
/** Used to match words composed of alphanumeric characters. */
|
||||
var reAsciiWord = /[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;
|
||||
|
||||
/**
|
||||
* Splits an ASCII `string` into an array of its words.
|
||||
*
|
||||
* @private
|
||||
* @param {string} The string to inspect.
|
||||
* @returns {Array} Returns the words of `string`.
|
||||
*/
|
||||
function asciiWords(string) {
|
||||
return string.match(reAsciiWord) || [];
|
||||
}
|
||||
|
||||
export default asciiWords;
|
||||
20
datav/src/utils/lodash/_assignMergeValue.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import baseAssignValue from './_baseAssignValue.js';
|
||||
import eq from './eq.js';
|
||||
|
||||
/**
|
||||
* This function is like `assignValue` except that it doesn't assign
|
||||
* `undefined` values.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} object The object to modify.
|
||||
* @param {string} key The key of the property to assign.
|
||||
* @param {*} value The value to assign.
|
||||
*/
|
||||
function assignMergeValue(object, key, value) {
|
||||
if ((value !== undefined && !eq(object[key], value)) ||
|
||||
(value === undefined && !(key in object))) {
|
||||
baseAssignValue(object, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
export default assignMergeValue;
|
||||
28
datav/src/utils/lodash/_assignValue.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import baseAssignValue from './_baseAssignValue.js';
|
||||
import eq from './eq.js';
|
||||
|
||||
/** Used for built-in method references. */
|
||||
var objectProto = Object.prototype;
|
||||
|
||||
/** Used to check objects for own properties. */
|
||||
var hasOwnProperty = objectProto.hasOwnProperty;
|
||||
|
||||
/**
|
||||
* Assigns `value` to `key` of `object` if the existing value is not equivalent
|
||||
* using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
|
||||
* for equality comparisons.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} object The object to modify.
|
||||
* @param {string} key The key of the property to assign.
|
||||
* @param {*} value The value to assign.
|
||||
*/
|
||||
function assignValue(object, key, value) {
|
||||
var objValue = object[key];
|
||||
if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) ||
|
||||
(value === undefined && !(key in object))) {
|
||||
baseAssignValue(object, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
export default assignValue;
|
||||
21
datav/src/utils/lodash/_assocIndexOf.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import eq from './eq.js';
|
||||
|
||||
/**
|
||||
* Gets the index at which the `key` is found in `array` of key-value pairs.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} array The array to inspect.
|
||||
* @param {*} key The key to search for.
|
||||
* @returns {number} Returns the index of the matched value, else `-1`.
|
||||
*/
|
||||
function assocIndexOf(array, key) {
|
||||
var length = array.length;
|
||||
while (length--) {
|
||||
if (eq(array[length][0], key)) {
|
||||
return length;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
export default assocIndexOf;
|
||||
21
datav/src/utils/lodash/_baseAggregator.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import baseEach from './_baseEach.js';
|
||||
|
||||
/**
|
||||
* Aggregates elements of `collection` on `accumulator` with keys transformed
|
||||
* by `iteratee` and values set by `setter`.
|
||||
*
|
||||
* @private
|
||||
* @param {Array|Object} collection The collection to iterate over.
|
||||
* @param {Function} setter The function to set `accumulator` values.
|
||||
* @param {Function} iteratee The iteratee to transform keys.
|
||||
* @param {Object} accumulator The initial aggregated object.
|
||||
* @returns {Function} Returns `accumulator`.
|
||||
*/
|
||||
function baseAggregator(collection, setter, iteratee, accumulator) {
|
||||
baseEach(collection, function(value, key, collection) {
|
||||
setter(accumulator, value, iteratee(value), collection);
|
||||
});
|
||||
return accumulator;
|
||||
}
|
||||
|
||||
export default baseAggregator;
|
||||
17
datav/src/utils/lodash/_baseAssign.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import copyObject from './_copyObject.js';
|
||||
import keys from './keys.js';
|
||||
|
||||
/**
|
||||
* The base implementation of `_.assign` without support for multiple sources
|
||||
* or `customizer` functions.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} object The destination object.
|
||||
* @param {Object} source The source object.
|
||||
* @returns {Object} Returns `object`.
|
||||
*/
|
||||
function baseAssign(object, source) {
|
||||
return object && copyObject(source, keys(source), object);
|
||||
}
|
||||
|
||||
export default baseAssign;
|
||||
17
datav/src/utils/lodash/_baseAssignIn.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import copyObject from './_copyObject.js';
|
||||
import keysIn from './keysIn.js';
|
||||
|
||||
/**
|
||||
* The base implementation of `_.assignIn` without support for multiple sources
|
||||
* or `customizer` functions.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} object The destination object.
|
||||
* @param {Object} source The source object.
|
||||
* @returns {Object} Returns `object`.
|
||||
*/
|
||||
function baseAssignIn(object, source) {
|
||||
return object && copyObject(source, keysIn(source), object);
|
||||
}
|
||||
|
||||
export default baseAssignIn;
|
||||
25
datav/src/utils/lodash/_baseAssignValue.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import defineProperty from './_defineProperty.js';
|
||||
|
||||
/**
|
||||
* The base implementation of `assignValue` and `assignMergeValue` without
|
||||
* value checks.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} object The object to modify.
|
||||
* @param {string} key The key of the property to assign.
|
||||
* @param {*} value The value to assign.
|
||||
*/
|
||||
function baseAssignValue(object, key, value) {
|
||||
if (key == '__proto__' && defineProperty) {
|
||||
defineProperty(object, key, {
|
||||
'configurable': true,
|
||||
'enumerable': true,
|
||||
'value': value,
|
||||
'writable': true
|
||||
});
|
||||
} else {
|
||||
object[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
export default baseAssignValue;
|
||||
23
datav/src/utils/lodash/_baseAt.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import get from './get.js';
|
||||
|
||||
/**
|
||||
* The base implementation of `_.at` without support for individual paths.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} object The object to iterate over.
|
||||
* @param {string[]} paths The property paths to pick.
|
||||
* @returns {Array} Returns the picked elements.
|
||||
*/
|
||||
function baseAt(object, paths) {
|
||||
var index = -1,
|
||||
length = paths.length,
|
||||
result = Array(length),
|
||||
skip = object == null;
|
||||
|
||||
while (++index < length) {
|
||||
result[index] = skip ? undefined : get(object, paths[index]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export default baseAt;
|
||||
22
datav/src/utils/lodash/_baseClamp.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* The base implementation of `_.clamp` which doesn't coerce arguments.
|
||||
*
|
||||
* @private
|
||||
* @param {number} number The number to clamp.
|
||||
* @param {number} [lower] The lower bound.
|
||||
* @param {number} upper The upper bound.
|
||||
* @returns {number} Returns the clamped number.
|
||||
*/
|
||||
function baseClamp(number, lower, upper) {
|
||||
if (number === number) {
|
||||
if (upper !== undefined) {
|
||||
number = number <= upper ? number : upper;
|
||||
}
|
||||
if (lower !== undefined) {
|
||||
number = number >= lower ? number : lower;
|
||||
}
|
||||
}
|
||||
return number;
|
||||
}
|
||||
|
||||
export default baseClamp;
|
||||
171
datav/src/utils/lodash/_baseClone.js
Normal file
@@ -0,0 +1,171 @@
|
||||
import Stack from './_Stack.js';
|
||||
import arrayEach from './_arrayEach.js';
|
||||
import assignValue from './_assignValue.js';
|
||||
import baseAssign from './_baseAssign.js';
|
||||
import baseAssignIn from './_baseAssignIn.js';
|
||||
import cloneBuffer from './_cloneBuffer.js';
|
||||
import copyArray from './_copyArray.js';
|
||||
import copySymbols from './_copySymbols.js';
|
||||
import copySymbolsIn from './_copySymbolsIn.js';
|
||||
import getAllKeys from './_getAllKeys.js';
|
||||
import getAllKeysIn from './_getAllKeysIn.js';
|
||||
import getTag from './_getTag.js';
|
||||
import initCloneArray from './_initCloneArray.js';
|
||||
import initCloneByTag from './_initCloneByTag.js';
|
||||
import initCloneObject from './_initCloneObject.js';
|
||||
import isArray from './isArray.js';
|
||||
import isBuffer from './isBuffer.js';
|
||||
import isMap from './isMap.js';
|
||||
import isObject from './isObject.js';
|
||||
import isSet from './isSet.js';
|
||||
import keys from './keys.js';
|
||||
|
||||
/** Used to compose bitmasks for cloning. */
|
||||
var CLONE_DEEP_FLAG = 1,
|
||||
CLONE_FLAT_FLAG = 2,
|
||||
CLONE_SYMBOLS_FLAG = 4;
|
||||
|
||||
/** `Object#toString` result references. */
|
||||
var argsTag = '[object Arguments]',
|
||||
arrayTag = '[object Array]',
|
||||
boolTag = '[object Boolean]',
|
||||
dateTag = '[object Date]',
|
||||
errorTag = '[object Error]',
|
||||
funcTag = '[object Function]',
|
||||
genTag = '[object GeneratorFunction]',
|
||||
mapTag = '[object Map]',
|
||||
numberTag = '[object Number]',
|
||||
objectTag = '[object Object]',
|
||||
regexpTag = '[object RegExp]',
|
||||
setTag = '[object Set]',
|
||||
stringTag = '[object String]',
|
||||
symbolTag = '[object Symbol]',
|
||||
weakMapTag = '[object WeakMap]';
|
||||
|
||||
var arrayBufferTag = '[object ArrayBuffer]',
|
||||
dataViewTag = '[object DataView]',
|
||||
float32Tag = '[object Float32Array]',
|
||||
float64Tag = '[object Float64Array]',
|
||||
int8Tag = '[object Int8Array]',
|
||||
int16Tag = '[object Int16Array]',
|
||||
int32Tag = '[object Int32Array]',
|
||||
uint8Tag = '[object Uint8Array]',
|
||||
uint8ClampedTag = '[object Uint8ClampedArray]',
|
||||
uint16Tag = '[object Uint16Array]',
|
||||
uint32Tag = '[object Uint32Array]';
|
||||
|
||||
/** Used to identify `toStringTag` values supported by `_.clone`. */
|
||||
var cloneableTags = {};
|
||||
cloneableTags[argsTag] = cloneableTags[arrayTag] =
|
||||
cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] =
|
||||
cloneableTags[boolTag] = cloneableTags[dateTag] =
|
||||
cloneableTags[float32Tag] = cloneableTags[float64Tag] =
|
||||
cloneableTags[int8Tag] = cloneableTags[int16Tag] =
|
||||
cloneableTags[int32Tag] = cloneableTags[mapTag] =
|
||||
cloneableTags[numberTag] = cloneableTags[objectTag] =
|
||||
cloneableTags[regexpTag] = cloneableTags[setTag] =
|
||||
cloneableTags[stringTag] = cloneableTags[symbolTag] =
|
||||
cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] =
|
||||
cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true;
|
||||
cloneableTags[errorTag] = cloneableTags[funcTag] =
|
||||
cloneableTags[weakMapTag] = false;
|
||||
|
||||
/**
|
||||
* The base implementation of `_.clone` and `_.cloneDeep` which tracks
|
||||
* traversed objects.
|
||||
*
|
||||
* @private
|
||||
* @param {*} value The value to clone.
|
||||
* @param {boolean} bitmask The bitmask flags.
|
||||
* 1 - Deep clone
|
||||
* 2 - Flatten inherited properties
|
||||
* 4 - Clone symbols
|
||||
* @param {Function} [customizer] The function to customize cloning.
|
||||
* @param {string} [key] The key of `value`.
|
||||
* @param {Object} [object] The parent object of `value`.
|
||||
* @param {Object} [stack] Tracks traversed objects and their clone counterparts.
|
||||
* @returns {*} Returns the cloned value.
|
||||
*/
|
||||
function baseClone(value, bitmask, customizer, key, object, stack) {
|
||||
var result,
|
||||
isDeep = bitmask & CLONE_DEEP_FLAG,
|
||||
isFlat = bitmask & CLONE_FLAT_FLAG,
|
||||
isFull = bitmask & CLONE_SYMBOLS_FLAG;
|
||||
|
||||
if (customizer) {
|
||||
result = object ? customizer(value, key, object, stack) : customizer(value);
|
||||
}
|
||||
if (result !== undefined) {
|
||||
return result;
|
||||
}
|
||||
if (!isObject(value)) {
|
||||
return value;
|
||||
}
|
||||
var isArr = isArray(value);
|
||||
if (isArr) {
|
||||
result = initCloneArray(value);
|
||||
if (!isDeep) {
|
||||
return copyArray(value, result);
|
||||
}
|
||||
} else {
|
||||
var tag = getTag(value),
|
||||
isFunc = tag == funcTag || tag == genTag;
|
||||
|
||||
if (isBuffer(value)) {
|
||||
return cloneBuffer(value, isDeep);
|
||||
}
|
||||
if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
|
||||
result = (isFlat || isFunc) ? {} : initCloneObject(value);
|
||||
if (!isDeep) {
|
||||
return isFlat
|
||||
? copySymbolsIn(value, baseAssignIn(result, value))
|
||||
: copySymbols(value, baseAssign(result, value));
|
||||
}
|
||||
} else {
|
||||
if (!cloneableTags[tag]) {
|
||||
return object ? value : {};
|
||||
}
|
||||
result = initCloneByTag(value, tag, isDeep);
|
||||
}
|
||||
}
|
||||
// Check for circular references and return its corresponding clone.
|
||||
stack || (stack = new Stack);
|
||||
var stacked = stack.get(value);
|
||||
if (stacked) {
|
||||
return stacked;
|
||||
}
|
||||
stack.set(value, result);
|
||||
|
||||
if (isSet(value)) {
|
||||
value.forEach(function(subValue) {
|
||||
result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack));
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (isMap(value)) {
|
||||
value.forEach(function(subValue, key) {
|
||||
result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack));
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
var keysFunc = isFull
|
||||
? (isFlat ? getAllKeysIn : getAllKeys)
|
||||
: (isFlat ? keysIn : keys);
|
||||
|
||||
var props = isArr ? undefined : keysFunc(value);
|
||||
arrayEach(props || value, function(subValue, key) {
|
||||
if (props) {
|
||||
key = subValue;
|
||||
subValue = value[key];
|
||||
}
|
||||
// Recursively populate clone (susceptible to call stack limits).
|
||||
assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export default baseClone;
|
||||