初始提交:全国牛产业大数据中心大屏项目

This commit is contained in:
2025-10-27 17:29:42 +08:00
commit b47ec8eb88
694 changed files with 35059 additions and 0 deletions

8
.editorconfig Normal file
View 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
View File

@@ -0,0 +1 @@
* text=auto eol=lf

30
.gitignore vendored Normal file
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

5417
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

42
package.json Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 KiB

BIN
public/data/map/bg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 KiB

BIN
public/data/map/dbg.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 KiB

BIN
public/data/map/gz-map.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

BIN
public/data/map/sc-map.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 708 KiB

BIN
public/data/map/sidebg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
public/data/map/牛.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

24
public/favicon.svg Normal file
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

33
public/texture/光柱.svg Normal file
View 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

View 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

View 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

View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

28
public/texture/标注.svg Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View 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
View 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

File diff suppressed because it is too large Load Diff

70
src/assets/ningxia.json Normal file
View 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

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

1437
src/components/Map3D.vue Normal file

File diff suppressed because it is too large Load Diff

View 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,
}
}

View 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
}
}

View 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
View 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
View 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
};
}

View 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
};
}

View 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
};
}

View 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
View 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
View 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
View 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];
}

View 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
View 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;

View 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;

View 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;

View 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
View 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;

View 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;

View 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
View 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;

View 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;

View 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;

View File

@@ -0,0 +1,6 @@
import root from './_root.js';
/** Built-in value references. */
var Symbol = root.Symbol;
export default Symbol;

View File

@@ -0,0 +1,6 @@
import root from './_root.js';
/** Built-in value references. */
var Uint8Array = root.Uint8Array;
export default Uint8Array;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

Some files were not shown because too many files have changed in this diff Show More