feat:西安应急大屏 v3 体验升级(路径规划纵向逐步导航卡片 + 体检雷达/人群现状/穿戴统计数值带单位 + 体重管理新增今日维度)
This commit is contained in:
@@ -279,14 +279,50 @@
|
||||
@keyframes planRouteDash{0%{stroke-dashoffset:0;}100%{stroke-dashoffset:-20;}}
|
||||
.plan-endpoint{transform-origin:center;animation:planEndpointPulse 1.6s ease-in-out infinite;}
|
||||
@keyframes planEndpointPulse{0%,100%{opacity:1;}50%{opacity:0.55;}}
|
||||
.plan-route-info{position:absolute;top:104px;right:12px;z-index:18;display:none;
|
||||
background:rgba(10,22,48,0.88);border:1px solid var(--panel-border);border-radius:8px;
|
||||
padding:8px 12px;font-size:11px;color:var(--text-sub);min-width:130px;
|
||||
backdrop-filter:blur(6px);box-shadow:0 4px 16px rgba(0,0,0,0.4);}
|
||||
.plan-route-info--show{display:block;}
|
||||
.plan-route-info__title{font-size:12px;color:var(--text-main);font-weight:600;margin-bottom:4px;}
|
||||
.plan-route-info__row{display:flex;justify-content:space-between;gap:12px;line-height:1.6;}
|
||||
.plan-route-info__row b{color:#fff;font-weight:600;}
|
||||
/* ========== 路径规划:纵向导航步骤卡片 ========== */
|
||||
.plan-route-info{position:absolute;top:104px;right:12px;z-index:18;display:none;flex-direction:column;
|
||||
width:238px;max-height:calc(100% - 170px);
|
||||
background:rgba(10,22,48,0.92);border:1px solid var(--panel-border);border-radius:10px;
|
||||
backdrop-filter:blur(10px);box-shadow:0 6px 22px rgba(0,0,0,0.5),0 0 16px rgba(0,229,255,0.12);overflow:hidden;}
|
||||
.plan-route-info--show{display:flex;}
|
||||
.plan-route-info__header{display:flex;align-items:center;gap:6px;padding:9px 8px 9px 12px;
|
||||
border-bottom:1px solid var(--panel-border);}
|
||||
.plan-route-info__title{flex:1;display:flex;align-items:center;gap:6px;
|
||||
font-size:13px;color:var(--text-main);font-weight:600;letter-spacing:0.5px;}
|
||||
.plan-route-info__title .ic{width:14px;height:14px;color:var(--cyan);}
|
||||
.plan-route-info__close{width:22px;height:22px;display:flex;align-items:center;justify-content:center;
|
||||
border:none;background:transparent;color:var(--text-sub);cursor:pointer;border-radius:4px;transition:all .2s;}
|
||||
.plan-route-info__close:hover{color:var(--danger);background:rgba(255,77,79,0.12);}
|
||||
.plan-route-info__summary{display:flex;gap:8px;padding:8px 12px;border-bottom:1px solid var(--panel-border);
|
||||
background:rgba(24,144,255,0.06);}
|
||||
.plan-route-info__summary-item{flex:1;display:flex;flex-direction:column;align-items:center;gap:2px;}
|
||||
.plan-route-info__summary-num{font-size:15px;font-weight:700;color:var(--cyan);line-height:1;font-variant-numeric:tabular-nums;}
|
||||
.plan-route-info__summary-label{font-size:10px;color:var(--text-sub);}
|
||||
.plan-route-info__list{flex:1;overflow-y:auto;padding:4px 6px 10px 0;}
|
||||
.plan-route-info__list::-webkit-scrollbar{width:5px;}
|
||||
.plan-route-info__list::-webkit-scrollbar-thumb{background:rgba(24,144,255,0.25);border-radius:3px;}
|
||||
.plan-route-info__list::-webkit-scrollbar-track{background:transparent;}
|
||||
|
||||
/* 单步:左侧时间轴(图标+连线) + 右侧文案 */
|
||||
.plan-route-step{display:flex;gap:9px;padding:7px 6px 0 10px;}
|
||||
.plan-route-step__rail{display:flex;flex-direction:column;align-items:center;flex-shrink:0;width:20px;}
|
||||
.plan-route-step__icon{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;
|
||||
background:rgba(24,144,255,0.16);color:var(--primary);flex-shrink:0;z-index:1;}
|
||||
.plan-route-step__icon svg{width:11px;height:11px;}
|
||||
.plan-route-step__line{width:2px;flex:1;min-height:10px;
|
||||
background:linear-gradient(180deg,rgba(24,144,255,0.45),rgba(24,144,255,0.1));margin:2px 0;}
|
||||
.plan-route-step:last-child .plan-route-step__line{display:none;}
|
||||
.plan-route-step__body{flex:1;padding-bottom:9px;}
|
||||
.plan-route-step__title{font-size:12px;color:var(--text-main);line-height:1.45;}
|
||||
.plan-route-step__title b{font-weight:600;color:#fff;}
|
||||
.plan-route-step__dist{display:inline-block;margin-left:2px;font-size:11px;font-weight:700;color:var(--cyan);font-variant-numeric:tabular-nums;}
|
||||
/* 起点/终点/拐弯 配色 */
|
||||
.plan-route-step--start .plan-route-step__icon{background:rgba(82,196,26,0.2);color:var(--success);}
|
||||
.plan-route-step--end .plan-route-step__icon{background:rgba(255,77,79,0.2);color:var(--danger);}
|
||||
.plan-route-step--turn .plan-route-step__icon{background:rgba(250,173,20,0.2);color:var(--warning);}
|
||||
.plan-route-step--start .plan-route-step__title{color:var(--success);font-weight:600;}
|
||||
.plan-route-step--end .plan-route-step__title{color:var(--danger);font-weight:600;}
|
||||
.plan-route-step--turn .plan-route-step__title b{color:var(--warning);}
|
||||
|
||||
/* ========== 通用面板(增强) ========== */
|
||||
.panel{background:var(--panel-bg);border:1px solid var(--panel-border);border-radius:10px;
|
||||
@@ -452,6 +488,8 @@
|
||||
@keyframes radarPulse{0%,100%{fill-opacity:0.15;}50%{fill-opacity:0.30;}}
|
||||
.exam-radar__point{fill:var(--primary);r:3;stroke:#fff;stroke-width:1.5;filter:drop-shadow(0 0 4px rgba(24,144,255,0.6));}
|
||||
.exam-radar__label{font-size:10px;fill:var(--text-sub);text-anchor:middle;dominant-baseline:middle;}
|
||||
.exam-radar__label-value{font-size:10px;fill:#fff;font-weight:700;font-variant-numeric:tabular-nums;margin-left:2px;}
|
||||
.exam-radar__label-unit{font-size:10px;fill:var(--text-sub);font-weight:400;}
|
||||
.exam-radar__value{font-size:9px;fill:#fff;font-weight:600;text-anchor:middle;dominant-baseline:middle;}
|
||||
.exam-radar-legend{display:flex;flex-wrap:wrap;gap:6px 12px;margin-top:6px;justify-content:center;}
|
||||
.exam-radar-legend__item{display:flex;align-items:center;gap:4px;font-size:10px;color:var(--text-sub);}
|
||||
@@ -553,7 +591,7 @@
|
||||
.event-item__desc{font-size:11px;color:var(--text-sub);margin-top:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
||||
.event-item__loc{font-size:10px;color:var(--text-dim);margin-top:2px;}
|
||||
|
||||
/* ========== 左下:人员现状监测(环形图) ========== */
|
||||
/* ========== 左下:人群现状(环形图) ========== */
|
||||
.panel--health-monitor .panel__body{padding:6px 8px 8px;overflow:hidden;}
|
||||
.health-monitor{display:flex;align-items:center;justify-content:center;gap:6px;padding:0;}
|
||||
.health-monitor__chart{position:relative;width:208px;height:208px;flex-shrink:0;}
|
||||
@@ -564,6 +602,7 @@
|
||||
.health-monitor__chart .hm-label-line{fill:none;stroke-width:1;opacity:.72;stroke-linecap:round;stroke-linejoin:round;}
|
||||
.health-monitor__chart .hm-value-label{font-size:11px;font-weight:700;font-variant-numeric:tabular-nums;
|
||||
paint-order:stroke;stroke:#071224;stroke-width:4px;stroke-linejoin:round;}
|
||||
.health-monitor__chart .hm-value-unit{font-size:9px;font-weight:400;}
|
||||
.health-monitor__center{position:absolute;inset:0;display:flex;flex-direction:column;
|
||||
align-items:center;justify-content:center;pointer-events:none;text-align:center;}
|
||||
.health-monitor__center-label{font-size:10px;color:var(--text-sub);letter-spacing:1.5px;}
|
||||
@@ -588,6 +627,7 @@
|
||||
.wearable-stat-chart__bar{height:10px;border-radius:2px 5px 5px 2px;transition:width .6s ease;min-width:4px;position:relative;box-shadow:0 0 6px currentColor;}
|
||||
.wearable-stat-chart__bar::after{content:'';position:absolute;top:0;left:0;bottom:0;width:20px;background:linear-gradient(90deg,rgba(255,255,255,0.3),transparent);border-radius:2px 0 0 5px;}
|
||||
.wearable-stat-chart__bar-value{font-size:10px;color:#fff;font-weight:600;text-shadow:0 0 6px rgba(0,0,0,0.4);white-space:nowrap;min-width:20px;text-align:left;line-height:1;}
|
||||
.wearable-stat-chart__bar-unit{font-size:10px;color:var(--text-sub);font-weight:400;margin-left:1px;}
|
||||
|
||||
/* ========== AI智能预警分析 ========== */
|
||||
.panel--ai-analysis .panel__body{padding:8px 10px 10px;overflow:hidden;}
|
||||
@@ -1096,42 +1136,42 @@
|
||||
<div class="wearable-stat-chart__bar-wrap">
|
||||
<div class="wearable-stat-chart__bar" style="width:90%;background:#FF4D4F;color:#FF4D4F;"></div>
|
||||
</div>
|
||||
<div class="wearable-stat-chart__bar-value">45</div>
|
||||
<div class="wearable-stat-chart__bar-value">45<span class="wearable-stat-chart__bar-unit">次</span></div>
|
||||
</div>
|
||||
<div class="wearable-stat-chart__col">
|
||||
<div class="wearable-stat-chart__label">体温异常</div>
|
||||
<div class="wearable-stat-chart__bar-wrap">
|
||||
<div class="wearable-stat-chart__bar" style="width:56%;background:#FAAD14;color:#FAAD14;"></div>
|
||||
</div>
|
||||
<div class="wearable-stat-chart__bar-value">28</div>
|
||||
<div class="wearable-stat-chart__bar-value">28<span class="wearable-stat-chart__bar-unit">次</span></div>
|
||||
</div>
|
||||
<div class="wearable-stat-chart__col">
|
||||
<div class="wearable-stat-chart__label">血氧异常</div>
|
||||
<div class="wearable-stat-chart__bar-wrap">
|
||||
<div class="wearable-stat-chart__bar" style="width:70%;background:#1890FF;color:#1890FF;"></div>
|
||||
</div>
|
||||
<div class="wearable-stat-chart__bar-value">35</div>
|
||||
<div class="wearable-stat-chart__bar-value">35<span class="wearable-stat-chart__bar-unit">次</span></div>
|
||||
</div>
|
||||
<div class="wearable-stat-chart__col">
|
||||
<div class="wearable-stat-chart__label">压力异常</div>
|
||||
<div class="wearable-stat-chart__bar-wrap">
|
||||
<div class="wearable-stat-chart__bar" style="width:36%;background:#00e5ff;color:#00e5ff;"></div>
|
||||
</div>
|
||||
<div class="wearable-stat-chart__bar-value">18</div>
|
||||
<div class="wearable-stat-chart__bar-value">18<span class="wearable-stat-chart__bar-unit">次</span></div>
|
||||
</div>
|
||||
<div class="wearable-stat-chart__col">
|
||||
<div class="wearable-stat-chart__label">一键告警</div>
|
||||
<div class="wearable-stat-chart__bar-wrap">
|
||||
<div class="wearable-stat-chart__bar" style="width:24%;background:#b37feb;color:#b37feb;"></div>
|
||||
</div>
|
||||
<div class="wearable-stat-chart__bar-value">12</div>
|
||||
<div class="wearable-stat-chart__bar-value">12<span class="wearable-stat-chart__bar-unit">次</span></div>
|
||||
</div>
|
||||
<div class="wearable-stat-chart__col">
|
||||
<div class="wearable-stat-chart__label">摔倒</div>
|
||||
<div class="wearable-stat-chart__bar-wrap">
|
||||
<div class="wearable-stat-chart__bar" style="width:16%;background:#FF7875;color:#FF7875;"></div>
|
||||
</div>
|
||||
<div class="wearable-stat-chart__bar-value">8</div>
|
||||
<div class="wearable-stat-chart__bar-value">8<span class="wearable-stat-chart__bar-unit">次</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1202,11 +1242,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 人员现状监测(环形图) -->
|
||||
<!-- 人群现状(环形图) -->
|
||||
<div class="panel panel--health-monitor" style="flex-shrink:0;">
|
||||
<div class="panel__header">
|
||||
<div class="panel__header-diamond"></div>
|
||||
<span class="panel__header-title">人员现状监测</span>
|
||||
<span class="panel__header-title">人群现状</span>
|
||||
</div>
|
||||
<div class="panel__body">
|
||||
<div class="health-monitor">
|
||||
@@ -1241,11 +1281,11 @@
|
||||
<polyline class="hm-label-line" points="192,115 198,112 204,110" stroke="#00e5ff"/>
|
||||
<polyline class="hm-label-line" points="192,128 198,134 204,140" stroke="#b37feb"/>
|
||||
<polyline class="hm-label-line" points="66,168 50,164 36,160" stroke="#52C41A"/>
|
||||
<text class="hm-value-label" x="170" y="30" fill="#FF4D4F" text-anchor="middle" dominant-baseline="middle">580</text>
|
||||
<text class="hm-value-label" x="206" y="80" fill="#FAAD14" text-anchor="middle" dominant-baseline="middle">1,860</text>
|
||||
<text class="hm-value-label" x="210" y="108" fill="#00e5ff" text-anchor="middle" dominant-baseline="middle">620</text>
|
||||
<text class="hm-value-label" x="210" y="140" fill="#b37feb" text-anchor="middle" dominant-baseline="middle">376</text>
|
||||
<text class="hm-value-label" x="30" y="160" fill="#52C41A" text-anchor="middle" dominant-baseline="middle">9,420</text>
|
||||
<text class="hm-value-label" x="170" y="30" fill="#FF4D4F" text-anchor="middle" dominant-baseline="middle">580<tspan class="hm-value-unit"> 人</tspan></text>
|
||||
<text class="hm-value-label" x="206" y="80" fill="#FAAD14" text-anchor="middle" dominant-baseline="middle">1,860<tspan class="hm-value-unit"> 人</tspan></text>
|
||||
<text class="hm-value-label" x="210" y="108" fill="#00e5ff" text-anchor="middle" dominant-baseline="middle">620<tspan class="hm-value-unit"> 人</tspan></text>
|
||||
<text class="hm-value-label" x="210" y="140" fill="#b37feb" text-anchor="middle" dominant-baseline="middle">376<tspan class="hm-value-unit"> 人</tspan></text>
|
||||
<text class="hm-value-label" x="30" y="160" fill="#52C41A" text-anchor="middle" dominant-baseline="middle">9,420<tspan class="hm-value-unit"> 人</tspan></text>
|
||||
</svg>
|
||||
<div class="health-monitor__center">
|
||||
<span class="health-monitor__center-label">总人数</span>
|
||||
@@ -1389,11 +1429,32 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- [交互] 路径规划信息卡片:规划完成后在地图右上角显示距离与用时 -->
|
||||
<!-- [交互] 路径规划导航卡片:规划完成后在地图右上角纵向展示逐步导航(起点->途经路/方向/拐弯->终点) -->
|
||||
<div class="plan-route-info" id="planRouteInfo">
|
||||
<div class="plan-route-info__title">规划路线</div>
|
||||
<div class="plan-route-info__row"><span>总距离</span><b id="planRouteDist">--</b></div>
|
||||
<div class="plan-route-info__row"><span>预计用时</span><b id="planRouteTime">--</b></div>
|
||||
<div class="plan-route-info__header">
|
||||
<div class="plan-route-info__title">
|
||||
<svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="3 11 22 2 13 21 11 13 3 11"/></svg>
|
||||
导航路线
|
||||
</div>
|
||||
<button class="plan-route-info__close" title="关闭" onclick="clearRoutePlan()">
|
||||
<svg viewBox="0 0 16 16" fill="none" width="14" height="14"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="plan-route-info__summary">
|
||||
<div class="plan-route-info__summary-item">
|
||||
<span class="plan-route-info__summary-num" id="planRouteDist">--</span>
|
||||
<span class="plan-route-info__summary-label">总距离</span>
|
||||
</div>
|
||||
<div class="plan-route-info__summary-item">
|
||||
<span class="plan-route-info__summary-num" id="planRouteTime">--</span>
|
||||
<span class="plan-route-info__summary-label">预计用时</span>
|
||||
</div>
|
||||
<div class="plan-route-info__summary-item">
|
||||
<span class="plan-route-info__summary-num" id="planRouteSteps">--</span>
|
||||
<span class="plan-route-info__summary-label">途经步骤</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="plan-route-info__list" id="planRouteList"></div>
|
||||
</div>
|
||||
|
||||
<div class="map-bg"></div>
|
||||
@@ -1770,9 +1831,10 @@
|
||||
<div class="panel__header">
|
||||
<div class="panel__header-diamond"></div>
|
||||
<span class="panel__header-title">体重管理数据</span>
|
||||
<!-- [交互] 时间段切换:近一周/近一月/近一年 -->
|
||||
<!-- [交互] 时间段切换:今日/近一周/近一月/近一年 -->
|
||||
<select class="panel__header-select" onchange="switchWeightPeriod(this.value)">
|
||||
<option value="week" selected>近一周</option>
|
||||
<option value="today" selected>今日</option>
|
||||
<option value="week">近一周</option>
|
||||
<option value="month">近一月</option>
|
||||
<option value="year">近一年</option>
|
||||
</select>
|
||||
@@ -3285,17 +3347,18 @@ function switchConsultPeriod(period){
|
||||
}
|
||||
|
||||
/* ========== 体重管理数据 时间段切换 ========== */
|
||||
/* [数据] 近一周/近一月/近一年,吃记录/动记录/体重记录 的已记录、未记录人数 */
|
||||
var currentWeightPeriod = 'week';
|
||||
/* [数据] 今日/近一周/近一月/近一年,吃记录/动记录/体重记录 的已记录、未记录人数 */
|
||||
var currentWeightPeriod = 'today';
|
||||
var WEIGHT_DATA = {
|
||||
today: { eatDone:28, eatUndone:9, exerciseDone:21, exerciseUndone:16, weightDone:31, weightUndone:6 },
|
||||
week: { eatDone:186, eatUndone:54, exerciseDone:142, exerciseUndone:98, weightDone:203, weightUndone:37 },
|
||||
month: { eatDone:642, eatUndone:208, exerciseDone:498, exerciseUndone:352, weightDone:712, weightUndone:138 },
|
||||
year: { eatDone:7680,eatUndone:2460,exerciseDone:5964,exerciseUndone:4176,weightDone:8532,weightUndone:1612 }
|
||||
};
|
||||
|
||||
function switchWeightPeriod(period){
|
||||
/* [交互] 根据 period('week'/'month'/'year') 刷新体重管理数据卡片 */
|
||||
currentWeightPeriod = period || 'week';
|
||||
/* [交互] 根据 period('today'/'week'/'month'/'year') 刷新体重管理数据卡片 */
|
||||
currentWeightPeriod = period || 'today';
|
||||
var data = WEIGHT_DATA[currentWeightPeriod];
|
||||
/* 按 DOM 顺序:吃记录(已/未)、动记录(已/未)、体重记录(已/未) */
|
||||
var nums = document.querySelectorAll('.panel--weight-data .weight-data__stat-num');
|
||||
@@ -3308,6 +3371,9 @@ function switchWeightPeriod(period){
|
||||
nums[5].textContent = data.weightUndone.toLocaleString();
|
||||
}
|
||||
}
|
||||
/* 页面加载时按默认时间段(今日)刷新一次,使显示与默认选中项一致 */
|
||||
if(document.readyState !== 'loading'){switchWeightPeriod('today');}
|
||||
else{document.addEventListener('DOMContentLoaded', function(){switchWeightPeriod('today');});}
|
||||
|
||||
/* ========== 医疗点数据:时间段切换 ========== */
|
||||
/* [数据] 近一周/近一月/近一年各医疗点指标(慢病服务人数/就诊次数/随访次数) */
|
||||
@@ -3463,25 +3529,14 @@ function updateExamRadarChart(){
|
||||
return '<circle cx="' + p.x + '" cy="' + p.y + '" class="exam-radar__point"/>';
|
||||
}).join('');
|
||||
|
||||
/* 标签 */
|
||||
/* 标签:医院名称 + 数值(数值紧跟在名称后面) */
|
||||
var labels = '';
|
||||
for(var i = 0; i < count; i++){
|
||||
var a = i * angleStep - Math.PI / 2;
|
||||
var labelR = radius + 18;
|
||||
var lx = cx + labelR * Math.cos(a);
|
||||
var ly = cy + labelR * Math.sin(a);
|
||||
labels += '<text x="' + lx.toFixed(1) + '" y="' + ly.toFixed(1) + '" class="exam-radar__label">' + hospitals[i] + '</text>';
|
||||
}
|
||||
|
||||
/* 数值标签(在数据点旁边) */
|
||||
var valueLabels = '';
|
||||
for(var i = 0; i < pointCoords.length; i++){
|
||||
var p = pointCoords[i];
|
||||
var a = i * angleStep - Math.PI / 2;
|
||||
var vr = 12;
|
||||
var vx = parseFloat(p.x) + vr * Math.cos(a) * 0.3;
|
||||
var vy = parseFloat(p.y) + vr * Math.sin(a) * 0.3;
|
||||
valueLabels += '<text x="' + vx.toFixed(1) + '" y="' + vy.toFixed(1) + '" class="exam-radar__value">' + p.val.toLocaleString('zh-CN') + '</text>';
|
||||
labels += '<text x="' + lx.toFixed(1) + '" y="' + ly.toFixed(1) + '" class="exam-radar__label">' + hospitals[i] + '<tspan class="exam-radar__label-value"> ' + values[i].toLocaleString('zh-CN') + '</tspan><tspan class="exam-radar__label-unit"> 人</tspan></text>';
|
||||
}
|
||||
|
||||
container.innerHTML =
|
||||
@@ -3491,13 +3546,7 @@ function updateExamRadarChart(){
|
||||
area +
|
||||
dots +
|
||||
labels +
|
||||
valueLabels +
|
||||
'</svg>'+
|
||||
'<div class="exam-radar__center-info">'+
|
||||
'<span class="exam-radar__center-label">体检总人数</span>'+
|
||||
'<span class="exam-radar__center-num">' + total.toLocaleString('zh-CN') + '</span>'+
|
||||
'<span class="exam-radar__center-unit">人</span>'+
|
||||
'</div>';
|
||||
'</svg>';
|
||||
}
|
||||
|
||||
/* [初始化] 页面加载时默认渲染体检数据雷达图 */
|
||||
@@ -3563,6 +3612,99 @@ var PLAN_POINTS = {
|
||||
aed2: {x:68, y:47, name:'AED-002'}
|
||||
};
|
||||
|
||||
/* ========== 路径规划:逐步导航生成 ==========
|
||||
基于起点/终点的相对方位(Δx/Δy),将路线拆为若干「沿某路·某方向·某米」的直行段,
|
||||
并在方向改变处插入「在某路口左/右转/直行」的拐弯提示,最后到达终点。 */
|
||||
var PLAN_ROAD_NAMES = ['长庆大道','油田路','兴庆路','矿区环线','靖边街','姬塬路','采油大道','高陵快速路'];
|
||||
/* 八方向文案(屏幕坐标:x→右=东,y→下=南) */
|
||||
var DIR_MATRIX = [
|
||||
{dx: 1, dy: 0, name:'向东'},
|
||||
{dx: 1, dy: 1, name:'向东南'},
|
||||
{dx: 0, dy: 1, name:'向南'},
|
||||
{dx:-1, dy: 1, name:'向西南'},
|
||||
{dx:-1, dy: 0, name:'向西'},
|
||||
{dx:-1, dy:-1, name:'向西北'},
|
||||
{dx: 0, dy:-1, name:'向北'},
|
||||
{dx: 1, dy:-1, name:'向东北'}
|
||||
];
|
||||
var TURN_ICON = {
|
||||
left: '<svg viewBox="0 0 16 16" fill="none"><path d="M3 9h7a3 3 0 013 3v1" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/><path d="M6 6L3 9l3 3" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
right:'<svg viewBox="0 0 16 16" fill="none"><path d="M13 9H6a3 3 0 00-3 3v1" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/><path d="M10 6l3 3-3 3" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
straight:'<svg viewBox="0 0 16 16" fill="none"><path d="M8 13V4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><path d="M5 7l3-3 3 3" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>'
|
||||
};
|
||||
var STEP_ICON = {
|
||||
start:'<svg viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="3" fill="currentColor"/><circle cx="8" cy="8" r="6.2" stroke="currentColor" stroke-width="1.4" opacity="0.5"/></svg>',
|
||||
end: '<svg viewBox="0 0 16 16" fill="none"><path d="M8 1.5l1.9 4.1 4.5.5-3.3 3 .9 4.4L8 11.3 4 13.5l.9-4.4-3.3-3 4.5-.5z" fill="currentColor"/></svg>',
|
||||
road: '<svg viewBox="0 0 16 16" fill="none"><path d="M5 2v12M11 2v12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M8 3v1.5M8 7v1.5M8 11v1.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>'
|
||||
};
|
||||
/* 计算由 from→to 的主导八方向 */
|
||||
function planDir(from,to){
|
||||
var dx=to.x-from.x, dy=to.y-from.y;
|
||||
if(Math.abs(dx)<0.001 && Math.abs(dy)<0.001) return DIR_MATRIX[0];
|
||||
var ang=Math.atan2(dy,dx); /* -π..π,0=东 */
|
||||
var idx=Math.round(ang/(Math.PI/4)); /* -4..4 */
|
||||
if(idx<0) idx+=8;
|
||||
return DIR_MATRIX[idx%8];
|
||||
}
|
||||
/* 计算两方向夹角对应的左转/右转/直行 */
|
||||
function planTurnType(prevDir,curDir){
|
||||
var pi=DIR_MATRIX.indexOf(prevDir), ci=DIR_MATRIX.indexOf(curDir);
|
||||
if(pi<0||ci<0) return 'straight';
|
||||
var diff=ci-pi;
|
||||
if(diff>4) diff-=8; if(diff<-4) diff+=8; /* 归一到 -4..4 */
|
||||
if(diff===0) return 'straight';
|
||||
return diff>0 ? 'right' : 'left'; /* y轴朝下:顺时针角增=右转 */
|
||||
}
|
||||
/* 在 path 上取等分中间折点,让路名随距离逐段变化 */
|
||||
function buildRouteSteps(s,e,totalMeters){
|
||||
/* 折点数量:长则多分段,控制在 2~4 段直行 */
|
||||
var segs = totalMeters>1500 ? 4 : totalMeters>700 ? 3 : 2;
|
||||
var pts=[{x:s.x,y:s.y}];
|
||||
for(var i=1;i<segs;i++){
|
||||
/* 沿直线略带扰动,模拟走不同路段 */
|
||||
var t=i/segs;
|
||||
var px=s.x+(e.x-s.x)*t + (e.y-s.y)*0.04*((i%2)?1:-1);
|
||||
var py=s.y+(e.y-s.y)*t + (s.x-e.x)*0.04*((i%2)?1:-1);
|
||||
pts.push({x:px,y:py});
|
||||
}
|
||||
pts.push({x:e.x,y:e.y});
|
||||
|
||||
var steps=[];
|
||||
var prevDir=null, roadIdx=0;
|
||||
for(var j=0;j<pts.length-1;j++){
|
||||
var a=pts[j], b=pts[j+1];
|
||||
var dir=planDir(a,b);
|
||||
var dist=Math.round(totalMeters/segs);
|
||||
/* 拐弯提示(非首段且方向变化) */
|
||||
if(prevDir && dir.name!==prevDir.name){
|
||||
var tt=planTurnType(prevDir,dir);
|
||||
steps.push({type:'turn',
|
||||
icon:TURN_ICON[tt],
|
||||
title:'在<b>'+PLAN_ROAD_NAMES[roadIdx%PLAN_ROAD_NAMES.length]+'</b>路口<b>'+({left:'左转',right:'右转',straight:'继续直行'}[tt])+'</b>'});
|
||||
roadIdx++;
|
||||
}
|
||||
var roadName=PLAN_ROAD_NAMES[roadIdx%PLAN_ROAD_NAMES.length];
|
||||
steps.push({type:'road', icon:STEP_ICON.road,
|
||||
title:'沿<b>'+roadName+'</b>'+dir.name+'行驶', dist:dist});
|
||||
prevDir=dir; roadIdx++;
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
/* 把单步渲染成 HTML */
|
||||
function renderRouteStep(step){
|
||||
var cls = step.type; /* start / road / turn / end */
|
||||
var distHtml = (step.type==='road' && step.dist) ? '<span class="plan-route-step__dist">'+step.dist+' 米</span>' : '';
|
||||
return '<div class="plan-route-step plan-route-step--'+cls+'">'
|
||||
+'<div class="plan-route-step__rail">'
|
||||
+'<div class="plan-route-step__icon">'+step.icon+'</div>'
|
||||
+'<div class="plan-route-step__line"></div>'
|
||||
+'</div>'
|
||||
+'<div class="plan-route-step__body">'
|
||||
+'<div class="plan-route-step__title">'+step.title+distHtml+'</div>'
|
||||
+'</div>'
|
||||
+'</div>';
|
||||
}
|
||||
|
||||
/* 切换路径规划 Tab:展开/收起规划面板,并保持所有地图标记可见 */
|
||||
function toggleRoutePlan(el){
|
||||
var tab=document.getElementById('planTab');
|
||||
@@ -3601,16 +3743,18 @@ function clearRoutePlan(){
|
||||
var svg=document.getElementById('planRouteSvg');
|
||||
var path=document.getElementById('planRoutePath');
|
||||
var info=document.getElementById('planRouteInfo');
|
||||
var list=document.getElementById('planRouteList');
|
||||
var sd=document.getElementById('planStartDot');
|
||||
var ed=document.getElementById('planEndDot');
|
||||
if(svg)svg.classList.remove('plan-route-svg--show');
|
||||
if(path)path.setAttribute('opacity','0');
|
||||
if(info)info.classList.remove('plan-route-info--show');
|
||||
if(list)list.innerHTML='';
|
||||
if(sd)sd.setAttribute('opacity','0');
|
||||
if(ed)ed.setAttribute('opacity','0');
|
||||
}
|
||||
|
||||
/* 执行路径规划:根据起点终点绘制曲线路径并显示距离与用时 */
|
||||
/* 执行路径规划:根据起点终点绘制曲线路径,并按纵向步骤展示逐段导航(路/方向/米/拐弯) */
|
||||
function executeRoutePlan(){
|
||||
var sk=document.getElementById('planStart').value;
|
||||
var ek=document.getElementById('planEnd').value;
|
||||
@@ -3620,6 +3764,7 @@ function executeRoutePlan(){
|
||||
var path=document.getElementById('planRoutePath');
|
||||
var svg=document.getElementById('planRouteSvg');
|
||||
var info=document.getElementById('planRouteInfo');
|
||||
var list=document.getElementById('planRouteList');
|
||||
var sd=document.getElementById('planStartDot');
|
||||
var ed=document.getElementById('planEndDot');
|
||||
/* 二次贝塞尔曲线:控制点垂直偏移,使路径呈现弧形而非直线 */
|
||||
@@ -3631,12 +3776,22 @@ function executeRoutePlan(){
|
||||
sd.setAttribute('cx',s.x);sd.setAttribute('cy',s.y);sd.setAttribute('opacity','1');
|
||||
ed.setAttribute('cx',e.x);ed.setAttribute('cy',e.y);ed.setAttribute('opacity','1');
|
||||
svg.classList.add('plan-route-svg--show');
|
||||
/* Mock 距离/用时:按 viewBox 距离换算 */
|
||||
/* Mock 距离/用时:按 viewBox 距离换算为米 */
|
||||
var dx=s.x-e.x, dy=s.y-e.y, dist=Math.sqrt(dx*dx+dy*dy);
|
||||
var km=(dist*0.15).toFixed(1);
|
||||
var min=Math.max(1,Math.round(dist*0.6));
|
||||
var totalMeters=Math.max(120, Math.round(dist*150));
|
||||
var km=(totalMeters/1000).toFixed(1);
|
||||
var min=Math.max(1,Math.round(totalMeters/180)); /* 约 10.8km/h 综合车速 */
|
||||
document.getElementById('planRouteDist').textContent=km+' km';
|
||||
document.getElementById('planRouteTime').textContent=min+' 分钟';
|
||||
|
||||
/* 生成纵向导航步骤:起点 → 各直行段(沿X路·向X方向·X米) → 拐弯 → 终点 */
|
||||
var legs=buildRouteSteps(s,e,totalMeters);
|
||||
var html='';
|
||||
html+=renderRouteStep({type:'start', icon:STEP_ICON.start, title:'从<b>'+s.name+'</b>出发'});
|
||||
html+=legs.map(renderRouteStep).join('');
|
||||
html+=renderRouteStep({type:'end', icon:STEP_ICON.end, title:'到达<b>'+e.name+'</b>'});
|
||||
if(list)list.innerHTML=html;
|
||||
document.getElementById('planRouteSteps').textContent=legs.length+2;
|
||||
info.classList.add('plan-route-info--show');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user