初始提交:全国牛产业大数据中心大屏项目
8
.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
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
30
.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
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
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
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
index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||||
|
<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
jsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
5417
package-lock.json
generated
Normal file
42
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
public/data/map/1.png
Normal file
|
After Width: | Height: | Size: 482 KiB |
BIN
public/data/map/bg.jpg
Normal file
|
After Width: | Height: | Size: 452 KiB |
BIN
public/data/map/circle-point.png
Normal file
|
After Width: | Height: | Size: 470 KiB |
BIN
public/data/map/dbg.webp
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
public/data/map/gz-map-fx.jpg
Normal file
|
After Width: | Height: | Size: 806 KiB |
BIN
public/data/map/gz-map.jpg
Normal file
|
After Width: | Height: | Size: 3.6 MiB |
BIN
public/data/map/rotating-point2.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
public/data/map/rotatingAperture.png
Normal file
|
After Width: | Height: | Size: 277 KiB |
BIN
public/data/map/sc-map.jpg
Normal file
|
After Width: | Height: | Size: 450 KiB |
BIN
public/data/map/scene-bg2.png
Normal file
|
After Width: | Height: | Size: 708 KiB |
BIN
public/data/map/sidebg.png
Normal file
|
After Width: | Height: | Size: 489 KiB |
BIN
public/data/map/上升粒子1.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
1
public/data/map/中华人民共和国.json
Normal file
1
public/data/map/宁夏回族自治区.json
Normal file
BIN
public/data/map/牛.png
Normal file
|
After Width: | Height: | Size: 200 KiB |
24
public/favicon.svg
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||||
|
<!-- 背景圆形 -->
|
||||||
|
<circle cx="16" cy="16" r="15" fill="#0c1426" stroke="#00d4ff" stroke-width="2"/>
|
||||||
|
|
||||||
|
<!-- 牛头轮廓 -->
|
||||||
|
<path d="M8 18 Q8 12 12 10 Q16 8 20 10 Q24 12 24 18 L22 20 Q20 22 16 22 Q12 22 10 20 Z" fill="#00d4ff" opacity="0.8"/>
|
||||||
|
|
||||||
|
<!-- 牛角 -->
|
||||||
|
<path d="M10 12 Q8 10 7 8" stroke="#00ffff" stroke-width="2" fill="none" stroke-linecap="round"/>
|
||||||
|
<path d="M22 12 Q24 10 25 8" stroke="#00ffff" stroke-width="2" fill="none" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- 眼睛 -->
|
||||||
|
<circle cx="13" cy="15" r="1.5" fill="#ffffff"/>
|
||||||
|
<circle cx="19" cy="15" r="1.5" fill="#ffffff"/>
|
||||||
|
|
||||||
|
<!-- 鼻子 -->
|
||||||
|
<ellipse cx="16" cy="18" rx="2" ry="1" fill="#ffffff" opacity="0.6"/>
|
||||||
|
|
||||||
|
<!-- 数据点装饰 -->
|
||||||
|
<circle cx="6" cy="8" r="1" fill="#00ffff" opacity="0.6"/>
|
||||||
|
<circle cx="26" cy="8" r="1" fill="#00ffff" opacity="0.6"/>
|
||||||
|
<circle cx="6" cy="24" r="1" fill="#00ffff" opacity="0.6"/>
|
||||||
|
<circle cx="26" cy="24" r="1" fill="#00ffff" opacity="0.6"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
32
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
public/texture/光柱.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
33
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
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
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
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
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
public/texture/标注.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
28
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
public/texture/标注光圈.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
26
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
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
src/App.vue
Normal file
70
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]
|
||||||
|
]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
1701
src/components/Alert.vue
Normal file
218
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>
|
||||||
1505
src/components/Home.vue
Normal file
1437
src/components/Map3D.vue
Normal file
223
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
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
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
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
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
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
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
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
src/main.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
|
||||||
|
createApp(App).mount('#app')
|
||||||
302
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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;
|
||||||
18
src/utils/lodash/_baseConforms.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import baseConformsTo from './_baseConformsTo.js';
|
||||||
|
import keys from './keys.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base implementation of `_.conforms` which doesn't clone `source`.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Object} source The object of property predicates to conform to.
|
||||||
|
* @returns {Function} Returns the new spec function.
|
||||||
|
*/
|
||||||
|
function baseConforms(source) {
|
||||||
|
var props = keys(source);
|
||||||
|
return function(object) {
|
||||||
|
return baseConformsTo(object, source, props);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default baseConforms;
|
||||||