| | |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <title>库位地图</title> |
| | | <link rel="stylesheet" href="../../static/css/animate.min.css"> |
| | | <link rel="stylesheet" href="../../static/vue/element/element.css"> |
| | | <link rel="stylesheet" href="../../static/css/console_vue.css"> |
| | | <link rel="stylesheet" href="../../static/css/toggle-switch.css"> |
| | | <script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script> |
| | | <script type="text/javascript" src="../../static/layui/layui.js"></script> |
| | | <script type="text/javascript" src="../../static/js/handlebars/handlebars-v4.5.3.js"></script> |
| | | <script type="text/javascript" src="../../static/js/common.js"></script> |
| | | <script type="text/javascript" src="../../static/vue/js/vue.min.js"></script> |
| | | <script type="text/javascript" src="../../static/vue/element/element.js"></script> |
| | | <script src="../../static/js/gsap.min.js"></script> |
| | | <script src="../../static/js/pixi-legacy.min.js"></script> |
| | | <style> |
| | | *{ |
| | | html, body, #app { |
| | | width: 100%; |
| | | height: 100%; |
| | | margin: 0; |
| | | padding: 0; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | body { |
| | | background: linear-gradient(180deg, #eef4f8 0%, #e7edf4 100%); |
| | | font-family: "Helvetica Neue", Arial, sans-serif; |
| | | } |
| | | |
| | | * { |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .locmap-shell { |
| | | position: relative; |
| | | width: 100%; |
| | | height: 100%; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .locmap-canvas { |
| | | position: absolute; |
| | | inset: 0; |
| | | } |
| | | |
| | | .locmap-title-card { |
| | | position: absolute; |
| | | top: 18px; |
| | | left: 18px; |
| | | z-index: 20; |
| | | padding: 14px 16px; |
| | | min-width: 220px; |
| | | border-radius: 18px; |
| | | border: 1px solid rgba(255, 255, 255, 0.42); |
| | | background: rgba(248, 251, 253, 0.92); |
| | | box-shadow: 0 10px 24px rgba(88, 110, 136, 0.08); |
| | | backdrop-filter: blur(6px); |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .locmap-title { |
| | | color: #243447; |
| | | font-size: 18px; |
| | | font-weight: 700; |
| | | line-height: 1.2; |
| | | } |
| | | |
| | | .locmap-title-desc { |
| | | margin-top: 6px; |
| | | color: #6b7b8d; |
| | | font-size: 12px; |
| | | line-height: 1.5; |
| | | } |
| | | |
| | | .locmap-tools { |
| | | position: absolute; |
| | | top: 18px; |
| | | right: 28px; |
| | | z-index: 30; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: flex-end; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .locmap-fps { |
| | | padding: 4px 10px; |
| | | border-radius: 999px; |
| | | background: rgba(255, 255, 255, 0.7); |
| | | border: 1px solid rgba(160, 180, 205, 0.28); |
| | | color: #48617c; |
| | | font-size: 12px; |
| | | line-height: 18px; |
| | | letter-spacing: 0.04em; |
| | | box-shadow: 0 6px 16px rgba(37, 64, 97, 0.06); |
| | | user-select: none; |
| | | } |
| | | |
| | | .locmap-tool-toggle, |
| | | .locmap-tool-btn, |
| | | .locmap-floor-btn, |
| | | .locmap-detail-close { |
| | | appearance: none; |
| | | cursor: pointer; |
| | | transition: all .18s ease; |
| | | outline: none; |
| | | } |
| | | |
| | | .locmap-tool-toggle { |
| | | height: 30px; |
| | | padding: 0 12px; |
| | | border-radius: 999px; |
| | | border: 1px solid rgba(160, 180, 205, 0.3); |
| | | background: rgba(255, 255, 255, 0.82); |
| | | color: #46617b; |
| | | font-size: 12px; |
| | | line-height: 30px; |
| | | box-shadow: 0 6px 16px rgba(37, 64, 97, 0.06); |
| | | } |
| | | |
| | | .locmap-tool-toggle.is-active { |
| | | border-color: rgba(96, 132, 170, 0.36); |
| | | background: rgba(235, 243, 251, 0.96); |
| | | } |
| | | |
| | | .locmap-tool-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 8px; |
| | | min-width: 168px; |
| | | padding: 8px; |
| | | border-radius: 14px; |
| | | background: rgba(255, 255, 255, 0.72); |
| | | border: 1px solid rgba(160, 180, 205, 0.3); |
| | | box-shadow: 0 8px 20px rgba(37, 64, 97, 0.08); |
| | | backdrop-filter: blur(4px); |
| | | } |
| | | |
| | | .locmap-tool-row { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 8px; |
| | | justify-content: flex-end; |
| | | } |
| | | |
| | | .locmap-tool-btn, |
| | | .locmap-floor-btn { |
| | | min-width: 64px; |
| | | height: 30px; |
| | | padding: 0 12px; |
| | | border-radius: 10px; |
| | | border: 1px solid rgba(160, 180, 205, 0.3); |
| | | background: rgba(255, 255, 255, 0.88); |
| | | color: #4d647d; |
| | | font-size: 12px; |
| | | line-height: 30px; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .locmap-tool-btn:hover, |
| | | .locmap-floor-btn:hover, |
| | | .locmap-detail-close:hover, |
| | | .locmap-tool-toggle:hover { |
| | | transform: translateY(-1px); |
| | | } |
| | | |
| | | .locmap-tool-btn.is-active, |
| | | .locmap-floor-btn.is-active { |
| | | border-color: rgba(255, 136, 93, 0.38); |
| | | background: rgba(255, 119, 77, 0.16); |
| | | color: #d85a31; |
| | | } |
| | | |
| | | .locmap-tool-section { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 4px; |
| | | padding-top: 6px; |
| | | border-top: 1px solid rgba(160, 180, 205, 0.22); |
| | | } |
| | | |
| | | .locmap-tool-label { |
| | | color: #6a7f95; |
| | | font-size: 10px; |
| | | line-height: 14px; |
| | | text-align: right; |
| | | } |
| | | |
| | | .locmap-detail-panel { |
| | | position: absolute; |
| | | top: 92px; |
| | | right: 18px; |
| | | bottom: 18px; |
| | | z-index: 25; |
| | | width: min(max(320px, 25vw), calc(100vw - 92px)); |
| | | border-radius: 20px; |
| | | border: 1px solid rgba(255, 255, 255, 0.42); |
| | | background: rgba(248, 251, 253, 0.94); |
| | | box-shadow: 0 10px 24px rgba(88, 110, 136, 0.08); |
| | | overflow: hidden; |
| | | display: flex; |
| | | flex-direction: column; |
| | | backdrop-filter: blur(6px); |
| | | } |
| | | |
| | | .locmap-detail-header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | gap: 12px; |
| | | padding: 16px 16px 12px; |
| | | border-bottom: 1px solid rgba(226, 232, 240, 0.72); |
| | | background: rgba(255, 255, 255, 0.24); |
| | | } |
| | | |
| | | .locmap-detail-title-wrap { |
| | | min-width: 0; |
| | | } |
| | | |
| | | .locmap-detail-type { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | height: 22px; |
| | | padding: 0 10px; |
| | | border-radius: 999px; |
| | | background: rgba(103, 149, 193, 0.12); |
| | | color: #4b6782; |
| | | font-size: 11px; |
| | | font-weight: 700; |
| | | } |
| | | |
| | | .locmap-detail-title { |
| | | margin-top: 8px; |
| | | color: #243447; |
| | | font-size: 18px; |
| | | font-weight: 700; |
| | | line-height: 1.2; |
| | | } |
| | | |
| | | .locmap-detail-subtitle { |
| | | margin-top: 6px; |
| | | color: #6b7b8d; |
| | | font-size: 12px; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | .locmap-detail-close { |
| | | flex-shrink: 0; |
| | | width: 32px; |
| | | height: 32px; |
| | | border-radius: 10px; |
| | | border: 1px solid rgba(160, 180, 205, 0.28); |
| | | background: rgba(255, 255, 255, 0.84); |
| | | color: #4d647d; |
| | | font-size: 18px; |
| | | line-height: 30px; |
| | | text-align: center; |
| | | } |
| | | |
| | | .locmap-detail-body { |
| | | flex: 1; |
| | | min-height: 0; |
| | | padding: 16px; |
| | | overflow: auto; |
| | | } |
| | | |
| | | .locmap-kv-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(2, minmax(0, 1fr)); |
| | | gap: 12px; |
| | | } |
| | | |
| | | .locmap-kv-card { |
| | | padding: 14px; |
| | | border-radius: 16px; |
| | | border: 1px solid rgba(224, 232, 239, 0.92); |
| | | background: rgba(255, 255, 255, 0.62); |
| | | box-shadow: 0 8px 18px rgba(148, 163, 184, 0.06); |
| | | } |
| | | |
| | | .locmap-kv-label { |
| | | color: #7d8fa2; |
| | | font-size: 12px; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | .locmap-kv-value { |
| | | margin-top: 8px; |
| | | color: #334155; |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | line-height: 1.35; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | .locmap-empty { |
| | | position: absolute; |
| | | right: 18px; |
| | | bottom: 18px; |
| | | z-index: 20; |
| | | width: min(max(260px, 20vw), calc(100vw - 92px)); |
| | | padding: 18px; |
| | | border-radius: 18px; |
| | | border: 1px solid rgba(255, 255, 255, 0.4); |
| | | background: rgba(248, 251, 253, 0.92); |
| | | box-shadow: 0 10px 24px rgba(88, 110, 136, 0.08); |
| | | color: #6b7b8d; |
| | | font-size: 12px; |
| | | line-height: 1.7; |
| | | backdrop-filter: blur(6px); |
| | | } |
| | | |
| | | @media (max-width: 960px) { |
| | | .locmap-title-card { |
| | | right: 18px; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .locmap-detail-panel { |
| | | top: auto; |
| | | height: min(54vh, 460px); |
| | | width: calc(100vw - 36px); |
| | | } |
| | | |
| | | .locmap-empty { |
| | | width: calc(100vw - 36px); |
| | | } |
| | | |
| | | .locmap-kv-grid { |
| | | grid-template-columns: 1fr; |
| | | } |
| | | } |
| | | </style> |
| | | </head> |
| | | <body> |
| | | <div id="app"> |
| | | <div id="pixiView"> |
| | | |
| | | </div> |
| | | |
| | | <!--输出操作和FPS--> |
| | | <div style="position: absolute;top: 20px;right: 50px;"> |
| | | <div>FPS:{{mapFps}}</div> |
| | | <el-button @click="drawer = true">操作</el-button> |
| | | </div> |
| | | |
| | | <el-drawer |
| | | title="操作区域" |
| | | :visible.sync="drawer" |
| | | :with-header="true" |
| | | :modal="false" |
| | | > |
| | | <div class="floorBtnBox" v-for="(lev,idx) in floorList"> |
| | | <el-button :style="{background:currentLev === lev ? '#7DCDFF':''}" @click="changeFloor(lev)">{{lev}}F</el-button> |
| | | </div> |
| | | <!-- <div>--> |
| | | <!-- <el-button @click="testMove()">测试移动车</el-button>--> |
| | | <!-- </div>--> |
| | | <!-- <div style="margin-top: 10px;">--> |
| | | <!-- <el-button @click="resetMap()">重置地图</el-button>--> |
| | | <!-- </div>--> |
| | | </el-drawer> |
| | | |
| | | <el-drawer |
| | | title="库位详情" |
| | | :visible.sync="drawerLocNo" |
| | | :with-header="true" |
| | | :modal="false" |
| | | > |
| | | <div v-if="drawerLocNoData!=null"> |
| | | <div style="margin: 10px;"> |
| | | <!-- <div style="margin-top: 5px;">排:{{drawerLocNoData.row}}</div>--> |
| | | <!-- <div style="margin-top: 5px;">列:{{drawerLocNoData.bay}}</div>--> |
| | | <!-- <div style="margin-top: 5px;">层:{{drawerLocNoData.lev}}</div>--> |
| | | <div style="margin-top: 5px;">库位号:{{drawerLocNoData.locNo}}</div> |
| | | <div style="margin-top: 5px;">库位状态:{{drawerLocNoData.locSts}}</div> |
| | | </div> |
| | | </div> |
| | | </el-drawer> |
| | | |
| | | <el-drawer |
| | | title="站点信息" |
| | | :visible.sync="drawerSta" |
| | | :with-header="true" |
| | | :modal="false" |
| | | > |
| | | <div v-if="drawerStaData!=null"> |
| | | <div style="margin: 10px;"> |
| | | <div style="margin-top: 5px;">站点:{{drawerStaData.siteId}}</div> |
| | | <div style="margin-top: 5px;">工作号:{{drawerStaData.workNo}}</div> |
| | | <div style="margin-top: 5px;">工作状态:{{drawerStaData.wrkSts}}</div> |
| | | <div style="margin-top: 5px;">工作类型:{{drawerStaData.ioType}}</div> |
| | | <div style="margin-top: 5px;">源站:{{drawerStaData.sourceStaNo}}</div> |
| | | <div style="margin-top: 5px;">目标站:{{drawerStaData.staNo}}</div> |
| | | <div style="margin-top: 5px;">源库位:{{drawerStaData.sourceLocNo}}</div> |
| | | <div style="margin-top: 5px;">目标库位:{{drawerStaData.locNo}}</div> |
| | | <div style="margin-top: 5px;">自动:{{drawerStaData.autoing}}</div> |
| | | <div style="margin-top: 5px;">有物:{{drawerStaData.loading}}</div> |
| | | <div style="margin-top: 5px;">能入:{{drawerStaData.canining}}</div> |
| | | <div style="margin-top: 5px;">能出:{{drawerStaData.canouting}}</div> |
| | | <div style="margin-top: 5px;">能出:{{drawerStaData.canouting}}</div> |
| | | </div> |
| | | </div> |
| | | </el-drawer> |
| | | |
| | | <loc-map-canvas></loc-map-canvas> |
| | | </div> |
| | | <script> |
| | | let width = 25; |
| | | let height = 25; |
| | | let pixiApp; |
| | | let pixiStageList = []; |
| | | let pixiStaMap = new Map(); |
| | | let objectsContainer; |
| | | let objectsContainer3; |
| | | let tracksGraphics; |
| | | let mapRoot; |
| | | let mapContentSize = { width: 0, height: 0 }; |
| | | let graphics0; |
| | | let graphicsF; |
| | | let graphics3; |
| | | let graphics4; |
| | | let graphics5; |
| | | let graphics9; |
| | | let graphics67; |
| | | let graphicsLock; |
| | | let ws; |
| | | |
| | | var app = new Vue({ |
| | | el: '#app', |
| | | data: { |
| | | map: [], |
| | | currentLev: 1, |
| | | floorList: [], //当前项目楼层 |
| | | drawer: false, |
| | | drawerLocNo: false, |
| | | drawerLocNoData: null, |
| | | drawerLocDetls: [], |
| | | reloadMap: true, |
| | | mapFps: 0, |
| | | currentLevStaList: [],//当前楼层站点list |
| | | drawerSta: false, |
| | | drawerStaData: null, |
| | | mapRotation: 0, |
| | | mapMirrorX: false, |
| | | mapConfigCodes: { |
| | | rotate: 'map_canvas_rotation', |
| | | mirror: 'map_canvas_mirror_x' |
| | | <script> |
| | | Vue.component('loc-map-canvas', { |
| | | template: ` |
| | | <div class="locmap-shell" ref="shell"> |
| | | <div ref="pixiView" class="locmap-canvas"></div> |
| | | |
| | | <div class="locmap-title-card"> |
| | | <div class="locmap-title">库位地图</div> |
| | | <div class="locmap-title-desc">点击库位后在右侧查看详情。</div> |
| | | </div> |
| | | |
| | | <div class="locmap-tools"> |
| | | <div class="locmap-fps">FPS {{ mapFps }}</div> |
| | | <button type="button" |
| | | class="locmap-tool-toggle" |
| | | :class="{ 'is-active': showMapToolPanel }" |
| | | @click="toggleMapToolPanel">{{ showMapToolPanel ? '收起操作' : '地图操作' }}</button> |
| | | <div v-show="showMapToolPanel" class="locmap-tool-panel"> |
| | | <div class="locmap-tool-row"> |
| | | <button type="button" class="locmap-tool-btn" @click="fitStageToContent">重置视图</button> |
| | | <button type="button" class="locmap-tool-btn" @click="rotateMap">旋转</button> |
| | | <button type="button" |
| | | class="locmap-tool-btn" |
| | | :class="{ 'is-active': mapMirrorX }" |
| | | @click="toggleMirror">{{ mapMirrorX ? '取消镜像' : '镜像' }}</button> |
| | | </div> |
| | | <div v-if="floorList && floorList.length > 0" class="locmap-tool-section"> |
| | | <div class="locmap-tool-label">楼层</div> |
| | | <div class="locmap-tool-row"> |
| | | <button v-for="lev in floorList" |
| | | :key="'loc-floor-' + lev" |
| | | type="button" |
| | | class="locmap-floor-btn" |
| | | :class="{ 'is-active': currentLev === lev }" |
| | | @click="changeFloor(lev)">{{ lev }}F</button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-if="detailPanelOpen" class="locmap-detail-panel" ref="detailPanel"> |
| | | <div class="locmap-detail-header"> |
| | | <div class="locmap-detail-title-wrap"> |
| | | <div class="locmap-detail-type">{{ detailTypeLabel }}</div> |
| | | <div class="locmap-detail-title">{{ detailTitle }}</div> |
| | | <div class="locmap-detail-subtitle">{{ detailSubtitle }}</div> |
| | | </div> |
| | | <button type="button" class="locmap-detail-close" @click="closeDetailPanel">×</button> |
| | | </div> |
| | | <div class="locmap-detail-body"> |
| | | <div class="locmap-kv-grid"> |
| | | <div v-for="item in detailFields" :key="item.label" class="locmap-kv-card"> |
| | | <div class="locmap-kv-label">{{ item.label }}</div> |
| | | <div class="locmap-kv-value">{{ formatDetailValue(item.value) }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-else class="locmap-empty"> |
| | | 点击库位可查看库位状态,点击站点可查看站点作业详情。地图操作在右上角工具面板中。 |
| | | </div> |
| | | </div> |
| | | `, |
| | | data() { |
| | | return { |
| | | cellWidth: 25, |
| | | cellHeight: 25, |
| | | pixiApp: null, |
| | | pixiStageList: [], |
| | | pixiStaMap: new Map(), |
| | | pixiTrackMap: new Map(), |
| | | pixiLabelList: [], |
| | | mapRoot: null, |
| | | shelvesContainer: null, |
| | | objectsContainer: null, |
| | | objectsOverlayContainer: null, |
| | | tracksGraphics: null, |
| | | mapContentSize: { width: 0, height: 0 }, |
| | | textureMap: {}, |
| | | locChunkList: [], |
| | | locChunkSize: 1024, |
| | | locCullPadding: 160, |
| | | locCullRaf: null, |
| | | ws: null, |
| | | wsReconnectTimer: null, |
| | | wsReconnectAttempts: 0, |
| | | wsReconnectBaseDelay: 1000, |
| | | wsReconnectMaxDelay: 15000, |
| | | containerResizeObserver: null, |
| | | adjustLabelTimer: null, |
| | | map: [], |
| | | floorList: [], |
| | | currentLev: 1, |
| | | reloadMap: true, |
| | | mapFps: 0, |
| | | showMapToolPanel: false, |
| | | detailPanelOpen: false, |
| | | detailType: '', |
| | | detailPayload: null, |
| | | highlightedSprite: null, |
| | | highlightedLocCell: null, |
| | | highlightedLocGraphic: null, |
| | | mapRotation: 0, |
| | | mapMirrorX: false, |
| | | mapConfigCodes: { |
| | | rotate: 'map_canvas_rotation', |
| | | mirror: 'map_canvas_mirror_x' |
| | | } |
| | | }; |
| | | }, |
| | | computed: { |
| | | detailTypeLabel() { |
| | | return this.detailType === 'site' ? '站点信息' : '库位详情'; |
| | | }, |
| | | detailTitle() { |
| | | if (this.detailType === 'site' && this.detailPayload) { |
| | | return '站点 ' + this.formatDetailValue(this.detailPayload.siteId); |
| | | } |
| | | if (this.detailType === 'loc' && this.detailPayload) { |
| | | return this.formatDetailValue(this.detailPayload.locNo); |
| | | } |
| | | return '详情'; |
| | | }, |
| | | detailSubtitle() { |
| | | if (this.detailType === 'site') { |
| | | return '站点作业状态、来源目标和作业参数'; |
| | | } |
| | | return '库位当前状态和所在排列层信息'; |
| | | }, |
| | | detailFields() { |
| | | if (!this.detailPayload) { return []; } |
| | | if (this.detailType === 'site') { |
| | | return [ |
| | | { label: '站点', value: this.detailPayload.siteId }, |
| | | { label: '工作号', value: this.detailPayload.workNo }, |
| | | { label: '工作状态', value: this.detailPayload.wrkSts }, |
| | | { label: '工作类型', value: this.detailPayload.ioType }, |
| | | { label: '源站', value: this.detailPayload.sourceStaNo }, |
| | | { label: '目标站', value: this.detailPayload.staNo }, |
| | | { label: '源库位', value: this.detailPayload.sourceLocNo }, |
| | | { label: '目标库位', value: this.detailPayload.locNo }, |
| | | { label: '自动', value: this.detailPayload.autoing }, |
| | | { label: '有物', value: this.detailPayload.loading }, |
| | | { label: '能入', value: this.detailPayload.canining }, |
| | | { label: '能出', value: this.detailPayload.canouting } |
| | | ]; |
| | | } |
| | | return [ |
| | | { label: '库位号', value: this.detailPayload.locNo }, |
| | | { label: '库位状态', value: this.detailPayload.locSts }, |
| | | { label: '排', value: this.detailPayload.row }, |
| | | { label: '列', value: this.detailPayload.bay }, |
| | | { label: '层', value: this.detailPayload.lev } |
| | | ]; |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.init() |
| | | this.createMap() |
| | | this.createMap(); |
| | | this.startContainerResizeObserve(); |
| | | this.loadMapTransformConfig(); |
| | | this.initLev(); |
| | | this.connectWs(); |
| | | setTimeout(() => { |
| | | this.getMap(this.currentLev); |
| | | }, 300); |
| | | }, |
| | | watch: { |
| | | map: { |
| | | deep: true, |
| | | handler(val) { |
| | | |
| | | } |
| | | }, |
| | | drawerLocNo: { |
| | | deep: true, |
| | | handler(val) { |
| | | if (!val) { |
| | | var sprite = pixiStageList[this.drawerLocNoData.x][this.drawerLocNoData.y]; |
| | | updateColor(sprite, 0xFFFFFF);//恢复颜色 |
| | | } |
| | | } |
| | | beforeDestroy() { |
| | | if (this.adjustLabelTimer) { |
| | | clearTimeout(this.adjustLabelTimer); |
| | | this.adjustLabelTimer = null; |
| | | } |
| | | if (this.locCullRaf) { |
| | | cancelAnimationFrame(this.locCullRaf); |
| | | this.locCullRaf = null; |
| | | } |
| | | if (this.containerResizeObserver) { |
| | | this.containerResizeObserver.disconnect(); |
| | | this.containerResizeObserver = null; |
| | | } |
| | | window.removeEventListener('resize', this.resizeToContainer); |
| | | if (this.wsReconnectTimer) { |
| | | clearTimeout(this.wsReconnectTimer); |
| | | this.wsReconnectTimer = null; |
| | | } |
| | | if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) { |
| | | try { this.ws.close(); } catch (e) {} |
| | | } |
| | | if (window.gsap && this.pixiApp && this.pixiApp.stage) { |
| | | window.gsap.killTweensOf(this.pixiApp.stage.position); |
| | | } |
| | | this.clearLocChunks(); |
| | | if (this.pixiApp) { |
| | | this.pixiApp.destroy(true, { children: true }); |
| | | this.pixiApp = null; |
| | | } |
| | | }, |
| | | methods: { |
| | | init(){ |
| | | let that = this |
| | | ws = new WebSocket("ws://" + window.location.host + baseUrl + "/console/websocket"); |
| | | ws.onopen = this.webSocketOnOpen |
| | | ws.onerror = this.webSocketOnError |
| | | ws.onmessage = this.webSocketOnMessage |
| | | ws.onclose = this.webSocketClose |
| | | |
| | | |
| | | this.loadMapTransformConfig() |
| | | this.initLev()//初始化楼层信息 |
| | | setTimeout(() => { |
| | | that.getMap(this.currentLev) |
| | | }, 1000); |
| | | |
| | | toggleMapToolPanel() { |
| | | this.showMapToolPanel = !this.showMapToolPanel; |
| | | }, |
| | | initLev(){ |
| | | let that = this |
| | | formatDetailValue(value) { |
| | | return value == null || value === '' ? '-' : value; |
| | | }, |
| | | initLev() { |
| | | $.ajax({ |
| | | url: baseUrl + "/console/map/lev/list", |
| | | headers: { |
| | | 'token': localStorage.getItem('token') |
| | | }, |
| | | data: {}, |
| | | headers: { token: localStorage.getItem('token') }, |
| | | method: 'get', |
| | | success: function(res) { |
| | | success: (res) => { |
| | | if (res.code === 200) { |
| | | that.floorList = res.data; |
| | | this.floorList = Array.isArray(res.data) ? res.data : []; |
| | | if (this.floorList.length > 0 && this.floorList.indexOf(this.currentLev) === -1) { |
| | | this.currentLev = this.floorList[0]; |
| | | } |
| | | } else if (res.code === 403) { |
| | | parent.location.href = baseUrl + "/login"; |
| | | } else { |
| | | that.$message({ |
| | | message: res.msg, |
| | | type: 'error' |
| | | }); |
| | | this.showMessage('error', res.msg || '楼层信息加载失败'); |
| | | } |
| | | } |
| | | }); |
| | | }, |
| | | //获取地图数据 |
| | | getMap(lev) { |
| | | let that = this; |
| | | $.ajax({ |
| | | url: baseUrl + "/console/map/" + lev + "/auth", |
| | | headers: { |
| | | 'token': localStorage.getItem('token') |
| | | }, |
| | | data: {}, |
| | | method: 'get', |
| | | success: function(res) { |
| | | //获取地图数据 |
| | | let data = res.data |
| | | that.createMapData(data) |
| | | } |
| | | }) |
| | | $.ajax({ |
| | | url: baseUrl + "/console/map/" + lev + "/auth", |
| | | headers: { token: localStorage.getItem('token') }, |
| | | method: 'get', |
| | | success: (res) => { |
| | | if (res.code === 200) { |
| | | this.reloadMap = true; |
| | | this.createMapData(res.data || []); |
| | | } else if (res.code === 403) { |
| | | parent.location.href = baseUrl + "/login"; |
| | | } else { |
| | | this.showMessage('error', res.msg || '地图加载失败'); |
| | | } |
| | | } |
| | | }); |
| | | }, |
| | | changeFloor(lev) { |
| | | this.currentLev = lev |
| | | this.loadMapTransformConfig() |
| | | this.reloadMap = true |
| | | this.getMap(lev) |
| | | if (this.currentLev === lev) { return; } |
| | | this.currentLev = lev; |
| | | this.reloadMap = true; |
| | | this.closeDetailPanel(); |
| | | this.getMap(lev); |
| | | }, |
| | | createMap(){ |
| | | //Create a Pixi Application |
| | | pixiApp = new PIXI.Application({ |
| | | // width: 1500, |
| | | // height: 800, |
| | | backgroundColor: 0xF5F7F9FF, |
| | | resizeTo: window |
| | | connectWs() { |
| | | if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) { |
| | | return; |
| | | } |
| | | this.ws = new WebSocket("ws://" + window.location.host + baseUrl + "/console/websocket"); |
| | | this.ws.onopen = this.webSocketOnOpen; |
| | | this.ws.onerror = this.webSocketOnError; |
| | | this.ws.onmessage = this.webSocketOnMessage; |
| | | this.ws.onclose = this.webSocketClose; |
| | | }, |
| | | scheduleReconnectWs() { |
| | | if (this.wsReconnectTimer) { return; } |
| | | const delay = Math.min(this.wsReconnectMaxDelay, this.wsReconnectBaseDelay * Math.pow(2, this.wsReconnectAttempts)); |
| | | this.wsReconnectAttempts += 1; |
| | | this.wsReconnectTimer = setTimeout(() => { |
| | | this.wsReconnectTimer = null; |
| | | this.connectWs(); |
| | | }, delay); |
| | | }, |
| | | webSocketOnOpen() { |
| | | this.wsReconnectAttempts = 0; |
| | | }, |
| | | webSocketOnError() {}, |
| | | webSocketOnMessage(event) { |
| | | let result = null; |
| | | try { |
| | | result = JSON.parse(event.data); |
| | | } catch (e) { |
| | | return; |
| | | } |
| | | if (!result || !result.url) { return; } |
| | | if (result.url === "/console/map/auth" || result.url === "/console/locMap/auth") { |
| | | let data = []; |
| | | try { |
| | | data = JSON.parse(result.data || '[]'); |
| | | } catch (e) { |
| | | data = []; |
| | | } |
| | | this.setMap(data); |
| | | } |
| | | }, |
| | | webSocketClose() { |
| | | this.scheduleReconnectWs(); |
| | | }, |
| | | setMap(data) { |
| | | if (!Array.isArray(data) || this.currentLev == null) { return; } |
| | | this.reloadMap = true; |
| | | this.createMapData(data); |
| | | }, |
| | | showMessage(type, message) { |
| | | if (this.$message) { |
| | | this.$message({ type: type, message: message }); |
| | | } |
| | | }, |
| | | createMap() { |
| | | this.pixiApp = new PIXI.Application({ |
| | | backgroundColor: 0xEEF4F8, |
| | | resizeTo: this.$refs.shell, |
| | | antialias: true, |
| | | autoDensity: true |
| | | }); |
| | | //Add the canvas that Pixi automatically created for you to the HTML document |
| | | $("#pixiView").append(pixiApp.view) |
| | | this.$refs.pixiView.appendChild(this.pixiApp.view); |
| | | this.createBaseTextures(); |
| | | |
| | | // 从Graphics对象创建一个纹理 |
| | | graphicsF = pixiApp.renderer.generateTexture(getContainer(1000)); |
| | | graphics0 = pixiApp.renderer.generateTexture(getContainer(0)); |
| | | graphics3 = pixiApp.renderer.generateTexture(getContainer(3)); |
| | | graphics4 = pixiApp.renderer.generateTexture(getContainer(4)); |
| | | graphics5 = pixiApp.renderer.generateTexture(getContainer(5)); |
| | | graphics9 = pixiApp.renderer.generateTexture(getContainer(9)); |
| | | graphics67 = pixiApp.renderer.generateTexture(getContainer(67)); |
| | | graphicsLock = pixiApp.renderer.generateTexture(getContainer(-999)); |
| | | this.mapRoot = new PIXI.Container(); |
| | | this.pixiApp.stage.addChild(this.mapRoot); |
| | | |
| | | mapRoot = new PIXI.Container(); |
| | | pixiApp.stage.addChild(mapRoot); |
| | | // 创建一个容器来管理大批量的显示对象 |
| | | objectsContainer = new PIXI.Container(); |
| | | mapRoot.addChild(objectsContainer); |
| | | this.shelvesContainer = new PIXI.Container(); |
| | | this.mapRoot.addChild(this.shelvesContainer); |
| | | |
| | | tracksGraphics = new PIXI.Graphics(); |
| | | mapRoot.addChild(tracksGraphics); |
| | | this.objectsContainer = new PIXI.Container(); |
| | | this.mapRoot.addChild(this.objectsContainer); |
| | | |
| | | // 创建一个容器来管理大批量的显示对象 |
| | | objectsContainer3 = new PIXI.Container(); |
| | | mapRoot.addChild(objectsContainer3); |
| | | this.tracksGraphics = new PIXI.Graphics(); |
| | | this.mapRoot.addChild(this.tracksGraphics); |
| | | |
| | | //*******************拖动画布******************* |
| | | let stageOriginalPos; |
| | | let mouseDownPoint; |
| | | this.objectsOverlayContainer = new PIXI.Container(); |
| | | this.mapRoot.addChild(this.objectsOverlayContainer); |
| | | |
| | | this.initStageInteractions(); |
| | | this.initFpsTicker(); |
| | | }, |
| | | createBaseTextures() { |
| | | this.textureMap = { |
| | | locEmpty: this.pixiApp.renderer.generateTexture(this.buildCellGraphic(0x5faeff)), |
| | | locFull: this.pixiApp.renderer.generateTexture(this.buildCellGraphic(0xf05d5d)), |
| | | site: this.pixiApp.renderer.generateTexture(this.buildCellGraphic(0xf6ca4b)), |
| | | charge: this.pixiApp.renderer.generateTexture(this.buildCellGraphic(0xffa66b)), |
| | | elevator: this.pixiApp.renderer.generateTexture(this.buildCellGraphic(0x7dd9ff)), |
| | | lock: this.pixiApp.renderer.generateTexture(this.buildCellGraphic(0xf83333)) |
| | | }; |
| | | }, |
| | | buildCellGraphic(fillColor) { |
| | | const graphics = new PIXI.Graphics(); |
| | | graphics.beginFill(fillColor); |
| | | graphics.lineStyle(1, 0xffffff, 1); |
| | | graphics.drawRect(0, 0, this.cellWidth, this.cellHeight); |
| | | graphics.endFill(); |
| | | return graphics; |
| | | }, |
| | | initStageInteractions() { |
| | | let stageOriginalPos = null; |
| | | let mouseDownPoint = null; |
| | | let touchBlank = false; |
| | | pixiApp.renderer.plugins.interaction.on( |
| | | 'pointerdown', |
| | | (event) => { |
| | | const globalPos = event.data.global; |
| | | // 记录下stage原来的位置 |
| | | stageOriginalPos = [pixiApp.stage.position._x, pixiApp.stage.position._y]; |
| | | // 记录下mouse down的位置 |
| | | mouseDownPoint = [globalPos.x, globalPos.y]; |
| | | if (!event.target) { |
| | | // 点到了画布的空白位置 |
| | | touchBlank = true; |
| | | } |
| | | } |
| | | ); |
| | | let pointerDownMoved = false; |
| | | const interaction = this.pixiApp.renderer.plugins.interaction; |
| | | |
| | | pixiApp.renderer.plugins.interaction.on( |
| | | 'pointermove', |
| | | (event) => { |
| | | const globalPos = event.data.global; |
| | | interaction.on('pointerdown', (event) => { |
| | | const globalPos = event.data.global; |
| | | stageOriginalPos = [this.pixiApp.stage.position.x, this.pixiApp.stage.position.y]; |
| | | mouseDownPoint = [globalPos.x, globalPos.y]; |
| | | pointerDownMoved = false; |
| | | touchBlank = !event.target; |
| | | }); |
| | | |
| | | if (touchBlank) { |
| | | // 拖拽画布 |
| | | const dx = globalPos.x - mouseDownPoint[0]; |
| | | const dy = globalPos.y - mouseDownPoint[1]; |
| | | pixiApp.stage.position.set( |
| | | stageOriginalPos[0] + dx, |
| | | stageOriginalPos[1] + dy |
| | | ); |
| | | } |
| | | } |
| | | ); |
| | | interaction.on('pointermove', (event) => { |
| | | const globalPos = event.data.global; |
| | | if (mouseDownPoint) { |
| | | const dragDx = globalPos.x - mouseDownPoint[0]; |
| | | const dragDy = globalPos.y - mouseDownPoint[1]; |
| | | if (Math.abs(dragDx) > 4 || Math.abs(dragDy) > 4) { |
| | | pointerDownMoved = true; |
| | | } |
| | | } |
| | | if (!touchBlank || !stageOriginalPos || !mouseDownPoint) { return; } |
| | | const dx = globalPos.x - mouseDownPoint[0]; |
| | | const dy = globalPos.y - mouseDownPoint[1]; |
| | | this.pixiApp.stage.position.set(stageOriginalPos[0] + dx, stageOriginalPos[1] + dy); |
| | | this.scheduleAdjustLabels(); |
| | | this.scheduleLocChunkCulling(); |
| | | }); |
| | | |
| | | pixiApp.renderer.plugins.interaction.on( |
| | | 'pointerup', |
| | | (event) => { |
| | | touchBlank = false; |
| | | } |
| | | ); |
| | | //*******************拖动画布******************* |
| | | interaction.on('pointerup', (event) => { |
| | | if (touchBlank && !pointerDownMoved && event && event.data && event.data.global) { |
| | | this.handleBlankPointerTap(event.data.global); |
| | | } |
| | | touchBlank = false; |
| | | mouseDownPoint = null; |
| | | stageOriginalPos = null; |
| | | }); |
| | | |
| | | //*******************缩放画布******************* |
| | | pixiApp.view.addEventListener('wheel', (event) => { |
| | | interaction.on('pointerupoutside', () => { |
| | | touchBlank = false; |
| | | mouseDownPoint = null; |
| | | stageOriginalPos = null; |
| | | }); |
| | | |
| | | this.pixiApp.view.addEventListener('wheel', (event) => { |
| | | event.stopPropagation(); |
| | | event.preventDefault(); |
| | | const rect = pixiApp.view.getBoundingClientRect(); |
| | | const rect = this.pixiApp.view.getBoundingClientRect(); |
| | | const sx = event.clientX - rect.left; |
| | | const sy = event.clientY - rect.top; |
| | | const oldZoomX = pixiApp.stage.scale.x || 1; |
| | | const oldZoomY = pixiApp.stage.scale.y || 1; |
| | | const oldZoomX = this.pixiApp.stage.scale.x || 1; |
| | | const oldZoomY = this.pixiApp.stage.scale.y || 1; |
| | | const oldZoomAbs = Math.abs(oldZoomX) || 1; |
| | | const delta = event.deltaY; |
| | | let newZoomAbs = oldZoomAbs * 0.999 ** delta; |
| | | let newZoomAbs = oldZoomAbs * Math.pow(0.999, event.deltaY); |
| | | newZoomAbs = Math.max(0.08, Math.min(newZoomAbs, 8)); |
| | | const mirrorX = this.mapMirrorX ? -1 : 1; |
| | | const newZoomX = mirrorX * newZoomAbs; |
| | | const newZoomY = newZoomAbs; |
| | | const worldX = (sx - pixiApp.stage.position.x) / oldZoomX; |
| | | const worldY = (sy - pixiApp.stage.position.y) / oldZoomY; |
| | | const worldX = (sx - this.pixiApp.stage.position.x) / oldZoomX; |
| | | const worldY = (sy - this.pixiApp.stage.position.y) / oldZoomY; |
| | | const newPosX = sx - worldX * newZoomX; |
| | | const newPosY = sy - worldY * newZoomY; |
| | | pixiApp.stage.setTransform(newPosX, newPosY, newZoomX, newZoomY, 0, 0, 0, 0, 0); |
| | | |
| | | }); |
| | | //*******************缩放画布******************* |
| | | |
| | | //*******************FPS******************* |
| | | let g_Time = 0; |
| | | let fpsLastUpdateTs = 0; |
| | | let fpsDeltaSumMs = 0; |
| | | let fpsFrameCount = 0; |
| | | const fpsUpdateInterval = 200; |
| | | pixiApp.ticker.add((delta) => { |
| | | const timeNow = (new Date()).getTime(); |
| | | const timeDiff = timeNow - g_Time; |
| | | g_Time = timeNow; |
| | | fpsDeltaSumMs += timeDiff; |
| | | fpsFrameCount += 1; |
| | | if (timeNow - fpsLastUpdateTs >= fpsUpdateInterval) { |
| | | const avgFps = fpsDeltaSumMs > 0 ? (fpsFrameCount * 1000 / fpsDeltaSumMs) : 0; |
| | | this.mapFps = Math.round(avgFps); |
| | | fpsDeltaSumMs = 0; |
| | | fpsFrameCount = 0; |
| | | fpsLastUpdateTs = timeNow; |
| | | this.pixiApp.stage.setTransform(newPosX, newPosY, newZoomX, newZoomY, 0, 0, 0, 0, 0); |
| | | this.scheduleAdjustLabels(); |
| | | this.scheduleLocChunkCulling(); |
| | | }, { passive: false }); |
| | | }, |
| | | initFpsTicker() { |
| | | let lastTs = 0; |
| | | let deltaSum = 0; |
| | | let frameCount = 0; |
| | | const updateInterval = 200; |
| | | this.pixiApp.ticker.add(() => { |
| | | const now = Date.now(); |
| | | if (!lastTs) { |
| | | lastTs = now; |
| | | return; |
| | | } |
| | | deltaSum += now - lastTs; |
| | | frameCount += 1; |
| | | lastTs = now; |
| | | if (deltaSum >= updateInterval) { |
| | | this.mapFps = deltaSum > 0 ? Math.round(frameCount * 1000 / deltaSum) : 0; |
| | | deltaSum = 0; |
| | | frameCount = 0; |
| | | } |
| | | }); |
| | | //*******************FPS******************* |
| | | |
| | | }, |
| | | createMapData(map) { |
| | | if (this.reloadMap) { |
| | | this.reloadMap = false |
| | | pixiStageList = [map.length]//初始化列表 |
| | | pixiStaMap = new Map();//重置 |
| | | objectsContainer.removeChildren() |
| | | if (tracksGraphics) { tracksGraphics.clear(); } |
| | | map.forEach((item,index) => { |
| | | pixiStageList[index] = [item.length] |
| | | for (let idx = 0; idx < item.length; idx++) { |
| | | let val = item[idx] |
| | | if (val.value < 0 && (val.value != -999)) { |
| | | continue; |
| | | } |
| | | if (!this.reloadMap) { |
| | | this.map = map; |
| | | return; |
| | | } |
| | | this.reloadMap = false; |
| | | this.map = map; |
| | | this.pixiStageList = []; |
| | | this.pixiStaMap = new Map(); |
| | | this.pixiLabelList = []; |
| | | this.restoreHighlightedSprite(); |
| | | this.clearLocHighlight(); |
| | | this.clearLocChunks(); |
| | | this.objectsContainer.removeChildren(); |
| | | this.objectsOverlayContainer.removeChildren(); |
| | | if (this.tracksGraphics) { this.tracksGraphics.clear(); } |
| | | |
| | | let sprite = getSprite(val.value, idx * width, index * height, val, (e) => { |
| | | if (val.value == 4) { |
| | | //站点 |
| | | this.openDrawerSta(val) |
| | | }else { |
| | | //库位 |
| | | this.rightEvent(index, idx, e); |
| | | updateColor(sprite, 0x9900ff); |
| | | } |
| | | }); |
| | | let rows = Array.isArray(map) ? map.length : 0; |
| | | let maxCols = 0; |
| | | map.forEach((row, rowIndex) => { |
| | | if (!Array.isArray(row)) { return; } |
| | | this.pixiStageList[rowIndex] = []; |
| | | maxCols = Math.max(maxCols, row.length); |
| | | row.forEach((cell, colIndex) => { |
| | | if (cell.value < 0 && cell.value !== -999) { return; } |
| | | if (parseInt(cell.value, 10) === 0) { |
| | | this.pixiStageList[rowIndex][colIndex] = null; |
| | | return; |
| | | } |
| | | if (this.isTrackCell(cell)) { |
| | | cell.trackMask = this.resolveTrackMask(map, rowIndex, colIndex); |
| | | } |
| | | const sprite = this.createSprite(cell, rowIndex, colIndex); |
| | | if (cell.value === -999) { |
| | | this.objectsOverlayContainer.addChild(sprite); |
| | | } else { |
| | | this.objectsContainer.addChild(sprite); |
| | | } |
| | | this.pixiStageList[rowIndex][colIndex] = sprite; |
| | | }); |
| | | }); |
| | | |
| | | if (val.value == 4) { |
| | | // 创建文本对象 |
| | | const style = new PIXI.TextStyle({ |
| | | fontFamily: 'Arial', |
| | | fontSize: 10, |
| | | fill: '#000000', |
| | | }); |
| | | const text = new PIXI.Text(val.data, style); |
| | | text.anchor.set(0.5); // 设置文本锚点为中心点 |
| | | text.position.set(sprite.width / 2, sprite.height / 2); // 将文本位置设置为Graphics对象的中心点 |
| | | // 将文本对象添加到Graphics对象中 |
| | | sprite.addChild(text); |
| | | sprite.textObj = text; |
| | | pixiStaMap.set(parseInt(val.data), sprite);//站点数据添加到map中 |
| | | }else if (val.value == 67) { |
| | | // 创建提升机文本对象 |
| | | const style = new PIXI.TextStyle({ |
| | | fontFamily: 'Arial', |
| | | fontSize: 10, |
| | | fill: '#000000', |
| | | }); |
| | | const text = new PIXI.Text(val.data, style); |
| | | text.anchor.set(0.5); // 设置文本锚点为中心点 |
| | | text.position.set(sprite.width / 2, sprite.height / 2); // 将文本位置设置为Graphics对象的中心点 |
| | | // 将文本对象添加到Graphics对象中 |
| | | sprite.addChild(text); |
| | | sprite.textObj = text; |
| | | pixiStaMap.set(parseInt(val.data), sprite);//站点数据添加到map中 |
| | | } |
| | | this.mapContentSize = { |
| | | width: maxCols * this.cellWidth, |
| | | height: rows * this.cellHeight |
| | | }; |
| | | this.buildLocChunks(map, this.mapContentSize.width, this.mapContentSize.height); |
| | | this.drawTracks(map); |
| | | this.applyMapTransform(true); |
| | | }, |
| | | createSprite(cell, rowIndex, colIndex) { |
| | | const sprite = this.isTrackCell(cell) |
| | | ? this.createTrackSprite(this.cellWidth, this.cellHeight, cell.trackMask) |
| | | : new PIXI.Sprite(this.getTextureForCell(cell)); |
| | | sprite.position.set(colIndex * this.cellWidth, rowIndex * this.cellHeight); |
| | | sprite.baseTint = 0xFFFFFF; |
| | | sprite.cellData = cell; |
| | | |
| | | if (val.value == -999) { |
| | | pixiShuttleLockPathMap.set(val.locNo, sprite); |
| | | objectsContainer3.addChild(sprite); |
| | | }else { |
| | | objectsContainer.addChild(sprite); |
| | | } |
| | | pixiStageList[index][idx] = sprite |
| | | if (cell.value === 4 || cell.value === 67) { |
| | | this.attachCellLabel(sprite, cell.data); |
| | | if (cell.value === 4 && cell.data != null) { |
| | | this.pixiStaMap.set(parseInt(cell.data, 10), sprite); |
| | | } |
| | | } |
| | | |
| | | if (!this.isTrackCell(cell)) { |
| | | sprite.interactive = true; |
| | | sprite.buttonMode = true; |
| | | sprite.on('pointerdown', () => { |
| | | if (cell.value === 4) { |
| | | this.openSiteDetail(cell, sprite); |
| | | } else { |
| | | this.openLocDetail(rowIndex, colIndex, sprite); |
| | | } |
| | | }); |
| | | |
| | | const b1 = objectsContainer.getLocalBounds(); |
| | | const minX = b1.x; |
| | | const minY = b1.y; |
| | | const maxX = b1.x + b1.width; |
| | | const maxY = b1.y + b1.height; |
| | | const contentW = Math.max(0, maxX - minX); |
| | | const contentH = Math.max(0, maxY - minY); |
| | | mapContentSize = { width: contentW, height: contentH }; |
| | | this.drawTracks(map); |
| | | this.applyMapTransform(true); |
| | | } |
| | | this.map = map; |
| | | return sprite; |
| | | }, |
| | | createTrackSprite(width, height, mask) { |
| | | const trackMask = mask != null ? mask : 10; |
| | | const key = width + '-' + height + '-' + trackMask; |
| | | let texture = this.pixiTrackMap.get(key); |
| | | if (!texture) { |
| | | texture = this.createTrackTexture(width, height, trackMask); |
| | | this.pixiTrackMap.set(key, texture); |
| | | } |
| | | return new PIXI.Sprite(texture); |
| | | }, |
| | | createTrackTexture(width, height, mask) { |
| | | const TRACK_N = 1; |
| | | const TRACK_E = 2; |
| | | const TRACK_S = 4; |
| | | const TRACK_W = 8; |
| | | const trackMask = mask != null ? mask : (TRACK_E | TRACK_W); |
| | | const g = new PIXI.Graphics(); |
| | | const size = Math.max(1, Math.min(width, height)); |
| | | const rail = Math.max(2, Math.round(size * 0.12)); |
| | | const gap = Math.max(4, Math.round(size * 0.38)); |
| | | const midX = Math.round(width / 2); |
| | | const midY = Math.round(height / 2); |
| | | |
| | | const hasN = (trackMask & TRACK_N) !== 0; |
| | | const hasE = (trackMask & TRACK_E) !== 0; |
| | | const hasS = (trackMask & TRACK_S) !== 0; |
| | | const hasW = (trackMask & TRACK_W) !== 0; |
| | | |
| | | const hasH = hasW || hasE; |
| | | const hasV = hasN || hasS; |
| | | const isCorner = hasH && hasV && !(hasW && hasE) && !(hasN && hasS); |
| | | const railColor = 0x555555; |
| | | |
| | | if (hasH && !isCorner) { |
| | | const hStart = hasW ? 0 : midX; |
| | | const hEnd = hasE ? width : midX; |
| | | const hWidth = Math.max(1, hEnd - hStart); |
| | | g.beginFill(railColor); |
| | | g.drawRect(hStart, midY - Math.round(rail / 2), hWidth, rail); |
| | | g.endFill(); |
| | | } |
| | | if (hasV && !isCorner) { |
| | | const vStart = hasN ? 0 : midY; |
| | | const vEnd = hasS ? height : midY; |
| | | const vHeight = Math.max(1, vEnd - vStart); |
| | | g.beginFill(railColor); |
| | | g.drawRect(midX - Math.round(rail / 2), vStart, rail, vHeight); |
| | | g.endFill(); |
| | | } |
| | | if (isCorner) { |
| | | const cornerEast = hasE; |
| | | const cornerSouth = hasS; |
| | | const centerX = cornerEast ? (width - 1) : 0; |
| | | const centerY = cornerSouth ? (height - 1) : 0; |
| | | const angleStart = (cornerEast && cornerSouth) ? Math.PI : (cornerEast ? Math.PI / 2 : (cornerSouth ? -Math.PI / 2 : 0)); |
| | | const angleEnd = (cornerEast && cornerSouth) ? Math.PI * 1.5 : (cornerEast ? Math.PI : (cornerSouth ? 0 : Math.PI / 2)); |
| | | const radius = Math.min(Math.abs(centerX - midX), Math.abs(centerY - midY)); |
| | | g.lineStyle(rail, railColor, 1); |
| | | g.arc(centerX, centerY, radius, angleStart, angleEnd); |
| | | g.lineStyle(0, 0, 0); |
| | | } |
| | | |
| | | const rt = PIXI.RenderTexture.create({ width: width, height: height }); |
| | | this.pixiApp.renderer.render(g, rt); |
| | | return rt; |
| | | }, |
| | | attachCellLabel(sprite, textValue) { |
| | | const text = new PIXI.Text(String(textValue == null ? '' : textValue), { |
| | | fontFamily: 'Arial', |
| | | fontSize: 10, |
| | | fill: '#1f2937', |
| | | align: 'center' |
| | | }); |
| | | text.anchor.set(0.5); |
| | | text.position.set(sprite.width / 2, sprite.height / 2); |
| | | sprite.addChild(text); |
| | | sprite.textObj = text; |
| | | this.pixiLabelList.push(sprite); |
| | | }, |
| | | getTextureForCell(cell) { |
| | | const value = parseInt(cell.value, 10); |
| | | if (value === 4) { return this.textureMap.site; } |
| | | if (value === 5) { return this.textureMap.charge; } |
| | | if (value === 67) { return this.textureMap.elevator; } |
| | | if (value === -999) { return this.textureMap.lock; } |
| | | return this.textureMap.locEmpty; |
| | | }, |
| | | isTrackCell(cell) { |
| | | if (!cell) { return false; } |
| | | const type = cell.type ? String(cell.type).toLowerCase() : ''; |
| | | if (type === 'track' || type === 'crn' || type === 'dualcrn' || type === 'rgv') { return true; } |
| | | if (cell.trackSiteNo != null) { return true; } |
| | | const v = parseInt(cell.value, 10); |
| | | if (v === 3 || v === 9) { return true; } |
| | | if (cell.value != null) { |
| | | try { |
| | | const obj = (typeof cell.value === 'string') ? JSON.parse(cell.value) : cell.value; |
| | | if (obj && (obj.trackSiteNo != null || (obj.deviceNo != null && (type === 'crn' || type === 'dualcrn' || type === 'rgv')))) { |
| | | return true; |
| | | } |
| | | } catch (e) {} |
| | | const value = parseInt(cell.value, 10); |
| | | return value === 3 || value === 9; |
| | | }, |
| | | resolveTrackMask(map, rowIndex, colIndex) { |
| | | const TRACK_N = 1; |
| | | const TRACK_E = 2; |
| | | const TRACK_S = 4; |
| | | const TRACK_W = 8; |
| | | const row = Array.isArray(map) ? map[rowIndex] : null; |
| | | const cell = row && row[colIndex] ? row[colIndex] : null; |
| | | if (!this.isTrackCell(cell)) { |
| | | return TRACK_E | TRACK_W; |
| | | } |
| | | return false; |
| | | let mask = 0; |
| | | const north = rowIndex > 0 && Array.isArray(map[rowIndex - 1]) ? map[rowIndex - 1][colIndex] : null; |
| | | const east = row && colIndex + 1 < row.length ? row[colIndex + 1] : null; |
| | | const south = rowIndex + 1 < map.length && Array.isArray(map[rowIndex + 1]) ? map[rowIndex + 1][colIndex] : null; |
| | | const west = row && colIndex > 0 ? row[colIndex - 1] : null; |
| | | if (north && this.isTrackCell(north)) { mask |= TRACK_N; } |
| | | if (east && this.isTrackCell(east)) { mask |= TRACK_E; } |
| | | if (south && this.isTrackCell(south)) { mask |= TRACK_S; } |
| | | if (west && this.isTrackCell(west)) { mask |= TRACK_W; } |
| | | return mask || (TRACK_E | TRACK_W); |
| | | }, |
| | | drawTracks(map) { |
| | | if (!tracksGraphics || !Array.isArray(map)) { return; } |
| | | tracksGraphics.clear(); |
| | | const railColor = 0x6c727a; |
| | | const railWidth = Math.max(1, Math.round(Math.min(width, height) * 0.08)); |
| | | tracksGraphics.lineStyle(railWidth, railColor, 1); |
| | | |
| | | for (let r = 0; r < map.length; r++) { |
| | | const row = map[r]; |
| | | if (this.tracksGraphics) { this.tracksGraphics.clear(); } |
| | | }, |
| | | buildLocChunks(map, contentW, contentH) { |
| | | this.clearLocChunks(); |
| | | if (!this.pixiApp || !this.pixiApp.renderer || !this.shelvesContainer || !Array.isArray(map)) { return; } |
| | | const chunkSize = Math.max(256, parseInt(this.locChunkSize, 10) || 1024); |
| | | const chunkMap = new Map(); |
| | | for (let rowIndex = 0; rowIndex < map.length; rowIndex += 1) { |
| | | const row = map[rowIndex]; |
| | | if (!Array.isArray(row)) { continue; } |
| | | for (let c = 0; c < row.length; c++) { |
| | | const cell = row[c]; |
| | | if (!this.isTrackCell(cell)) { continue; } |
| | | const cx = c * width + width / 2; |
| | | const cy = r * height + height / 2; |
| | | const up = (r - 1 >= 0 && Array.isArray(map[r - 1])) ? map[r - 1][c] : null; |
| | | const right = (c + 1 < row.length) ? row[c + 1] : null; |
| | | const down = (r + 1 < map.length && Array.isArray(map[r + 1])) ? map[r + 1][c] : null; |
| | | const left = (c - 1 >= 0) ? row[c - 1] : null; |
| | | const hasN = this.isTrackCell(up); |
| | | const hasE = this.isTrackCell(right); |
| | | const hasS = this.isTrackCell(down); |
| | | const hasW = this.isTrackCell(left); |
| | | const seg = Math.min(width, height) * 0.5; |
| | | let drew = false; |
| | | if (hasN) { tracksGraphics.moveTo(cx, cy); tracksGraphics.lineTo(cx, cy - seg); drew = true; } |
| | | if (hasE) { tracksGraphics.moveTo(cx, cy); tracksGraphics.lineTo(cx + seg, cy); drew = true; } |
| | | if (hasS) { tracksGraphics.moveTo(cx, cy); tracksGraphics.lineTo(cx, cy + seg); drew = true; } |
| | | if (hasW) { tracksGraphics.moveTo(cx, cy); tracksGraphics.lineTo(cx - seg, cy); drew = true; } |
| | | if (!drew) { |
| | | tracksGraphics.moveTo(cx - seg * 0.4, cy); |
| | | tracksGraphics.lineTo(cx + seg * 0.4, cy); |
| | | for (let colIndex = 0; colIndex < row.length; colIndex += 1) { |
| | | const cell = row[colIndex]; |
| | | if (!cell || parseInt(cell.value, 10) !== 0) { continue; } |
| | | const posX = colIndex * this.cellWidth; |
| | | const posY = rowIndex * this.cellHeight; |
| | | const chunkX = Math.floor(posX / chunkSize); |
| | | const chunkY = Math.floor(posY / chunkSize); |
| | | const key = chunkX + ',' + chunkY; |
| | | let list = chunkMap.get(key); |
| | | if (!list) { |
| | | list = []; |
| | | chunkMap.set(key, list); |
| | | } |
| | | list.push({ |
| | | x: posX, |
| | | y: posY, |
| | | width: this.cellWidth, |
| | | height: this.cellHeight, |
| | | color: cell.locSts === 'F' ? 0xf05d5d : 0x5faeff |
| | | }); |
| | | } |
| | | } |
| | | |
| | | const chunkList = []; |
| | | chunkMap.forEach((cells, key) => { |
| | | const keyParts = key.split(','); |
| | | const chunkX = parseInt(keyParts[0], 10) || 0; |
| | | const chunkY = parseInt(keyParts[1], 10) || 0; |
| | | const chunkLeft = chunkX * chunkSize; |
| | | const chunkTop = chunkY * chunkSize; |
| | | const chunkWidth = Math.max(1, Math.min(chunkSize, contentW - chunkLeft)); |
| | | const chunkHeight = Math.max(1, Math.min(chunkSize, contentH - chunkTop)); |
| | | const graphics = new PIXI.Graphics(); |
| | | for (let i = 0; i < cells.length; i += 1) { |
| | | const cell = cells[i]; |
| | | graphics.beginFill(cell.color); |
| | | graphics.lineStyle(1, 0xffffff, 1); |
| | | graphics.drawRect(cell.x - chunkLeft, cell.y - chunkTop, cell.width, cell.height); |
| | | graphics.endFill(); |
| | | } |
| | | const texture = this.pixiApp.renderer.generateTexture( |
| | | graphics, |
| | | PIXI.SCALE_MODES.LINEAR, |
| | | 1, |
| | | new PIXI.Rectangle(0, 0, chunkWidth, chunkHeight) |
| | | ); |
| | | graphics.destroy(true); |
| | | const sprite = new PIXI.Sprite(texture); |
| | | sprite.position.set(chunkLeft, chunkTop); |
| | | sprite._chunkBounds = { |
| | | x: chunkLeft, |
| | | y: chunkTop, |
| | | width: chunkWidth, |
| | | height: chunkHeight |
| | | }; |
| | | this.shelvesContainer.addChild(sprite); |
| | | chunkList.push(sprite); |
| | | }); |
| | | this.locChunkList = chunkList; |
| | | this.updateVisibleLocChunks(); |
| | | }, |
| | | clearLocChunks() { |
| | | if (this.locCullRaf) { |
| | | cancelAnimationFrame(this.locCullRaf); |
| | | this.locCullRaf = null; |
| | | } |
| | | this.locChunkList = []; |
| | | if (!this.shelvesContainer) { return; } |
| | | const children = this.shelvesContainer.removeChildren(); |
| | | children.forEach((child) => { |
| | | if (child && typeof child.destroy === 'function') { |
| | | child.destroy({ children: true, texture: true, baseTexture: true }); |
| | | } |
| | | }); |
| | | }, |
| | | getViewportLocalBounds(padding) { |
| | | if (!this.mapRoot || !this.pixiApp) { return null; } |
| | | const viewport = this.getViewportSize(); |
| | | const pad = Math.max(0, Number(padding) || 0); |
| | | const points = [ |
| | | new PIXI.Point(-pad, -pad), |
| | | new PIXI.Point(viewport.width + pad, -pad), |
| | | new PIXI.Point(-pad, viewport.height + pad), |
| | | new PIXI.Point(viewport.width + pad, viewport.height + pad) |
| | | ]; |
| | | let minX = Infinity; |
| | | let minY = Infinity; |
| | | let maxX = -Infinity; |
| | | let maxY = -Infinity; |
| | | points.forEach((point) => { |
| | | const local = this.mapRoot.toLocal(point); |
| | | if (local.x < minX) { minX = local.x; } |
| | | if (local.y < minY) { minY = local.y; } |
| | | if (local.x > maxX) { maxX = local.x; } |
| | | if (local.y > maxY) { maxY = local.y; } |
| | | }); |
| | | if (!isFinite(minX) || !isFinite(minY) || !isFinite(maxX) || !isFinite(maxY)) { return null; } |
| | | return { minX: minX, minY: minY, maxX: maxX, maxY: maxY }; |
| | | }, |
| | | updateVisibleLocChunks() { |
| | | if (!this.locChunkList || this.locChunkList.length === 0) { return; } |
| | | const localBounds = this.getViewportLocalBounds(this.locCullPadding); |
| | | if (!localBounds) { return; } |
| | | for (let i = 0; i < this.locChunkList.length; i += 1) { |
| | | const sprite = this.locChunkList[i]; |
| | | const bounds = sprite && sprite._chunkBounds; |
| | | if (!bounds) { continue; } |
| | | const visible = bounds.x < localBounds.maxX && |
| | | bounds.x + bounds.width > localBounds.minX && |
| | | bounds.y < localBounds.maxY && |
| | | bounds.y + bounds.height > localBounds.minY; |
| | | if (sprite.visible !== visible) { |
| | | sprite.visible = visible; |
| | | } |
| | | } |
| | | }, |
| | | scheduleLocChunkCulling() { |
| | | if (this.locCullRaf) { return; } |
| | | this.locCullRaf = requestAnimationFrame(() => { |
| | | this.locCullRaf = null; |
| | | this.updateVisibleLocChunks(); |
| | | }); |
| | | }, |
| | | handleBlankPointerTap(globalPos) { |
| | | if (!globalPos || !this.mapRoot || !Array.isArray(this.map) || this.map.length === 0) { return; } |
| | | const local = this.mapRoot.toLocal(new PIXI.Point(globalPos.x, globalPos.y)); |
| | | const rowIndex = Math.floor(local.y / this.cellHeight); |
| | | const colIndex = Math.floor(local.x / this.cellWidth); |
| | | if (rowIndex < 0 || colIndex < 0 || rowIndex >= this.map.length) { |
| | | this.closeDetailPanel(); |
| | | return; |
| | | } |
| | | const row = this.map[rowIndex]; |
| | | if (!Array.isArray(row) || colIndex >= row.length) { |
| | | this.closeDetailPanel(); |
| | | return; |
| | | } |
| | | const cell = row[colIndex]; |
| | | if (!cell || parseInt(cell.value, 10) !== 0) { |
| | | this.closeDetailPanel(); |
| | | return; |
| | | } |
| | | this.openLocDetail(rowIndex, colIndex); |
| | | }, |
| | | parseLocNoMeta(locNo) { |
| | | if (locNo == null || locNo === '') { return { row: null, bay: null, lev: null }; } |
| | | const parts = String(locNo).split('-'); |
| | | return { |
| | | row: parts.length > 0 ? parts[0] : null, |
| | | bay: parts.length > 1 ? parts[1] : null, |
| | | lev: parts.length > 2 ? parts[2] : null |
| | | }; |
| | | }, |
| | | openLocDetail(rowIndex, colIndex, sprite) { |
| | | const cell = this.map[rowIndex] && this.map[rowIndex][colIndex] ? this.map[rowIndex][colIndex] : null; |
| | | if (!cell) { return; } |
| | | const locMeta = this.parseLocNoMeta(cell.locNo); |
| | | if (sprite) { |
| | | this.highlightSprite(sprite, 0x915eff); |
| | | } else { |
| | | this.restoreHighlightedSprite(); |
| | | this.highlightLocCell(rowIndex, colIndex); |
| | | } |
| | | this.detailType = 'loc'; |
| | | this.detailPayload = { |
| | | locNo: cell.locNo, |
| | | locSts: cell.locSts, |
| | | row: cell.row != null ? cell.row : locMeta.row, |
| | | bay: cell.bay != null ? cell.bay : locMeta.bay, |
| | | lev: cell.lev != null ? cell.lev : (locMeta.lev != null ? locMeta.lev : this.currentLev) |
| | | }; |
| | | this.detailPanelOpen = true; |
| | | }, |
| | | openSiteDetail(cell, sprite) { |
| | | this.clearLocHighlight(); |
| | | this.highlightSprite(sprite, 0xff7e47); |
| | | $.ajax({ |
| | | url: baseUrl + "/console/site/detail", |
| | | headers: { token: localStorage.getItem('token') }, |
| | | data: { siteId: cell.data }, |
| | | method: 'post', |
| | | success: (res) => { |
| | | if (res.code === 200) { |
| | | this.detailType = 'site'; |
| | | this.detailPayload = res.data || {}; |
| | | this.detailPanelOpen = true; |
| | | } else if (res.code === 403) { |
| | | parent.location.href = baseUrl + "/login"; |
| | | } else { |
| | | this.showMessage('error', res.msg || '站点详情加载失败'); |
| | | } |
| | | } |
| | | }); |
| | | }, |
| | | highlightSprite(sprite, tint) { |
| | | this.restoreHighlightedSprite(); |
| | | if (!sprite) { return; } |
| | | sprite.tint = tint; |
| | | this.highlightedSprite = sprite; |
| | | }, |
| | | restoreHighlightedSprite() { |
| | | if (this.highlightedSprite) { |
| | | this.highlightedSprite.tint = this.highlightedSprite.baseTint || 0xFFFFFF; |
| | | this.highlightedSprite = null; |
| | | } |
| | | }, |
| | | highlightLocCell(rowIndex, colIndex) { |
| | | this.clearLocHighlight(); |
| | | this.highlightedLocCell = { rowIndex: rowIndex, colIndex: colIndex }; |
| | | const graphic = new PIXI.Graphics(); |
| | | graphic.lineStyle(2, 0x915eff, 0.95); |
| | | graphic.beginFill(0x915eff, 0.14); |
| | | graphic.drawRect(colIndex * this.cellWidth, rowIndex * this.cellHeight, this.cellWidth, this.cellHeight); |
| | | graphic.endFill(); |
| | | this.objectsOverlayContainer.addChild(graphic); |
| | | this.highlightedLocGraphic = graphic; |
| | | }, |
| | | clearLocHighlight() { |
| | | this.highlightedLocCell = null; |
| | | if (this.highlightedLocGraphic) { |
| | | if (this.highlightedLocGraphic.parent) { |
| | | this.highlightedLocGraphic.parent.removeChild(this.highlightedLocGraphic); |
| | | } |
| | | this.highlightedLocGraphic.destroy(true); |
| | | this.highlightedLocGraphic = null; |
| | | } |
| | | }, |
| | | closeDetailPanel() { |
| | | this.detailPanelOpen = false; |
| | | this.detailType = ''; |
| | | this.detailPayload = null; |
| | | this.restoreHighlightedSprite(); |
| | | this.clearLocHighlight(); |
| | | }, |
| | | parseRotation(value) { |
| | | const num = parseInt(value, 10); |
| | | if (!isFinite(num)) { return 0; } |
| | | const rot = ((num % 360) + 360) % 360; |
| | | return (rot === 90 || rot === 180 || rot === 270) ? rot : 0; |
| | | const rotation = ((num % 360) + 360) % 360; |
| | | return rotation === 90 || rotation === 180 || rotation === 270 ? rotation : 0; |
| | | }, |
| | | parseMirror(value) { |
| | | if (value === true || value === false) { return value; } |
| | |
| | | const str = String(value).toLowerCase(); |
| | | return str === '1' || str === 'true' || str === 'y'; |
| | | }, |
| | | buildMissingMapConfigList(byCode) { |
| | | const list = []; |
| | | if (!byCode[this.mapConfigCodes.rotate]) { |
| | | list.push({ |
| | | name: '地图旋转', |
| | | code: this.mapConfigCodes.rotate, |
| | | value: String(this.mapRotation || 0), |
| | | type: 1, |
| | | status: 1, |
| | | selectType: 'map' |
| | | }); |
| | | } |
| | | if (!byCode[this.mapConfigCodes.mirror]) { |
| | | list.push({ |
| | | name: '地图镜像', |
| | | code: this.mapConfigCodes.mirror, |
| | | value: this.mapMirrorX ? '1' : '0', |
| | | type: 1, |
| | | status: 1, |
| | | selectType: 'map' |
| | | }); |
| | | } |
| | | return list; |
| | | }, |
| | | createMapConfigs(list) { |
| | | if (!Array.isArray(list) || list.length === 0) { return; } |
| | | list.forEach((cfg) => { |
| | | $.ajax({ |
| | | url: baseUrl + "/config/add/auth", |
| | | headers: { token: localStorage.getItem('token') }, |
| | | method: 'POST', |
| | | data: cfg |
| | | }); |
| | | }); |
| | | }, |
| | | loadMapTransformConfig() { |
| | | if (!window.$ || typeof baseUrl === 'undefined') { return; } |
| | | $.ajax({ |
| | | url: baseUrl + "/config/listAll/auth", |
| | | headers: { 'token': localStorage.getItem('token') }, |
| | | headers: { token: localStorage.getItem('token') }, |
| | | dataType: 'json', |
| | | method: 'GET', |
| | | success: (res) => { |
| | |
| | | } |
| | | const byCode = {}; |
| | | res.data.forEach((item) => { |
| | | if (item && item.code) { byCode[item.code] = item; } |
| | | if (item && item.code) { |
| | | byCode[item.code] = item; |
| | | } |
| | | }); |
| | | const rotateCfg = byCode[this.mapConfigCodes.rotate]; |
| | | const mirrorCfg = byCode[this.mapConfigCodes.mirror]; |
| | | if (rotateCfg && rotateCfg.value != null) { |
| | | this.mapRotation = this.parseRotation(rotateCfg.value); |
| | | if (byCode[this.mapConfigCodes.rotate] && byCode[this.mapConfigCodes.rotate].value != null) { |
| | | this.mapRotation = this.parseRotation(byCode[this.mapConfigCodes.rotate].value); |
| | | } |
| | | if (mirrorCfg && mirrorCfg.value != null) { |
| | | this.mapMirrorX = this.parseMirror(mirrorCfg.value); |
| | | if (byCode[this.mapConfigCodes.mirror] && byCode[this.mapConfigCodes.mirror].value != null) { |
| | | this.mapMirrorX = this.parseMirror(byCode[this.mapConfigCodes.mirror].value); |
| | | } |
| | | if (mapContentSize && mapContentSize.width > 0 && mapContentSize.height > 0) { |
| | | this.createMapConfigs(this.buildMissingMapConfigList(byCode)); |
| | | if (this.mapContentSize.width > 0 && this.mapContentSize.height > 0) { |
| | | this.applyMapTransform(true); |
| | | } |
| | | } |
| | | }); |
| | | }, |
| | | saveMapTransformConfig() { |
| | | $.ajax({ |
| | | url: baseUrl + "/config/updateBatch", |
| | | headers: { token: localStorage.getItem('token') }, |
| | | data: JSON.stringify([ |
| | | { code: this.mapConfigCodes.rotate, value: String(this.mapRotation || 0) }, |
| | | { code: this.mapConfigCodes.mirror, value: this.mapMirrorX ? '1' : '0' } |
| | | ]), |
| | | dataType: 'json', |
| | | contentType: 'application/json;charset=UTF-8', |
| | | method: 'POST' |
| | | }); |
| | | }, |
| | | rotateMap() { |
| | | this.mapRotation = (this.mapRotation + 90) % 360; |
| | | this.applyMapTransform(true); |
| | | this.saveMapTransformConfig(); |
| | | }, |
| | | toggleMirror() { |
| | | this.mapMirrorX = !this.mapMirrorX; |
| | | this.applyMapTransform(true); |
| | | this.saveMapTransformConfig(); |
| | | }, |
| | | getViewportSize() { |
| | | if (!pixiApp || !pixiApp.renderer) { return { width: 0, height: 0 }; } |
| | | const screen = pixiApp.renderer.screen; |
| | | if (!this.pixiApp || !this.pixiApp.renderer) { return { width: 0, height: 0 }; } |
| | | const screen = this.pixiApp.renderer.screen; |
| | | if (screen && screen.width > 0 && screen.height > 0) { |
| | | return { width: screen.width, height: screen.height }; |
| | | } |
| | | const rect = pixiApp.view ? pixiApp.view.getBoundingClientRect() : null; |
| | | const rect = this.pixiApp.view ? this.pixiApp.view.getBoundingClientRect() : null; |
| | | return { width: rect ? rect.width : 0, height: rect ? rect.height : 0 }; |
| | | }, |
| | | getViewportPadding() { |
| | | return { top: 24, right: 24, bottom: 24, left: 24 }; |
| | | }, |
| | | getTransformedContentSize() { |
| | | const size = mapContentSize || { width: 0, height: 0 }; |
| | | const w = size.width || 0; |
| | | const h = size.height || 0; |
| | | const rot = ((this.mapRotation % 360) + 360) % 360; |
| | | const swap = rot === 90 || rot === 270; |
| | | return { width: swap ? h : w, height: swap ? w : h }; |
| | | const width = this.mapContentSize.width || 0; |
| | | const height = this.mapContentSize.height || 0; |
| | | const rotation = ((this.mapRotation % 360) + 360) % 360; |
| | | const swap = rotation === 90 || rotation === 270; |
| | | return { width: swap ? height : width, height: swap ? width : height }; |
| | | }, |
| | | fitStageToContent() { |
| | | if (!pixiApp || !mapContentSize) { return; } |
| | | if (!this.pixiApp || !this.mapContentSize.width || !this.mapContentSize.height) { return; } |
| | | const size = this.getTransformedContentSize(); |
| | | const contentW = size.width || 0; |
| | | const contentH = size.height || 0; |
| | | if (contentW <= 0 || contentH <= 0) { return; } |
| | | const viewport = this.getViewportSize(); |
| | | const vw = viewport.width; |
| | | const vh = viewport.height; |
| | | let scale = Math.min(vw / contentW, vh / contentH) * 0.95; |
| | | const padding = this.getViewportPadding(); |
| | | const availableW = Math.max(1, viewport.width - padding.left - padding.right); |
| | | const availableH = Math.max(1, viewport.height - padding.top - padding.bottom); |
| | | let scale = Math.min(availableW / size.width, availableH / size.height) * 0.95; |
| | | if (!isFinite(scale) || scale <= 0) { scale = 1; } |
| | | const baseW = mapContentSize.width || contentW; |
| | | const baseH = mapContentSize.height || contentH; |
| | | const mirrorX = this.mapMirrorX ? -1 : 1; |
| | | const scaleX = scale * mirrorX; |
| | | const scaleY = scale; |
| | | const posX = (vw / 2) - (baseW / 2) * scaleX; |
| | | const posY = (vh / 2) - (baseH / 2) * scaleY; |
| | | pixiApp.stage.setTransform(posX, posY, scaleX, scaleY, 0, 0, 0, 0, 0); |
| | | const centerX = padding.left + availableW / 2; |
| | | const centerY = padding.top + availableH / 2; |
| | | const posX = centerX - (this.mapContentSize.width / 2) * scaleX; |
| | | const posY = centerY - (this.mapContentSize.height / 2) * scaleY; |
| | | this.pixiApp.stage.setTransform(posX, posY, scaleX, scaleY, 0, 0, 0, 0, 0); |
| | | this.scheduleAdjustLabels(); |
| | | this.scheduleLocChunkCulling(); |
| | | }, |
| | | applyMapTransform(fitToView) { |
| | | if (!mapRoot || !mapContentSize) { return; } |
| | | const contentW = mapContentSize.width || 0; |
| | | const contentH = mapContentSize.height || 0; |
| | | if (contentW <= 0 || contentH <= 0) { return; } |
| | | mapRoot.pivot.set(contentW / 2, contentH / 2); |
| | | mapRoot.position.set(contentW / 2, contentH / 2); |
| | | mapRoot.rotation = (this.mapRotation % 360) * Math.PI / 180; |
| | | mapRoot.scale.set(1, 1); |
| | | if (fitToView) { this.fitStageToContent(); } |
| | | }, |
| | | rightEvent(x, y, e) { |
| | | this.drawerLocNo = true |
| | | this.drawerLocNoData = {x:x, y: y, z: this.currentLev, locNo: this.map[x][y].locNo, |
| | | locSts: this.map[x][y].locSts,row:this.map[x][y].row, bay: this.map[x][y].bay, lev: this.currentLev}; |
| | | }, |
| | | webSocketOnOpen(e) { |
| | | console.log("open"); |
| | | }, |
| | | webSocketOnError(e) { |
| | | console.log(e); |
| | | }, |
| | | webSocketOnMessage(e) { |
| | | const result = JSON.parse(e.data); |
| | | if (result.url == "/console/map/auth") { |
| | | this.setMap(JSON.parse(result.data)) |
| | | }else if (result.url == "/console/locMap/auth") { |
| | | this.setMap(JSON.parse(result.data)) |
| | | if (!this.mapRoot || !this.mapContentSize.width || !this.mapContentSize.height) { return; } |
| | | const contentW = this.mapContentSize.width; |
| | | const contentH = this.mapContentSize.height; |
| | | this.mapRoot.pivot.set(contentW / 2, contentH / 2); |
| | | this.mapRoot.position.set(contentW / 2, contentH / 2); |
| | | this.mapRoot.rotation = (this.mapRotation % 360) * Math.PI / 180; |
| | | this.mapRoot.scale.set(1, 1); |
| | | if (fitToView) { |
| | | this.fitStageToContent(); |
| | | } else { |
| | | this.scheduleAdjustLabels(); |
| | | this.scheduleLocChunkCulling(); |
| | | } |
| | | }, |
| | | webSocketClose(e) { |
| | | console.log("close"); |
| | | }, |
| | | sendWs(message) { |
| | | if (ws.readyState == WebSocket.OPEN) { |
| | | ws.send(message) |
| | | scheduleAdjustLabels() { |
| | | if (this.adjustLabelTimer) { |
| | | clearTimeout(this.adjustLabelTimer); |
| | | } |
| | | this.adjustLabelTimer = setTimeout(() => { |
| | | this.adjustLabelScale(); |
| | | this.adjustLabelTimer = null; |
| | | }, 20); |
| | | }, |
| | | openDrawerSta(data) { |
| | | let that = this |
| | | this.drawerSta = true; |
| | | $.ajax({ |
| | | url: baseUrl + "/console/site/detail", |
| | | headers: { |
| | | 'token': localStorage.getItem('token') |
| | | }, |
| | | data: { |
| | | siteId: data.data |
| | | }, |
| | | method: 'post', |
| | | success: function(res) { |
| | | //解析数据 |
| | | let siteInfo = res.data; |
| | | if (res.code == 200) { |
| | | that.drawerStaData = siteInfo; |
| | | } |
| | | } |
| | | }) |
| | | adjustLabelScale() { |
| | | if (!this.pixiApp || !this.pixiLabelList.length) { return; } |
| | | const scaleX = this.pixiApp.stage.scale.x || 1; |
| | | const scaleY = this.pixiApp.stage.scale.y || 1; |
| | | const scale = Math.max(Math.abs(scaleX), Math.abs(scaleY), 0.0001); |
| | | const mirrorSign = scaleX < 0 ? -1 : 1; |
| | | const inverseRotation = -this.mapRoot.rotation; |
| | | const visible = scale >= 0.25; |
| | | this.pixiLabelList.forEach((sprite) => { |
| | | if (!sprite || !sprite.textObj) { return; } |
| | | const textObj = sprite.textObj; |
| | | let textScale = 1 / scale; |
| | | textScale = Math.max(0.9, Math.min(textScale, 3)); |
| | | textObj.scale.set(textScale * mirrorSign, textScale); |
| | | textObj.rotation = inverseRotation; |
| | | textObj.visible = visible; |
| | | textObj.position.set(sprite.width / 2, sprite.height / 2); |
| | | }); |
| | | }, |
| | | } |
| | | }) |
| | | |
| | | function getContainer(value) { |
| | | let graphics = new PIXI.Graphics(); |
| | | if (value === 0) { |
| | | graphics.beginFill(0x55aaff); |
| | | } else if (value === 3) {//母轨道 |
| | | graphics.beginFill(0x00ff7f); |
| | | graphics.visible = true; |
| | | } else if (value === 4) {//站点 |
| | | graphics.beginFill(0xffff00); |
| | | graphics.visible = true; |
| | | } else if (value === 5) {//充电桩 |
| | | graphics.beginFill(0xffaa7f); |
| | | graphics.visible = true; |
| | | } else if (value === 9) {//轨迹 |
| | | graphics.beginFill(0xff0000); |
| | | }else if (value === 67) {//提升机 |
| | | graphics.beginFill(0xaaffff); |
| | | }else if (value === -999) {//路径锁定 |
| | | graphics.beginFill(0xf83333); |
| | | }else if (value === 1000) {//满库位 |
| | | graphics.beginFill(0xf83333); |
| | | } |
| | | graphics.lineStyle(1, 0xffffff, 1); |
| | | graphics.drawRect(0, 0, width, height); |
| | | graphics.endFill(); |
| | | |
| | | return graphics; |
| | | } |
| | | |
| | | function getGraphics(color, width, height, x, y) { |
| | | let graphics = new PIXI.Graphics(); |
| | | graphics.beginFill(color); |
| | | graphics.lineStyle(1, 0xffffff, 1); |
| | | graphics.drawRect(0, 0, width, height); |
| | | graphics.position.set(x, y); |
| | | graphics.endFill(); |
| | | return graphics; |
| | | } |
| | | |
| | | function getSprite(value, x, y, item, pointerDownEvent) { |
| | | let sprite; |
| | | if (value == 0) { |
| | | if(item.locSts == 'O') { |
| | | sprite = new PIXI.Sprite(graphics0); |
| | | }else if(item.locSts == 'F') { |
| | | sprite = new PIXI.Sprite(graphicsF); |
| | | }else { |
| | | sprite = new PIXI.Sprite(graphics0); |
| | | startContainerResizeObserve() { |
| | | this.resizeToContainer = () => { |
| | | if (!this.pixiApp || !this.mapContentSize.width || !this.mapContentSize.height) { return; } |
| | | this.fitStageToContent(); |
| | | }; |
| | | if (window.ResizeObserver) { |
| | | this.containerResizeObserver = new ResizeObserver(() => { |
| | | this.resizeToContainer(); |
| | | }); |
| | | this.containerResizeObserver.observe(this.$refs.shell); |
| | | } else { |
| | | window.addEventListener('resize', this.resizeToContainer); |
| | | } |
| | | } |
| | | } else if (value == 3) { |
| | | sprite = new PIXI.Sprite(graphics3); |
| | | } else if (value == 4) { |
| | | sprite = new PIXI.Sprite(graphics4); |
| | | } else if (value == 5) { |
| | | sprite = new PIXI.Sprite(graphics5); |
| | | } else if (value == 9) { |
| | | sprite = new PIXI.Sprite(graphics9); |
| | | } else if (value == 67) { |
| | | sprite = new PIXI.Sprite(graphics67); |
| | | } else if (value == -999) { |
| | | sprite = new PIXI.Sprite(graphicsLock); |
| | | } else { |
| | | sprite = new PIXI.Sprite(graphics0); |
| | | } |
| | | sprite.position.set(x, y); |
| | | const type = item && item.type ? String(item.type).toLowerCase() : ''; |
| | | const numVal = parseInt(value, 10); |
| | | const isTrackCell = numVal === 3 || numVal === 9 || type === 'track' || type === 'crn' || type === 'dualcrn' || type === 'rgv'; |
| | | if (!isTrackCell) { |
| | | sprite.interactive = true; // 必须要设置才能接收事件 |
| | | sprite.buttonMode = true; // 让光标在hover时变为手型指针 |
| | | sprite.on('pointerdown', (e) => { |
| | | pointerDownEvent(e) |
| | | }) |
| | | } |
| | | }); |
| | | |
| | | return sprite; |
| | | } |
| | | |
| | | /** |
| | | * 更新颜色 |
| | | */ |
| | | function updateColor(sprite, color) { |
| | | // graphics.clear() |
| | | // graphics.beginFill(color); |
| | | // graphics.drawRect(0, 0, width, height); |
| | | sprite.tint = color; |
| | | } |
| | | |
| | | new Vue({ |
| | | el: '#app' |
| | | }); |
| | | </script> |
| | | </body> |
| | | </html> |