基本功能已完成

This commit is contained in:
2024-07-15 02:19:48 +08:00
parent 84425462ab
commit 78bc464a76
6 changed files with 109 additions and 137 deletions

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

View File

@@ -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;` 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({ 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({ Vue.prototype.$multipleStoreBoard = Vue.observable({
search: {"groupCodeList": [], "hourNum": "", type: "1"} search: {"groupCodeList": [], "hourNum": "", type: "1"}

View File

@@ -8,12 +8,10 @@ export default {
name: "AppNavbar", name: "AppNavbar",
label: "标题栏", label: "标题栏",
data() { data() {
return { return {screens}
screens,
groupId: "5b1849ac-4fc3-451a-844c-3362b47341ef"
}
}, },
computed: { computed: {
groupId: v => v.$marketBoard.screenId,
backgroundImage() { backgroundImage() {
const item = this.screens.find(e => e.id === this.groupId) const item = this.screens.find(e => e.id === this.groupId)
return `url(${item.bg})` return `url(${item.bg})`
@@ -25,11 +23,9 @@ export default {
handler(val) { handler(val) {
if (val) { if (val) {
$glob.group = this.groupId $glob.group = this.groupId
} else {
this.groupId = $glob.group
} }
} }
} },
} }
} }
</script> </script>

View File

@@ -6,7 +6,8 @@ export default {
return { return {
geoMap: null, geoMap: null,
layers: [], layers: [],
font: null font: null,
geoJson: null
} }
}, },
computed: { computed: {
@@ -17,29 +18,33 @@ export default {
const {$waitFor, THREE, $loadScript} = window const {$waitFor, THREE, $loadScript} = window
return $waitFor(THREE).then(() => Promise.all([ 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/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/loaders/FontLoader.js`,
`http://10.0.97.209/presource/datascreen/js/three/js/geometries/TextGeometry.js`, `http://10.0.97.209/presource/datascreen/js/three/js/geometries/TextGeometry.js`,
'/presource/datascreen/js/turf.min.js' '/presource/datascreen/js/turf.min.js'
].map(e => $loadScript('js', e)))) ].map(e => $loadScript('js', e))))
}, },
initMap() { initMap() {
const {THREE, d3, axios, TWEEN} = window const {THREE, d3, TWEEN, turf} = window
const rootEl = this.$el const rootEl = this.$el
const root = this const root = this
const scale = 4 const scale = 8
class GeoMap { class GeoMap {
constructor() { constructor() {
this.cameraPosition = {x: 40, y: 0, z: 40}; // 相机位置 this.cameraPosition = {x: 40, y: 0, z: 80}; // 相机位置
this.scene = null; // 场景 this.scene = null; // 场景
this.camera = null; // 相机 this.camera = null; // 相机
this.renderer = null; // 渲染器 this.renderer = null; // 渲染器
this.CSS3DRenderer = null; // css渲染器
this.controls = null; // 控制器 this.controls = null; // 控制器
this.mapGroup = new THREE.Group(); // 组 this.mapGroup = new THREE.Group(); // 组
this.mouse = new THREE.Vector2(); this.mouse = new THREE.Vector2();
this.font = null; this.font = null;
this.tips = new THREE.Group() 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() { init() {
this.setScene(); this.setScene();
this.setCamera(); this.setCamera();
this.setLight() // this.setAxes();
this.setAxes();
this.setRenderer(); this.setRenderer();
this.setControl(); this.setControl();
this.makeGround(); this.setMapData(root.geoJson)
this.getMap('http://10.0.97.209/blade-visual/map/data?id=1456');
this.addMarkers()
this.animation(); this.animation();
this.bindMouseEvent() 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 动画循环 * @desc 动画循环
* */ * */
animation() { animation() {
requestAnimationFrame(this.animation.bind(this)); 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(); TWEEN.update();
this.controls.update(); this.controls.update();
this.renderer.render(this.scene, this.camera); this.renderer.render(this.scene, this.camera);
} this.CSS3DRenderer.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)
}
})
} }
/** /**
@@ -96,7 +85,7 @@ export default {
* @params geojson * @params geojson
* */ * */
setMapData(data) { setMapData(data) {
const that = this, {turf} = window; const that = this
const getLnglat = (arr, cb) => { const getLnglat = (arr, cb) => {
arr?.map(e => { arr?.map(e => {
if (e.length === 2 && typeof e[0] === 'number' && typeof e[1] === 'number') { if (e.length === 2 && typeof e[0] === 'number' && typeof e[1] === 'number') {
@@ -108,7 +97,7 @@ export default {
} }
let vector3json = [], let vector3json = [],
vector3border = [] vector3border = []
const maxBoundaryPoints = turf.union(this.geoJson) const maxBoundaryPoints = turf.union(data)
getLnglat(maxBoundaryPoints.geometry.coordinates, p => { getLnglat(maxBoundaryPoints.geometry.coordinates, p => {
const lnglat = that.lnglatToVector3(p); const lnglat = that.lnglatToVector3(p);
const vector3 = new THREE.Vector3(lnglat[0], lnglat[1], lnglat[2]).multiplyScalar(1.2); 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) { data.features.forEach(function (features, featuresIndex) {
const areaItems = features.geometry.coordinates; 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] = { vector3json[featuresIndex] = {
data: features.properties, data: features.properties,
mercator: [] mercator: []
@@ -140,9 +129,8 @@ export default {
drawMap(data, border) { drawMap(data, border) {
let that = this; let that = this;
this.mapGroup.position.y = 0; this.mapGroup.position.y = 0;
this.scene.add(that.mapGroup);
const extrudeSettings = { const extrudeSettings = {
depth: 0.2, depth: 0.1,
steps: 1, steps: 1,
bevelSegments: 0, bevelSegments: 0,
curveSegments: 1, curveSegments: 1,
@@ -161,7 +149,6 @@ export default {
ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillRect(0, 0, canvas.width, canvas.height);
const blockMaterial = new THREE.MeshBasicMaterial({ const blockMaterial = new THREE.MeshBasicMaterial({
map: new THREE.CanvasTexture(canvas), map: new THREE.CanvasTexture(canvas),
// side: THREE.DoubleSide,
transparent: true, wireframe: false transparent: true, wireframe: false
}); });
const lineMaterial = new THREE.LineBasicMaterial({color: '#97CAE6'}); const lineMaterial = new THREE.LineBasicMaterial({color: '#97CAE6'});
@@ -173,7 +160,7 @@ export default {
// Draw Line // Draw Line
let lineGeometry = new THREE.BufferGeometry().setFromPoints(areaItem); let lineGeometry = new THREE.BufferGeometry().setFromPoints(areaItem);
let lineMesh = new THREE.Line(lineGeometry, lineMaterial); let lineMesh = new THREE.Line(lineGeometry, lineMaterial);
lineMesh.position.z = 0.201; lineMesh.position.z = 0.101;
areaGroup.add(lineMesh); areaGroup.add(lineMesh);
}); });
const {name, cp} = areaData.data const {name, cp} = areaData.data
@@ -184,11 +171,9 @@ export default {
that.mapGroup.add(that.tips) that.mapGroup.add(that.tips)
const shape = new THREE.Shape(border); const shape = new THREE.Shape(border);
const areaGeometry = new THREE.ExtrudeGeometry(shape, extrudeSettings); 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.add(mesh)
that.mapGroup.scale.set(scale, scale, scale)
that.mapGroup.position.set(0, 0, 0)
that.scene.add(that.mapGroup);
} }
transLayer(item = {}) { transLayer(item = {}) {
@@ -197,24 +182,18 @@ export default {
latitude = Number(latitude || 0).toFixed(6); latitude = Number(latitude || 0).toFixed(6);
const markerGeometry = new THREE.CircleGeometry(0.015, 32); const markerGeometry = new THREE.CircleGeometry(0.015, 32);
const markerMaterial = new THREE.MeshBasicMaterial({ const markerMaterial = new THREE.MeshBasicMaterial({
side: THREE.DoubleSide, wireframe: false,
blending: THREE.AdditiveBlending,
color: bakeStockAmt > 0 ? "#66FFFF" : "#FFD15C", color: bakeStockAmt > 0 ? "#66FFFF" : "#FFD15C",
depthTest: false,
transparent: true,
opacity: 1
}); });
const marker = new THREE.Mesh(markerGeometry, markerMaterial); const marker = new THREE.Mesh(markerGeometry, markerMaterial);
const lnglat = this.lnglatToVector3([longitude, latitude]); const lnglat = this.lnglatToVector3([longitude, latitude]);
let v3 = new THREE.Vector3(lnglat[0], lnglat[1], lnglat[2]).multiplyScalar(1.2); let v3 = new THREE.Vector3(lnglat[0], lnglat[1], lnglat[2]).multiplyScalar(1.2);
marker.data = item marker.data = item
marker.position.set(v3.x, v3.y, 0.201) marker.position.set(v3.x, v3.y, 0.11)
marker.renderOrder = 3
return marker return marker
} }
addMarkers() { addMarkers() {
this.markers = new THREE.Group();
root.layers.map(layer => { root.layers.map(layer => {
const marker = this.transLayer(layer) const marker = this.transLayer(layer)
this.markers.add(marker) this.markers.add(marker)
@@ -224,7 +203,7 @@ export default {
lnglatToVector3(lnglat = []) { lnglatToVector3(lnglat = []) {
if (!this.projection) { 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 [x, y] = this.projection([lnglat[0], lnglat[1]])
const z = 0; const z = 0;
@@ -236,6 +215,9 @@ export default {
* */ * */
setScene() { setScene() {
this.scene = new THREE.Scene(); 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.setClearColor(0xffffff, 0);
this.renderer.setSize(rootEl.offsetWidth, rootEl.offsetHeight); this.renderer.setSize(rootEl.offsetWidth, rootEl.offsetHeight);
rootEl.appendChild(this.renderer.domElement); rootEl.appendChild(this.renderer.domElement);
this.CSS3DRenderer = new THREE.CSS3DRenderer();
this.CSS3DRenderer.setSize(rootEl.offsetWidth, rootEl.offsetHeight);
rootEl.appendChild(this.CSS3DRenderer.domElement);
function onWindowResize() { function onWindowResize() {
this.camera.aspect = rootEl.offsetWidth / rootEl.offsetHeight; this.camera.aspect = rootEl.offsetWidth / rootEl.offsetHeight;
this.camera.updateProjectionMatrix(); this.camera.updateProjectionMatrix();
this.renderer.setSize(rootEl.offsetWidth, rootEl.offsetHeight); this.renderer.setSize(rootEl.offsetWidth, rootEl.offsetHeight);
this.CSS3DRenderer.setSize(rootEl.offsetWidth, rootEl.offsetHeight);
} }
rootEl.addEventListener('resize', onWindowResize.bind(this), false); rootEl.addEventListener('resize', onWindowResize.bind(this), false);
@@ -275,7 +261,7 @@ export default {
* */ * */
setControl() { setControl() {
this.controls = new THREE.OrbitControls(this.camera, rootEl); 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); this.camera.position.set(this.cameraPosition.x, this.cameraPosition.y, this.cameraPosition.z);
} }
@@ -287,21 +273,6 @@ export default {
this.scene.add(axes); 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) { setTips(text, x, y) {
const textGeometry = new THREE.TextGeometry(text, { const textGeometry = new THREE.TextGeometry(text, {
font: root.font, font: root.font,
@@ -310,8 +281,8 @@ export default {
}); });
textGeometry.center() textGeometry.center()
textGeometry.rotateZ(Math.PI / 2) textGeometry.rotateZ(Math.PI / 2)
textGeometry.rotateY(Math.PI / 4) textGeometry.rotateY(Math.PI / 6)
textGeometry.translate(x, y, 0.25) textGeometry.translate(x, y, 0.15)
const textMaterial = new THREE.MeshBasicMaterial({color: 0xffffff, transparent: true, opacity: 0.8}); const textMaterial = new THREE.MeshBasicMaterial({color: 0xffffff, transparent: true, opacity: 0.8});
const textMesh = new THREE.Mesh(textGeometry, textMaterial); const textMesh = new THREE.Mesh(textGeometry, textMaterial);
this.tips.add(textMesh) this.tips.add(textMesh)
@@ -321,63 +292,58 @@ export default {
const raycaster = new THREE.Raycaster(); const raycaster = new THREE.Raycaster();
const onPointerMove = (event) => { const onPointerMove = (event) => {
const {clientWidth: width, clientHeight: height} = rootEl; const {offsetWidth: width, offsetHeight: height} = rootEl, {left, top} = rootEl.getBoundingClientRect();
this.mouse.x = (event.clientX / width) * 2 - 1; //标准设备横坐标 this.mouse.x = ((event.clientX - left) / width) * 2 - 1; //标准设备横坐标
this.mouse.y = -(event.clientY / height) * 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 standardVector = new THREE.Vector3(x, y, 0.5); //标准设备坐标
// //标准设备坐标转世界坐标 // //标准设备坐标转世界坐标
// const worldVector = standardVector.unproject(that.camera); // const worldVector = standardVector.unproject(this.camera);
// //射线投射方向单位向量(worldVector坐标减相机位置坐标) // const ray = worldVector.sub(this.camera.position).normalize();
// 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 = () => {
// 创建一个射线投射器 // 创建一个射线投射器
raycaster.setFromCamera(this.mouse, this.camera); raycaster.setFromCamera(this.mouse, this.camera);
console.table(raycaster.ray)
const intersects = raycaster.intersectObjects(this.markers.children) const intersects = raycaster.intersectObjects(this.markers.children)
console.log(intersects)
intersects.forEach(e => { intersects.forEach(e => {
if (e.visible) { if (e.object?.data) {
const {$glob} = window const marker = e.object?.data
root.$storeBoard.search.storeCode = marker.data?.storeCode root.$storeBoard.search.storeCode = marker.storeCode
$glob.group = '9f299712-5549-413b-a93b-7c3e3b5bfadb' root.$marketBoard.screenId = 'a90522ef-869b-40ea-8542-d1fc9674a1e8'
} }
}) })
} }
rootEl.addEventListener('pointermove', onPointerMove); rootEl.addEventListener('pointermove', onPointerMove);
rootEl.addEventListener('click', onClick); 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() return new GeoMap()
@@ -385,20 +351,26 @@ export default {
getData() { getData() {
const {$http, $waitFor} = window const {$http, $waitFor} = window
const {groupCodeList, currentDate} = this.search const {groupCodeList, currentDate} = this.search
return $waitFor($http).then(() => $http.post("/data-boot/la/screen/marketBoard/storeReport", { return $waitFor($http).then(() => Promise.all([
groupCodeList, currentDate $http.post("/data-boot/la/screen/marketBoard/storeReport", {
})).then(res => { groupCodeList, currentDate
if (res?.data) { }).then(res => {
return this.layers = res.data || [] 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: { watch: {
search: { search: {
immediate: true, deep: true, handler() { deep: true, handler() {
const {$waitFor} = window this.getData().then(() => this.geoMap.addMarkers())
this.getData().then(() => $waitFor(this.geoMap)).then(() => this.geoMap.addMarkers())
} }
} }
}, },
@@ -410,8 +382,11 @@ export default {
resolve() resolve()
}) })
})).then(() => { })).then(() => {
return !this.geoJson && this.getData()
}).then(() => {
this.geoMap = this.initMap(); this.geoMap = this.initMap();
this.geoMap.init(); this.geoMap.init();
this.geoMap.addMarkers()
}) })
} }
} }
@@ -421,7 +396,8 @@ export default {
</template> </template>
<style> <style>
.AppThreeMap { .AppThreeMap {
width: 100%; width: 900px;
height: 100%; height: 500px;
position: relative;
} }
</style> </style>