基本功能已完成
This commit is contained in:
BIN
public/presource/datascreen/img/selectIcon.png
Normal file
BIN
public/presource/datascreen/img/selectIcon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -58,7 +58,8 @@ window.$waitFor = (target, t = 500) => new Promise(resolve => {
|
||||
window.evenRowBGC = (color = "#09265B") => `transparent;background-image: linear-gradient(-45deg, ${color} 0, ${color} 10%, transparent 10%, transparent 50%,${color} 50%, ${color} 60%, transparent 60%, transparent);background-size: 10px 10px;`
|
||||
|
||||
Vue.prototype.$marketBoard = Vue.observable({
|
||||
search: {"groupCodeList": ["20011061"], "currentDate": "20240701", "compareDate": "20240630", "hourNum": "18"}
|
||||
screenId: '5b1849ac-4fc3-451a-844c-3362b47341ef',
|
||||
search: {"groupCodeList": [], "currentDate": "20240701", "compareDate": "20240630", "hourNum": "18"}
|
||||
})
|
||||
Vue.prototype.$multipleStoreBoard = Vue.observable({
|
||||
search: {"groupCodeList": [], "hourNum": "", type: "1"}
|
||||
|
||||
@@ -8,12 +8,10 @@ export default {
|
||||
name: "AppNavbar",
|
||||
label: "标题栏",
|
||||
data() {
|
||||
return {
|
||||
screens,
|
||||
groupId: "5b1849ac-4fc3-451a-844c-3362b47341ef"
|
||||
}
|
||||
return {screens}
|
||||
},
|
||||
computed: {
|
||||
groupId: v => v.$marketBoard.screenId,
|
||||
backgroundImage() {
|
||||
const item = this.screens.find(e => e.id === this.groupId)
|
||||
return `url(${item.bg})`
|
||||
@@ -25,11 +23,9 @@ export default {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
$glob.group = this.groupId
|
||||
} else {
|
||||
this.groupId = $glob.group
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -6,7 +6,8 @@ export default {
|
||||
return {
|
||||
geoMap: null,
|
||||
layers: [],
|
||||
font: null
|
||||
font: null,
|
||||
geoJson: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -17,29 +18,33 @@ export default {
|
||||
const {$waitFor, THREE, $loadScript} = window
|
||||
return $waitFor(THREE).then(() => Promise.all([
|
||||
`http://10.0.97.209/presource/datascreen/js/three/js/controls/OrbitControls.js`,
|
||||
`http://10.0.97.209/presource/datascreen/js/three/js/renderers/CSS2DRenderer.js`,
|
||||
`http://10.0.97.209/presource/datascreen/js/three/js/renderers/CSS3DRenderer.js`,
|
||||
`http://10.0.97.209/presource/datascreen/js/three/js/loaders/FontLoader.js`,
|
||||
`http://10.0.97.209/presource/datascreen/js/three/js/geometries/TextGeometry.js`,
|
||||
'/presource/datascreen/js/turf.min.js'
|
||||
].map(e => $loadScript('js', e))))
|
||||
},
|
||||
initMap() {
|
||||
const {THREE, d3, axios, TWEEN} = window
|
||||
const {THREE, d3, TWEEN, turf} = window
|
||||
const rootEl = this.$el
|
||||
const root = this
|
||||
const scale = 4
|
||||
const scale = 8
|
||||
|
||||
class GeoMap {
|
||||
constructor() {
|
||||
this.cameraPosition = {x: 40, y: 0, z: 40}; // 相机位置
|
||||
this.cameraPosition = {x: 40, y: 0, z: 80}; // 相机位置
|
||||
this.scene = null; // 场景
|
||||
this.camera = null; // 相机
|
||||
this.renderer = null; // 渲染器
|
||||
this.CSS3DRenderer = null; // css渲染器
|
||||
this.controls = null; // 控制器
|
||||
this.mapGroup = new THREE.Group(); // 组
|
||||
this.mouse = new THREE.Vector2();
|
||||
this.font = null;
|
||||
this.tips = new THREE.Group()
|
||||
this.loop = 0
|
||||
this.markers = new THREE.Group();
|
||||
this.center = turf.center(root.geoJson).geometry.coordinates
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,47 +53,31 @@ export default {
|
||||
init() {
|
||||
this.setScene();
|
||||
this.setCamera();
|
||||
this.setLight()
|
||||
this.setAxes();
|
||||
// this.setAxes();
|
||||
this.setRenderer();
|
||||
this.setControl();
|
||||
this.makeGround();
|
||||
this.getMap('http://10.0.97.209/blade-visual/map/data?id=1456');
|
||||
this.addMarkers()
|
||||
this.setMapData(root.geoJson)
|
||||
this.animation();
|
||||
this.bindMouseEvent()
|
||||
}
|
||||
|
||||
setLight() {
|
||||
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
|
||||
this.scene.add(ambientLight);
|
||||
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
|
||||
directionalLight.position.set(1, 1, 1).normalize();
|
||||
this.scene.add(directionalLight);
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 动画循环
|
||||
* */
|
||||
animation() {
|
||||
requestAnimationFrame(this.animation.bind(this));
|
||||
this.loop = (this.loop * 100 + 2) % 100 * 0.01
|
||||
this.markers.children.forEach(e => {
|
||||
e.scale = {
|
||||
x: Math.asin(this.loop) + 1,
|
||||
y: Math.asin(this.loop) + 1,
|
||||
z: Math.asin(this.loop) + 1
|
||||
};
|
||||
})
|
||||
TWEEN.update();
|
||||
this.controls.update();
|
||||
this.renderer.render(this.scene, this.camera);
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 获取地图
|
||||
* */
|
||||
getMap(url) {
|
||||
const that = this;
|
||||
axios.get(url).then(function (res) {
|
||||
if (res.status === 200) {
|
||||
const data = res.data;
|
||||
that.geoJson = data
|
||||
that.setMapData(data)
|
||||
}
|
||||
})
|
||||
this.CSS3DRenderer.render(this.scene, this.camera);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,7 +85,7 @@ export default {
|
||||
* @params geojson
|
||||
* */
|
||||
setMapData(data) {
|
||||
const that = this, {turf} = window;
|
||||
const that = this
|
||||
const getLnglat = (arr, cb) => {
|
||||
arr?.map(e => {
|
||||
if (e.length === 2 && typeof e[0] === 'number' && typeof e[1] === 'number') {
|
||||
@@ -108,7 +97,7 @@ export default {
|
||||
}
|
||||
let vector3json = [],
|
||||
vector3border = []
|
||||
const maxBoundaryPoints = turf.union(this.geoJson)
|
||||
const maxBoundaryPoints = turf.union(data)
|
||||
getLnglat(maxBoundaryPoints.geometry.coordinates, p => {
|
||||
const lnglat = that.lnglatToVector3(p);
|
||||
const vector3 = new THREE.Vector3(lnglat[0], lnglat[1], lnglat[2]).multiplyScalar(1.2);
|
||||
@@ -116,7 +105,7 @@ export default {
|
||||
})
|
||||
data.features.forEach(function (features, featuresIndex) {
|
||||
const areaItems = features.geometry.coordinates;
|
||||
features.properties.cp = that.lnglatToVector3(features.properties.centroid);
|
||||
features.properties.cp = that.lnglatToVector3(turf.center(features).geometry.coordinates);
|
||||
vector3json[featuresIndex] = {
|
||||
data: features.properties,
|
||||
mercator: []
|
||||
@@ -140,9 +129,8 @@ export default {
|
||||
drawMap(data, border) {
|
||||
let that = this;
|
||||
this.mapGroup.position.y = 0;
|
||||
this.scene.add(that.mapGroup);
|
||||
const extrudeSettings = {
|
||||
depth: 0.2,
|
||||
depth: 0.1,
|
||||
steps: 1,
|
||||
bevelSegments: 0,
|
||||
curveSegments: 1,
|
||||
@@ -161,7 +149,6 @@ export default {
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
const blockMaterial = new THREE.MeshBasicMaterial({
|
||||
map: new THREE.CanvasTexture(canvas),
|
||||
// side: THREE.DoubleSide,
|
||||
transparent: true, wireframe: false
|
||||
});
|
||||
const lineMaterial = new THREE.LineBasicMaterial({color: '#97CAE6'});
|
||||
@@ -173,7 +160,7 @@ export default {
|
||||
// Draw Line
|
||||
let lineGeometry = new THREE.BufferGeometry().setFromPoints(areaItem);
|
||||
let lineMesh = new THREE.Line(lineGeometry, lineMaterial);
|
||||
lineMesh.position.z = 0.201;
|
||||
lineMesh.position.z = 0.101;
|
||||
areaGroup.add(lineMesh);
|
||||
});
|
||||
const {name, cp} = areaData.data
|
||||
@@ -184,11 +171,9 @@ export default {
|
||||
that.mapGroup.add(that.tips)
|
||||
const shape = new THREE.Shape(border);
|
||||
const areaGeometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
||||
const mesh = new THREE.Mesh(areaGeometry, blockMaterial);
|
||||
const borderMaterial = new THREE.MeshBasicMaterial({color: "#1B4C92", transparent: true, opacity: 0.8});
|
||||
const mesh = new THREE.Mesh(areaGeometry, [blockMaterial, borderMaterial]);
|
||||
that.mapGroup.add(mesh)
|
||||
that.mapGroup.scale.set(scale, scale, scale)
|
||||
that.mapGroup.position.set(0, 0, 0)
|
||||
that.scene.add(that.mapGroup);
|
||||
}
|
||||
|
||||
transLayer(item = {}) {
|
||||
@@ -197,24 +182,18 @@ export default {
|
||||
latitude = Number(latitude || 0).toFixed(6);
|
||||
const markerGeometry = new THREE.CircleGeometry(0.015, 32);
|
||||
const markerMaterial = new THREE.MeshBasicMaterial({
|
||||
side: THREE.DoubleSide,
|
||||
blending: THREE.AdditiveBlending,
|
||||
wireframe: false,
|
||||
color: bakeStockAmt > 0 ? "#66FFFF" : "#FFD15C",
|
||||
depthTest: false,
|
||||
transparent: true,
|
||||
opacity: 1
|
||||
});
|
||||
const marker = new THREE.Mesh(markerGeometry, markerMaterial);
|
||||
const lnglat = this.lnglatToVector3([longitude, latitude]);
|
||||
let v3 = new THREE.Vector3(lnglat[0], lnglat[1], lnglat[2]).multiplyScalar(1.2);
|
||||
marker.data = item
|
||||
marker.position.set(v3.x, v3.y, 0.201)
|
||||
marker.renderOrder = 3
|
||||
marker.position.set(v3.x, v3.y, 0.11)
|
||||
return marker
|
||||
}
|
||||
|
||||
addMarkers() {
|
||||
this.markers = new THREE.Group();
|
||||
root.layers.map(layer => {
|
||||
const marker = this.transLayer(layer)
|
||||
this.markers.add(marker)
|
||||
@@ -224,7 +203,7 @@ export default {
|
||||
|
||||
lnglatToVector3(lnglat = []) {
|
||||
if (!this.projection) {
|
||||
this.projection = d3.geoMercator().center([113.665412, 34.757975]).scale(100).translate([0.3, 0]);
|
||||
this.projection = d3.geoMercator().center(this.center).scale(100).translate([0, 0]);
|
||||
}
|
||||
const [x, y] = this.projection([lnglat[0], lnglat[1]])
|
||||
const z = 0;
|
||||
@@ -236,6 +215,9 @@ export default {
|
||||
* */
|
||||
setScene() {
|
||||
this.scene = new THREE.Scene();
|
||||
this.mapGroup.scale.set(scale, scale, scale)
|
||||
this.mapGroup.position.set(0, 0, 0)
|
||||
this.scene.add(this.mapGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,11 +242,15 @@ export default {
|
||||
this.renderer.setClearColor(0xffffff, 0);
|
||||
this.renderer.setSize(rootEl.offsetWidth, rootEl.offsetHeight);
|
||||
rootEl.appendChild(this.renderer.domElement);
|
||||
this.CSS3DRenderer = new THREE.CSS3DRenderer();
|
||||
this.CSS3DRenderer.setSize(rootEl.offsetWidth, rootEl.offsetHeight);
|
||||
rootEl.appendChild(this.CSS3DRenderer.domElement);
|
||||
|
||||
function onWindowResize() {
|
||||
this.camera.aspect = rootEl.offsetWidth / rootEl.offsetHeight;
|
||||
this.camera.updateProjectionMatrix();
|
||||
this.renderer.setSize(rootEl.offsetWidth, rootEl.offsetHeight);
|
||||
this.CSS3DRenderer.setSize(rootEl.offsetWidth, rootEl.offsetHeight);
|
||||
}
|
||||
|
||||
rootEl.addEventListener('resize', onWindowResize.bind(this), false);
|
||||
@@ -275,7 +261,7 @@ export default {
|
||||
* */
|
||||
setControl() {
|
||||
this.controls = new THREE.OrbitControls(this.camera, rootEl);
|
||||
// this.controls.enableRotate = false
|
||||
this.controls.enableRotate = false
|
||||
this.camera.position.set(this.cameraPosition.x, this.cameraPosition.y, this.cameraPosition.z);
|
||||
}
|
||||
|
||||
@@ -287,21 +273,6 @@ export default {
|
||||
this.scene.add(axes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 鼠标 hover 事件
|
||||
* */
|
||||
makeGround() {
|
||||
const material = new THREE.MeshBasicMaterial({opacity: 0.5, transparent: true, color: '#07193D'});
|
||||
const geometry = new THREE.PlaneGeometry(100, 100, 1, 1);
|
||||
let ground = new THREE.Mesh(geometry, material);
|
||||
ground.position.x = 0;
|
||||
ground.position.y = 0;
|
||||
ground.position.z = -1;
|
||||
this.scene.add(ground);
|
||||
ground.receiveShadow = true;
|
||||
ground.castShadow = true;
|
||||
}
|
||||
|
||||
setTips(text, x, y) {
|
||||
const textGeometry = new THREE.TextGeometry(text, {
|
||||
font: root.font,
|
||||
@@ -310,8 +281,8 @@ export default {
|
||||
});
|
||||
textGeometry.center()
|
||||
textGeometry.rotateZ(Math.PI / 2)
|
||||
textGeometry.rotateY(Math.PI / 4)
|
||||
textGeometry.translate(x, y, 0.25)
|
||||
textGeometry.rotateY(Math.PI / 6)
|
||||
textGeometry.translate(x, y, 0.15)
|
||||
const textMaterial = new THREE.MeshBasicMaterial({color: 0xffffff, transparent: true, opacity: 0.8});
|
||||
const textMesh = new THREE.Mesh(textGeometry, textMaterial);
|
||||
this.tips.add(textMesh)
|
||||
@@ -321,63 +292,58 @@ export default {
|
||||
const raycaster = new THREE.Raycaster();
|
||||
|
||||
const onPointerMove = (event) => {
|
||||
const {clientWidth: width, clientHeight: height} = rootEl;
|
||||
this.mouse.x = (event.clientX / width) * 2 - 1; //标准设备横坐标
|
||||
this.mouse.y = -(event.clientY / height) * 2 + 1; //标准设备纵坐标
|
||||
const {offsetWidth: width, offsetHeight: height} = rootEl, {left, top} = rootEl.getBoundingClientRect();
|
||||
this.mouse.x = ((event.clientX - left) / width) * 2 - 1; //标准设备横坐标
|
||||
this.mouse.y = -((event.clientY - top) / height) * 2 + 1; //标准设备纵坐标
|
||||
raycaster.setFromCamera(this.mouse, this.camera);
|
||||
const intersects = raycaster.intersectObjects(this.markers.children)
|
||||
intersects.forEach(e => {
|
||||
if (e.object?.data) {
|
||||
this.showHoverPanel(e.object)
|
||||
}
|
||||
})
|
||||
}
|
||||
const onClick = () => {
|
||||
// const {x, y} = this.mouse
|
||||
// const standardVector = new THREE.Vector3(x, y, 0.5); //标准设备坐标
|
||||
// //标准设备坐标转世界坐标
|
||||
// const worldVector = standardVector.unproject(that.camera);
|
||||
// //射线投射方向单位向量(worldVector坐标减相机位置坐标)
|
||||
// const ray = worldVector.sub(that.camera.position).normalize();
|
||||
// //创建射线投射器对象
|
||||
// let raycaster = new THREE.Raycaster(that.camera.position, ray);
|
||||
// //返回射线选中的对象
|
||||
// let intersects = raycaster.intersectObjects(that.meshList);
|
||||
// if (intersects.length) {
|
||||
// if (intersects[0].object.parent && intersects[0].object.parent._groupType === 'areaBlock') {
|
||||
// if (that.selectObject !== intersects[0].object.parent) {
|
||||
// if (that.selectObject) {
|
||||
// transiform(that.selectObject.position, {
|
||||
// x: that.selectObject.position.x,
|
||||
// y: that.selectObject.position.y,
|
||||
// z: 0
|
||||
// }, 100);
|
||||
// transiform(intersects[0].object.parent.position, {
|
||||
// x: intersects[0].object.parent.position.x,
|
||||
// y: intersects[0].object.parent.position.y,
|
||||
// z: 0.8
|
||||
// }, 100);
|
||||
// that.selectObject = intersects[0].object.parent;
|
||||
// } else {
|
||||
// transiform(intersects[0].object.parent.position, {
|
||||
// x: intersects[0].object.parent.position.x,
|
||||
// y: intersects[0].object.parent.position.y,
|
||||
// z: 0.8
|
||||
// }, 100);
|
||||
// that.selectObject = intersects[0].object.parent;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
const onClick = () => {
|
||||
// const worldVector = standardVector.unproject(this.camera);
|
||||
// const ray = worldVector.sub(this.camera.position).normalize();
|
||||
// 创建一个射线投射器
|
||||
raycaster.setFromCamera(this.mouse, this.camera);
|
||||
console.table(raycaster.ray)
|
||||
const intersects = raycaster.intersectObjects(this.markers.children)
|
||||
console.log(intersects)
|
||||
intersects.forEach(e => {
|
||||
if (e.visible) {
|
||||
const {$glob} = window
|
||||
root.$storeBoard.search.storeCode = marker.data?.storeCode
|
||||
$glob.group = '9f299712-5549-413b-a93b-7c3e3b5bfadb'
|
||||
if (e.object?.data) {
|
||||
const marker = e.object?.data
|
||||
root.$storeBoard.search.storeCode = marker.storeCode
|
||||
root.$marketBoard.screenId = 'a90522ef-869b-40ea-8542-d1fc9674a1e8'
|
||||
}
|
||||
})
|
||||
}
|
||||
rootEl.addEventListener('pointermove', onPointerMove);
|
||||
rootEl.addEventListener('click', onClick);
|
||||
}
|
||||
|
||||
showHoverPanel({data: marker, position} = {}) {
|
||||
const hoverPanelElement = document.createElement('div')
|
||||
Object.entries({
|
||||
background: 'linear-gradient( 90deg, rgba(9,63,107,0.79) 0%, rgba(13,58,99,0.03) 100%)',
|
||||
borderRadius: '2px',
|
||||
color: '#fff',
|
||||
fontSize: '10px',
|
||||
lineHeight: '14px',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
}).forEach(([key, value]) => {
|
||||
hoverPanelElement.style[key] = value
|
||||
})
|
||||
const span = document.createElement('span')
|
||||
span.innerHTML = [marker.storeName, `现烤库存金额:${marker.bakeStockAmt}`, `现烤销售机会:${marker.bakeStockAmt}`].join('<br/>')
|
||||
hoverPanelElement.appendChild(span)
|
||||
const hoverPanel = new THREE.CSS3DObject(hoverPanelElement)
|
||||
hoverPanel.position.set(position)
|
||||
this.scene.add(hoverPanel)
|
||||
}
|
||||
}
|
||||
|
||||
return new GeoMap()
|
||||
@@ -385,20 +351,26 @@ export default {
|
||||
getData() {
|
||||
const {$http, $waitFor} = window
|
||||
const {groupCodeList, currentDate} = this.search
|
||||
return $waitFor($http).then(() => $http.post("/data-boot/la/screen/marketBoard/storeReport", {
|
||||
groupCodeList, currentDate
|
||||
})).then(res => {
|
||||
if (res?.data) {
|
||||
return this.layers = res.data || []
|
||||
}
|
||||
})
|
||||
return $waitFor($http).then(() => Promise.all([
|
||||
$http.post("/data-boot/la/screen/marketBoard/storeReport", {
|
||||
groupCodeList, currentDate
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
return this.layers = res.data || []
|
||||
}
|
||||
}),
|
||||
axios.get('http://10.0.97.209/blade-visual/map/data?id=1456').then(res => {
|
||||
if (res?.data) {
|
||||
return this.geoJson = res.data
|
||||
}
|
||||
})
|
||||
]))
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
search: {
|
||||
immediate: true, deep: true, handler() {
|
||||
const {$waitFor} = window
|
||||
this.getData().then(() => $waitFor(this.geoMap)).then(() => this.geoMap.addMarkers())
|
||||
deep: true, handler() {
|
||||
this.getData().then(() => this.geoMap.addMarkers())
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -410,8 +382,11 @@ export default {
|
||||
resolve()
|
||||
})
|
||||
})).then(() => {
|
||||
return !this.geoJson && this.getData()
|
||||
}).then(() => {
|
||||
this.geoMap = this.initMap();
|
||||
this.geoMap.init();
|
||||
this.geoMap.addMarkers()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -421,7 +396,8 @@ export default {
|
||||
</template>
|
||||
<style>
|
||||
.AppThreeMap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 900px;
|
||||
height: 500px;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user