From 2bfbee66e863dfe33bab3d0111a0a6a16bd7e9be Mon Sep 17 00:00:00 2001 From: Zhuyanzy <2280684519@qq.com> Date: Sun, 12 Apr 2026 13:32:33 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A6=96=E6=AC=A1=E6=8F=90=E4=BA=A4doubao?= =?UTF-8?q?=E5=88=86=E6=94=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- css/indoor3D.css | 220 ++++++++++++++++++++++++++++++++++++++++++ data/testMapData.json | 194 ++++++++++++++++++++++++++++++++++++- index.html | 134 +++++++++++++++++++++++-- js/IndoorMap3d.js | 40 +++++++- server.js | 49 ++++++++++ 5 files changed, 623 insertions(+), 14 deletions(-) create mode 100644 server.js diff --git a/css/indoor3D.css b/css/indoor3D.css index a49e5c6..b8e6dff 100644 --- a/css/indoor3D.css +++ b/css/indoor3D.css @@ -54,4 +54,224 @@ .floorsUI .selected{ background-color: #cccccc !important; +} + +.shop-detail-panel { + position: fixed; + top: 50%; + right: 40px; + transform: translateY(-50%) translateX(120%); + width: 380px; + max-height: 85vh; + background: linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(248, 250, 252, 0.95) 100%); + backdrop-filter: blur(20px); + border-radius: 20px; + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.18); + overflow: hidden; + z-index: 1000; + transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); + font-family: 'Noto Sans SC', -apple-system, BlinkMacSystemFont, sans-serif; +} + +.shop-detail-panel.active { + transform: translateY(-50%) translateX(0); +} + +.panel-header { + padding: 20px 24px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + position: relative; +} + +.close-btn { + position: absolute; + top: 16px; + right: 20px; + font-size: 28px; + color: rgba(255, 255, 255, 0.9); + cursor: pointer; + transition: all 0.3s ease; + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + background: rgba(255, 255, 255, 0.15); +} + +.close-btn:hover { + background: rgba(255, 255, 255, 0.25); + color: #ffffff; + transform: rotate(90deg); +} + +.panel-content { + padding: 32px 28px; + overflow-y: auto; + max-height: calc(85vh - 80px); +} + +.shop-logo { + text-align: center; + margin-bottom: 24px; +} + +.logo-placeholder { + width: 90px; + height: 90px; + margin: 0 auto; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 42px; + box-shadow: 0 10px 30px -5px rgba(102, 126, 234, 0.4); +} + +.shop-name { + font-family: 'Noto Serif SC', serif; + font-size: 26px; + font-weight: 700; + text-align: center; + color: #1a202c; + margin: 0 0 16px 0; + letter-spacing: 2px; +} + +.shop-rating { + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + margin-bottom: 24px; +} + +.stars { + color: #fbbf24; + font-size: 18px; + letter-spacing: 2px; +} + +.rating-value { + font-size: 18px; + font-weight: 600; + color: #f59e0b; + font-family: 'Noto Serif SC', serif; +} + +.shop-category { + display: flex; + align-items: center; + gap: 12px; + padding: 16px 20px; + background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%); + border-radius: 12px; + margin-bottom: 20px; + border-left: 4px solid #0ea5e9; +} + +.category-label { + font-size: 14px; + font-weight: 600; + color: #0369a1; + text-transform: uppercase; + letter-spacing: 1px; +} + +.category-value { + font-size: 14px; + color: #0c4a6e; + font-weight: 500; +} + +.shop-brief { + font-size: 15px; + line-height: 1.8; + color: #4b5563; + padding: 20px; + background: #ffffff; + border-radius: 12px; + margin-bottom: 20px; + border: 1px solid #e5e7eb; + text-indent: 2em; +} + +.shop-hours { + display: flex; + align-items: center; + gap: 12px; + padding: 16px 20px; + background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); + border-radius: 12px; + margin-bottom: 28px; +} + +.hours-icon { + font-size: 20px; +} + +.hours-text { + font-size: 14px; + font-weight: 600; + color: #92400e; +} + +.panel-actions { + display: flex; + gap: 14px; +} + +.btn { + flex: 1; + padding: 14px 24px; + border: none; + border-radius: 12px; + font-size: 15px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + font-family: 'Noto Sans SC', sans-serif; + letter-spacing: 1px; +} + +.btn-primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: #ffffff; + box-shadow: 0 10px 20px -5px rgba(102, 126, 234, 0.4); +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 15px 30px -5px rgba(102, 126, 234, 0.5); +} + +.btn-secondary { + background: #f3f4f6; + color: #4b5563; + border: 2px solid #e5e7eb; +} + +.btn-secondary:hover { + background: #e5e7eb; + transform: translateY(-2px); +} + +.panel-content::-webkit-scrollbar { + width: 6px; +} + +.panel-content::-webkit-scrollbar-track { + background: #f1f5f9; + border-radius: 3px; +} + +.panel-content::-webkit-scrollbar-thumb { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-radius: 3px; +} + +.panel-content::-webkit-scrollbar-thumb:hover { + background: linear-gradient(135deg, #5a67d8 0%, #6b46c1 100%); } \ No newline at end of file diff --git a/data/testMapData.json b/data/testMapData.json index bcc081d..3a92d6a 100644 --- a/data/testMapData.json +++ b/data/testMapData.json @@ -17903,6 +17903,196 @@ "_id": 6, "High": 5, "Name": "F6" + }, + { + "Brief": "爱·阅读", + "ImageLayer": [], + "Area": 13454.14, + "PubPoint": [ + { + "Brief": "", + "Name_en": "Cashier", + "Outline": [[[[-300, 5]]]], + "_id": 7071644732, + "Type": "11003", + "Name": "收银台" + }, + { + "Brief": "", + "Name_en": "Toilet", + "Outline": [[[[-499, -629]]]], + "_id": 7071644733, + "Type": "11001", + "Name": "洗手间" + }, + { + "Brief": "", + "Name_en": "Escalator", + "Outline": [[[[-72, -472]]]], + "_id": 7071644734, + "Type": "21002", + "Name": "扶梯" + }, + { + "Brief": "", + "Name_en": "Stair", + "Outline": [[[[-479, -604]]]], + "_id": 7071644735, + "Type": "21001", + "Name": "楼梯" + }, + { + "Brief": "", + "Name_en": "Gate", + "Outline": [[[[-72, -325]]]], + "_id": 7071644736, + "Type": "22006", + "Name": "出入口" + } + ], + "FuncAreas": [ + { + "Brand": 70001, + "Type": "80100", + "Outline": [[[-85, -545, -54, -747, -104, -757, -154, -763, -195, -547, -85, -545]]], + "_id": 7070596097, + "ShopNo": "F7-01", + "Category2": 201, + "dianping_id": -1, + "Brief": "", + "Center": [-137.36, -624.27], + "Category": 201, + "name": "书香阁", + "Name_en": "BOOK PAVILION", + "Area": 261.94, + "Name": "书香阁", + "BrandShop": 760001 + }, + { + "Brand": 70002, + "Type": "80100", + "Outline": [[[201, -607, 73, -516, 50, -497, 75, -472, 107, -491, 182, -536, 244, -573, 201, -607]]], + "_id": 7070596098, + "ShopNo": "F7-02", + "Category2": 202, + "dianping_id": -1, + "Brief": "", + "Center": [130.47, -533.53], + "Category": 202, + "name": "科技前沿书店", + "Name_en": "TECH FRONTIER", + "Area": 89.18, + "Name": "科技前沿书店", + "BrandShop": 760002 + }, + { + "Brand": 70003, + "Type": "80100", + "Outline": [[[26, -724, -85, -545, -61, -540, -34, -533, 26, -724]]], + "_id": 7070596099, + "ShopNo": "F7-03", + "Category2": 203, + "dianping_id": -1, + "Brief": "", + "Center": [-27.86, -650], + "Category": 203, + "name": "文学时空", + "Name_en": "LITERATURE SPACE", + "Area": 131.15, + "Name": "文学时空", + "BrandShop": 760003 + }, + { + "Brand": 70004, + "Type": "80100", + "Outline": [[[-249, -528, -195, -547, -206, -765, -295, -760, -249, -528]]], + "_id": 7070596100, + "ShopNo": "F7-04", + "Category2": 204, + "dianping_id": -1, + "Brief": "", + "Center": [-244.3, -626.6], + "Category": 204, + "name": "儿童绘本馆", + "Name_en": "KIDS PICTURE BOOK", + "Area": 205.62, + "Name": "儿童绘本馆", + "BrandShop": 760004 + }, + { + "Brand": 70005, + "Type": "80100", + "Outline": [[[514, -248, 272, -306, 273, -139, 514, -137, 514, -248]]], + "_id": 7070596101, + "ShopNo": "F7-05", + "Category2": 205, + "dianping_id": -1, + "Brief": "", + "Center": [417.4, -215.6], + "Category": 205, + "name": "历史春秋", + "Name_en": "HISTORY CHRONICLE", + "Area": 318.23, + "Name": "历史春秋", + "BrandShop": 760005 + }, + { + "Brand": 70006, + "Type": "80100", + "Outline": [[[121, -137, 229, -136, 228, -277, 121, -278, 121, -137]]], + "_id": 7070596102, + "ShopNo": "F7-06", + "Category2": 206, + "dianping_id": -1, + "Brief": "", + "Center": [164, -193], + "Category": 206, + "name": "艺术画廊书店", + "Name_en": "ART GALLERY BOOKS", + "Area": 144.07, + "Name": "艺术画廊书店", + "BrandShop": 760006 + }, + { + "Brand": 70007, + "Type": "80100", + "Outline": [[[70, 223, 238, 225, 238, 153, 70, 151, 70, 223]]], + "_id": 7070596103, + "ShopNo": "F7-07", + "Category2": 207, + "dianping_id": -1, + "Brief": "", + "Center": [137.2, 195], + "Category": 207, + "name": "哲学思想屋", + "Name_en": "PHILOSOPHY HOUSE", + "Area": 114.72, + "Name": "哲学思想屋", + "BrandShop": 760007 + }, + { + "Brand": 70008, + "Type": "80100", + "Outline": [[[-22, 333, -140, 406, -84, 451, 81, 416, 0, 351, -22, 333]]], + "_id": 7070596104, + "ShopNo": "F7-08", + "Category2": 208, + "dianping_id": -1, + "Brief": "", + "Center": [-16.92, 393.77], + "Category": 208, + "name": "经济管理书店", + "Name_en": "BUSINESS & MANAGEMENT", + "Area": 168.20, + "Name": "经济管理书店", + "BrandShop": 760008 + } + ], + "Name_en": "Floor 7", + "Outline": [[[-202, -768, 566, -232, 566, 631, -202, -768]]], + "_id": 7, + "High": 5, + "Name": "F7" } ], "building": { @@ -17912,7 +18102,7 @@ "Address": "", "Brief": "", "Area": 13454.140625, - "FloorsId": "-1,1,2,3,4,5,6", + "FloorsId": "-1,1,2,3,4,5,6,7", "Name_en": "", "Version": 5, "High": 1, @@ -17923,7 +18113,7 @@ "Type": "6", "Adcode": "110105", "DefaultFloor": 1, - "Remark": "\"{\"FL_NONA\":\"B1,F1,F2,F3,F4,F5,F6\",\"FL_NAME\":\"爱·生活,爱·精致,爱·风尚,爱·活力,爱·珍味,爱·分享,爱·娱乐\"}\"", + "Remark": "\"{\"FL_NONA\":\"B1,F1,F2,F3,F4,F5,F6,F7\",\"FL_NAME\":\"爱·生活,爱·精致,爱·风尚,爱·活力,爱·珍味,爱·分享,爱·娱乐,爱·阅读\"}\"", "_yLat": 39.9718578960126, "Outline": [ [ diff --git a/index.html b/index.html index 076fca0..3286481 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,8 @@ - + 3D智慧书城 - F7楼层 + @@ -17,30 +18,145 @@ -
- - +
diff --git a/js/IndoorMap3d.js b/js/IndoorMap3d.js index 612762a..1c55582 100644 --- a/js/IndoorMap3d.js +++ b/js/IndoorMap3d.js @@ -20,6 +20,9 @@ IndoorMap3d = function(mapdiv){ var _sceneOrtho, _cameraOrtho;//for 2d var _spriteMaterials = [], _pubPointSprites=null, _nameSprites = null; + var _targetDistance = 500; + var _isSmoothZooming = false; + this.camera = null; this.renderer = null; this.mall = null; @@ -38,6 +41,16 @@ IndoorMap3d = function(mapdiv){ //controls _controls = new THREE.OrbitControls(_this.camera); + _controls.enableDamping = true; + _controls.dampingFactor = 0.08; + _controls.zoomSpeed = 0.8; + _controls.rotateSpeed = 0.5; + _controls.panSpeed = 0.5; + _controls.minDistance = 150; + _controls.maxDistance = 1000; + _controls.enablePan = true; + _controls.enableRotate = true; + _controls.enableZoom = true; //renderer _this.renderer = new THREE.WebGLRenderer({ antialias: true }); @@ -140,12 +153,16 @@ IndoorMap3d = function(mapdiv){ } this.zoomIn = function(zoomScale){ - _controls.zoomOut(zoomScale); + var currentDistance = _this.camera.position.length(); + _targetDistance = Math.max(_controls.minDistance, currentDistance * (1 / zoomScale)); + _isSmoothZooming = true; redraw(); } this.zoomOut = function(zoomScale){ - _controls.zoomIn(zoomScale); + var currentDistance = _this.camera.position.length(); + _targetDistance = Math.min(_controls.maxDistance, currentDistance * zoomScale); + _isSmoothZooming = true; redraw(); } @@ -307,8 +324,25 @@ IndoorMap3d = function(mapdiv){ function animate () { requestAnimationFrame(animate); + + if (_isSmoothZooming) { + var currentDistance = _this.camera.position.length(); + var distanceDiff = _targetDistance - currentDistance; + + if (Math.abs(distanceDiff) > 0.5) { + var direction = _this.camera.position.clone().normalize(); + var lerpFactor = 0.08; + var newDistance = currentDistance + distanceDiff * lerpFactor; + + _this.camera.position.copy(direction.multiplyScalar(newDistance)); + _controls.viewChanged = true; + } else { + _isSmoothZooming = false; + } + } + _controls.update(); - if(_controls.viewChanged) { + if(_controls.viewChanged || _isSmoothZooming) { _this.renderer.clear(); _this.renderer.render(_scene, _this.camera); diff --git a/server.js b/server.js new file mode 100644 index 0000000..ae9ff8c --- /dev/null +++ b/server.js @@ -0,0 +1,49 @@ +const http = require('http'); +const fs = require('fs'); +const path = require('path'); + +const PORT = 8080; + +const mimeTypes = { + '.html': 'text/html', + '.js': 'text/javascript', + '.css': 'text/css', + '.json': 'application/json', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.gif': 'image/gif', + '.svg': 'image/svg+xml', + '.woff': 'font/woff', + '.woff2': 'font/woff2', + '.ttf': 'font/ttf' +}; + +const server = http.createServer((req, res) => { + let filePath = '.' + req.url; + if (filePath === './') { + filePath = './index.html'; + } + + const extname = String(path.extname(filePath)).toLowerCase(); + const contentType = mimeTypes[extname] || 'application/octet-stream'; + + fs.readFile(filePath, (error, content) => { + if (error) { + if(error.code === 'ENOENT') { + res.writeHead(404); + res.end('File not found'); + } else { + res.writeHead(500); + res.end('Server Error: ' + error.code); + } + } else { + res.writeHead(200, { 'Content-Type': contentType }); + res.end(content, 'utf-8'); + } + }); +}); + +server.listen(PORT, () => { + console.log(`Server running at http://localhost:${PORT}/`); + console.log('Press Ctrl+C to stop the server'); +});