| | |
| | | <script setup> |
| | | import { ref, onMounted, watch } from 'vue'; |
| | | import { get, post, postBlob } from '@/utils/request.js' |
| | | import { message, Modal } from 'ant-design-vue'; |
| | | import { logout } from '@/config.js'; |
| | | import { formatMessage } from '@/utils/localeUtils.js'; |
| | | import { |
| | | CompressOutlined, |
| | | } from "@ant-design/icons-vue"; |
| | | import * as PIXI from 'pixi.js' |
| | | |
| | | let width = 25; |
| | | let height = 25; |
| | | let pixiApp; |
| | | let pixiStageMap = new Map(); |
| | | let objectsContainer; |
| | | let map = [] |
| | | |
| | | const pixiView = ref(null) |
| | | const mapFps = ref(0) |
| | | const currentLev = ref(1) |
| | | const levList = ref([]) |
| | | const pointContainerWidth = ref(0) |
| | | const drawer = ref(false) |
| | | const drawerLocData = ref(null) |
| | | const drawerLocDetls = ref([]) |
| | | const drawerLocDetlField = ref([]) |
| | | |
| | | const drawerOper = ref(false) |
| | | const drawerOperLocNo = ref(null) |
| | | const drawerOperMatnr = ref(null) |
| | | const drawerOperMaktx = ref(null) |
| | | |
| | | onMounted(() => { |
| | | createMap(); |
| | | init(currentLev.value); |
| | | }) |
| | | |
| | | watch(drawer, (newVal, oldVal) => { |
| | | if (!drawer.value) { |
| | | var rectangle = pixiStageMap.get(drawerLocData.value.locNo) |
| | | updateColor(rectangle, rectangle.originColor);//恢复颜色 |
| | | } |
| | | }) |
| | | |
| | | function switchLev(lev) { |
| | | currentLev.value = lev; |
| | | init(lev); |
| | | } |
| | | |
| | | function init(lev) { |
| | | get('/api/locMap/getData/' + lev + '/auth', {}).then(resp => { |
| | | let result = resp.data; |
| | | if (result.code == 200) { |
| | | let tmp = JSON.parse(result.data); |
| | | let tmpMap = [] |
| | | tmp.forEach((item, index) => { |
| | | let data2 = [] |
| | | item.forEach((val, idx) => { |
| | | val.searchStatus = false//搜索标记 |
| | | val.rectangle = null; |
| | | data2.push(val) |
| | | }) |
| | | pointContainerWidth.value = item.length * (40 + 1) |
| | | tmpMap.push(data2) |
| | | }) |
| | | |
| | | createMapData(tmpMap); |
| | | } |
| | | }) |
| | | |
| | | get('/api/locMap/getLev', {}).then(resp => { |
| | | let result = resp.data; |
| | | if (result.code == 200) { |
| | | let tmp = result.data; |
| | | levList.value = tmp; |
| | | } |
| | | }) |
| | | } |
| | | |
| | | function createMap() { |
| | | //Create a Pixi Application |
| | | pixiApp = new PIXI.Application({ |
| | | width: pixiView.value.offsetWidth, |
| | | height: window.innerHeight * 0.75, |
| | | backgroundColor: 0xF5F7F9FF, |
| | | // resizeTo: window |
| | | }); |
| | | |
| | | //Add the canvas that Pixi automatically created for you to the HTML document |
| | | pixiView.value.appendChild(pixiApp.view) |
| | | |
| | | // 创建一个容器来管理大批量的显示对象 |
| | | objectsContainer = new PIXI.Container(); |
| | | |
| | | pixiApp.stage.addChild(objectsContainer); |
| | | |
| | | //*******************拖动画布******************* |
| | | let stageOriginalPos; |
| | | let mouseDownPoint; |
| | | 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; |
| | | } |
| | | } |
| | | ); |
| | | |
| | | pixiApp.renderer.plugins.interaction.on( |
| | | 'pointermove', |
| | | (event) => { |
| | | const globalPos = event.data.global; |
| | | |
| | | if (touchBlank) { |
| | | // 拖拽画布 |
| | | const dx = globalPos.x - mouseDownPoint[0]; |
| | | const dy = globalPos.y - mouseDownPoint[1]; |
| | | pixiApp.stage.position.set( |
| | | stageOriginalPos[0] + dx, |
| | | stageOriginalPos[1] + dy |
| | | ); |
| | | } |
| | | } |
| | | ); |
| | | |
| | | pixiApp.renderer.plugins.interaction.on( |
| | | 'pointerup', |
| | | (event) => { |
| | | touchBlank = false; |
| | | } |
| | | ); |
| | | //*******************拖动画布******************* |
| | | |
| | | //*******************缩放画布******************* |
| | | pixiApp.view.addEventListener('wheel', (event) => { |
| | | event.stopPropagation(); |
| | | event.preventDefault(); |
| | | // 因为画布是充满视窗的,所以clientX等于mouse point在renderer上的x坐标 |
| | | const globalPos = [event.clientX, event.clientY]; |
| | | const delta = event.deltaY; |
| | | const oldZoom = pixiApp.stage.scale.x; |
| | | let newZoom = oldZoom * 0.999 ** delta; |
| | | |
| | | // const oldStageMatrix = app.stage.localTransform.clone(); |
| | | // const oldStagePos = oldStageMatrix.applyInverse(pointerGlobalPos); |
| | | const oldStagePos = globalPos; |
| | | const dx = oldStagePos[0] * oldZoom - oldStagePos[0] * newZoom; |
| | | const dy = oldStagePos[1] * oldZoom - oldStagePos[1] * newZoom; |
| | | |
| | | pixiApp.stage.setTransform( |
| | | pixiApp.stage.position.x + dx, |
| | | pixiApp.stage.position.y + dy, |
| | | newZoom, |
| | | newZoom, |
| | | 0, |
| | | 0, |
| | | 0, |
| | | 0, |
| | | 0 |
| | | ); |
| | | |
| | | }); |
| | | //*******************缩放画布******************* |
| | | |
| | | //*******************FPS******************* |
| | | var g_Time = 0; |
| | | pixiApp.ticker.add((delta) => { |
| | | var timeNow = (new Date()).getTime(); |
| | | var timeDiff = timeNow - g_Time; |
| | | g_Time = timeNow; |
| | | var fps = 1000 / timeDiff; |
| | | mapFps.value = parseInt(fps) |
| | | }); |
| | | //*******************FPS******************* |
| | | } |
| | | |
| | | function createMapData(map) { |
| | | objectsContainer.removeChildren(); |
| | | map.forEach((item, index) => { |
| | | for (let idx = 0; idx < item.length; idx++) { |
| | | let val = item[idx] |
| | | if (val.value < 0) { |
| | | continue; |
| | | } |
| | | let rectangle = getContainer(val.value, idx * width, index * height, map[index][idx].locSts); |
| | | rectangle.on('click', (e) => { |
| | | openLocDrawer(index, idx, map[index][idx], { x: e.data.originalEvent.offsetX, y: e.data.originalEvent.offsetY }) |
| | | updateColor(rectangle, 0x9900ff); |
| | | }); |
| | | rectangle.locX = index; |
| | | rectangle.locY = idx; |
| | | |
| | | pixiStageMap.set(map[index][idx].locNo, rectangle); |
| | | objectsContainer.addChild(rectangle); |
| | | } |
| | | }) |
| | | |
| | | //视角居中 |
| | | let containerWidth = (pixiApp.view.width - objectsContainer.width) / 2; |
| | | let containerHeight = (pixiApp.view.height - objectsContainer.height) / 2; |
| | | pixiApp.stage.position.set(containerWidth, containerHeight); |
| | | } |
| | | |
| | | function containerAppViewCenter() { |
| | | //视角居中 |
| | | let containerWidth = (pixiApp.view.width - objectsContainer.width) / 2; |
| | | let containerHeight = (pixiApp.view.height - objectsContainer.height) / 2; |
| | | pixiApp.stage.position.set(containerWidth, containerHeight); |
| | | } |
| | | |
| | | function openLocDrawer(x, y, loc, e) { |
| | | drawer.value = true; |
| | | drawerLocData.value = loc; |
| | | |
| | | get('/api/locDetl/locNo/' + loc.locNo, {}).then(resp => { |
| | | let result = resp.data; |
| | | if (result.code == 200) { |
| | | drawerLocDetls.value = result.data; |
| | | } |
| | | }) |
| | | } |
| | | |
| | | function getContainer(value, x, y, locSts) { |
| | | let rectangle = new PIXI.Graphics(); |
| | | if (value === 0) { |
| | | if (locSts === "F") { |
| | | rectangle.beginFill(0xff0000); |
| | | rectangle.originColor = 0xff0000; |
| | | } else if (locSts === "O") { |
| | | rectangle.beginFill(0x55aaff); |
| | | rectangle.originColor = 0x55aaff; |
| | | } else if (locSts === "D") { |
| | | rectangle.beginFill(0xc2c934); |
| | | rectangle.originColor = 0xc2c934; |
| | | } else if (locSts === "P") { |
| | | rectangle.beginFill(0xf1aa19); |
| | | rectangle.originColor = 0xf1aa19; |
| | | } else if (locSts === "R") { |
| | | rectangle.beginFill(0x618593); |
| | | rectangle.originColor = 0x618593; |
| | | } else if (locSts === "S") { |
| | | rectangle.beginFill(0xfa736f); |
| | | rectangle.originColor = 0xfa736f; |
| | | } else { |
| | | rectangle.beginFill(0x86779d); |
| | | rectangle.originColor = 0x86779d; |
| | | } |
| | | } else if (value === 3) {//母轨道 |
| | | rectangle.beginFill(0x00ff7f); |
| | | rectangle.originColor = 0x00ff7f; |
| | | rectangle.visible = false; |
| | | } else if (value === 4) {//站点 |
| | | rectangle.beginFill(0xffff00); |
| | | rectangle.originColor = 0xffff00; |
| | | rectangle.visible = false; |
| | | } else if (value === 5) {//充电桩 |
| | | rectangle.beginFill(0xffaa7f); |
| | | rectangle.originColor = 0xffaa7f; |
| | | rectangle.visible = false; |
| | | } else if (value === 9) {//轨迹 |
| | | rectangle.beginFill(0xff0000); |
| | | rectangle.originColor = 0xff0000; |
| | | } |
| | | // rectangle.lineStyle(1, 0xffffff, 1); |
| | | rectangle.drawRect(0, 0, width, height); |
| | | rectangle.x = x; |
| | | rectangle.y = y; |
| | | // 设置是否可以交互 |
| | | rectangle.interactive = true; |
| | | rectangle.endFill(); |
| | | |
| | | // 创建文本对象 |
| | | const style = new PIXI.TextStyle({ |
| | | fontSize: 14 * window.devicePixelRatio, |
| | | fill: 'white', |
| | | align: 'center', // 设置文本水平居中对齐 |
| | | verticalAlign: 'middle' // 设置文本垂直居中对齐 |
| | | }); |
| | | const text = new PIXI.Text(locSts, style); |
| | | text.anchor.set(0.5); // 设置文本锚点为中心点 |
| | | text.position.set(rectangle.width / 2, rectangle.height / 2); // 将文本位置设置为Graphics对象的中心点 |
| | | // 将文本对象添加到Graphics对象中 |
| | | rectangle.addChild(text); |
| | | |
| | | return rectangle; |
| | | } |
| | | |
| | | /** |
| | | * 更新颜色 |
| | | */ |
| | | function updateColor(rectangle, color) { |
| | | rectangle.clear() |
| | | rectangle.beginFill(color); |
| | | rectangle.drawRect(0, 0, width, height); |
| | | } |
| | | |
| | | </script> |
| | | |
| | | <script> |
| | | export default { |
| | | name: '库位地图' |
| | | } |
| | | </script> |
| | | |
| | | <template> |
| | | <div style="position: relative;overflow: hidden;"> |
| | | <div ref="pixiView"> |
| | | |
| | | </div> |
| | | |
| | | <!--输出操作和FPS--> |
| | | <div style="position: absolute;top: 0px;right: 10px;user-select: none;"> |
| | | <div>FPS:{{ mapFps }}</div> |
| | | <a-button @click="drawerOper = true">操作</a-button> |
| | | </div> |
| | | |
| | | <!--输出操作和FPS--> |
| | | <div style="position: absolute;bottom: 20px;left: 20px;user-select: none;"> |
| | | <a-button type="dashed" @click="containerAppViewCenter"> |
| | | <CompressOutlined /> |
| | | </a-button> |
| | | </div> |
| | | |
| | | <div> |
| | | <a-drawer v-model:open="drawer" placement="right" style="background: #f3f3f3;"> |
| | | <div style="margin-top: 10px;"> |
| | | <a-tag>{{ formatMessage('locMap.locNo', '库位号') }}</a-tag>{{ drawerLocData.locNo }} |
| | | </div> |
| | | <div style="margin-top: 20px;"> |
| | | <a-tag>{{ formatMessage('locMap.locSts', '库位状态') }}</a-tag>{{ drawerLocData.locSts }}.{{ |
| | | drawerLocData.locSts$ }} |
| | | </div> |
| | | <div style="margin-top: 20px;"> |
| | | <div v-for="(item, index) in drawerLocDetls" :key="index" style="margin-top: 20px;"> |
| | | <a-card :title="item.matnr" :bordered="false" style="width: 300px"> |
| | | <div>{{ formatMessage('locMap.batch', '批号') }}:{{ item.batch }}</div> |
| | | <div>{{ formatMessage('locMap.orderNo', '单据编号') }}:{{ item.orderNo }}</div> |
| | | <div>{{ formatMessage('locMap.anfme', '数量') }}:{{ item.anfme }}</div> |
| | | <div v-for="(field, index) in item.dynamicFieldsList" :key="index">{{ |
| | | formatMessage('locMap.' + field.key, field.desc) }}:{{ field.value }}</div> |
| | | </a-card> |
| | | </div> |
| | | </div> |
| | | </a-drawer> |
| | | </div> |
| | | |
| | | <div> |
| | | <a-drawer v-model:open="drawerOper" placement="right"> |
| | | <div style="margin-top: 10px;"> |
| | | <div> |
| | | {{ formatMessage('locMap.locNo', '库位号') }} |
| | | </div> |
| | | <div style="margin-top: 10px;"> |
| | | <a-input v-model:value="drawerOperLocNo" :placeholder="formatMessage('locMap.locNo', '库位号')" /> |
| | | </div> |
| | | </div> |
| | | <div style="margin-top: 20px;"> |
| | | <div> |
| | | {{ formatMessage('locMap.matnr', '商品编号') }} |
| | | </div> |
| | | <div style="margin-top: 10px;"> |
| | | <a-input v-model:value="drawerOperMatnr" :placeholder="formatMessage('locMap.matnr', '商品编号')" /> |
| | | </div> |
| | | </div> |
| | | <div style="margin-top: 20px;"> |
| | | <div> |
| | | {{ formatMessage('locMap.maktx', '商品名称') }} |
| | | </div> |
| | | <div style="margin-top: 10px;"> |
| | | <a-input v-model:value="drawerOperMaktx" :placeholder="formatMessage('locMap.maktx', '商品名称')" /> |
| | | </div> |
| | | </div> |
| | | <div style="margin-top: 20px;"> |
| | | <a-button type="primary" @click="drawerOper = true">搜索</a-button> |
| | | </div> |
| | | |
| | | <div style="margin-top: 50px;"> |
| | | <div v-for="(lev, index) in levList" :key="index" style="margin-top: 10px;"> |
| | | <a-button :type="currentLev == lev ? 'primary' : 'dashed'" @click="switchLev(lev)" |
| | | style="width: 100%;">{{ lev }}F</a-button> |
| | | </div> |
| | | </div> |
| | | </a-drawer> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <style> |
| | | * { |
| | | margin: 0; |
| | | padding: 0; |
| | | } |
| | | |
| | | .pointContainer { |
| | | display: flex; |
| | | justify-content: center; |
| | | /*margin-top: 1px;*/ |
| | | } |
| | | |
| | | .pointBox { |
| | | background: #bababa; |
| | | width: 40px; |
| | | height: 40px; |
| | | /*margin-right: 1px;*/ |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | font-size: 14px; |
| | | user-select: none; |
| | | color: #fff; |
| | | } |
| | | |
| | | .pointBox:hover { |
| | | background: #00ff7f; |
| | | } |
| | | |
| | | .pointBoxEmpty { |
| | | background: #c2c934; |
| | | } |
| | | |
| | | .pointBoxOut { |
| | | background: #f1aa19; |
| | | } |
| | | |
| | | .pointBoxOutYy { |
| | | background: #618593; |
| | | } |
| | | |
| | | .pointBoxInYy { |
| | | background: #fa736f; |
| | | } |
| | | |
| | | .pointBoxGreen { |
| | | background: #00ff7f; |
| | | } |
| | | |
| | | .pointBoxBlue { |
| | | background: #55aaff; |
| | | } |
| | | |
| | | .pointBoxRed { |
| | | background: #ff0000; |
| | | } |
| | | |
| | | .pointBoxStart { |
| | | background: #ffaa00; |
| | | } |
| | | |
| | | .pointBoxEnd { |
| | | background: #ff55ff; |
| | | } |
| | | |
| | | .pointBoxStation { |
| | | background: #ffff00; |
| | | } |
| | | |
| | | .chargeStation { |
| | | background: #ffaa7f; |
| | | } |
| | | |
| | | .pointBoxDefault { |
| | | background: #86779d; |
| | | } |
| | | |
| | | .pointBoxSelected { |
| | | background: #00ff7f !important; |
| | | } |
| | | |
| | | .pointBoxSearch { |
| | | background: #9900ff; |
| | | } |
| | | |
| | | .crnLine { |
| | | width: auto; |
| | | height: 2px; |
| | | margin: 10px 0; |
| | | background: #000; |
| | | position: relative; |
| | | } |
| | | |
| | | .popBox { |
| | | position: absolute; |
| | | } |
| | | |
| | | /*卡片样式start*/ |
| | | .apple-card { |
| | | width: 190px; |
| | | height: 254px; |
| | | margin: 0 auto; |
| | | background-color: #011522; |
| | | border-radius: 8px; |
| | | z-index: 1; |
| | | animation: fadeInOut 0.5s 1; |
| | | } |
| | | |
| | | .apple-card .tools { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 9px; |
| | | } |
| | | |
| | | .apple-card .circle { |
| | | padding: 0 4px; |
| | | } |
| | | |
| | | .apple-card .box { |
| | | display: inline-block; |
| | | align-items: center; |
| | | width: 10px; |
| | | height: 10px; |
| | | padding: 1px; |
| | | border-radius: 50%; |
| | | } |
| | | |
| | | .apple-card .red { |
| | | background-color: #ff605c; |
| | | position: relative; |
| | | } |
| | | |
| | | .apple-card .red:hover { |
| | | background-color: #ff0300; |
| | | } |
| | | |
| | | .apple-card .red:hover::before { |
| | | content: "x"; |
| | | font-size: 11px; |
| | | color: #fff; |
| | | width: 10px; |
| | | height: 10px; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | position: absolute; |
| | | animation: fadeInOut 0.5s 1; |
| | | } |
| | | |
| | | .apple-card .yellow { |
| | | background-color: #ffbd44; |
| | | } |
| | | |
| | | .apple-card .green { |
| | | background-color: #00ca4e; |
| | | } |
| | | |
| | | .apple-card .card-content { |
| | | color: #fff; |
| | | padding: 10px; |
| | | } |
| | | |
| | | /*卡片样式end*/ |
| | | |
| | | /*滑动卡片start*/ |
| | | .hoverCard { |
| | | width: 150px; |
| | | height: 224px; |
| | | border-radius: 20px; |
| | | background: #f5f5f5; |
| | | position: relative; |
| | | padding: 1.8rem; |
| | | border: 2px solid #c3c6ce; |
| | | transition: 0.5s ease-out; |
| | | overflow: visible; |
| | | margin-top: 30px; |
| | | } |
| | | |
| | | .hoverCard .card-details { |
| | | color: black; |
| | | height: 100%; |
| | | gap: .5em; |
| | | display: grid; |
| | | place-content: center; |
| | | } |
| | | |
| | | .hoverCard .card-button { |
| | | transform: translate(-50%, 125%); |
| | | width: 60%; |
| | | border-radius: 1rem; |
| | | border: none; |
| | | background-color: #008bf8; |
| | | color: #fff; |
| | | font-size: 1rem; |
| | | padding: .5rem 1rem; |
| | | position: absolute; |
| | | left: 50%; |
| | | bottom: 0; |
| | | opacity: 0; |
| | | transition: 0.3s ease-out; |
| | | } |
| | | |
| | | .hoverCard .text-body { |
| | | color: rgb(134, 134, 134); |
| | | } |
| | | |
| | | /*Text*/ |
| | | .hoverCard .text-title { |
| | | font-size: 1.5em; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | /*Hover*/ |
| | | .hoverCard:hover { |
| | | border-color: #008bf8; |
| | | box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.25); |
| | | } |
| | | |
| | | .hoverCard:hover .card-button { |
| | | transform: translate(-50%, 50%); |
| | | opacity: 1; |
| | | } |
| | | |
| | | /*滑动卡片end*/ |
| | | |
| | | /*楼层控制start*/ |
| | | .floorSelect { |
| | | --text: #414856; |
| | | --radio: #7C96B2; |
| | | --radio-checked: #4F29F0; |
| | | --radio-size: 20px; |
| | | --width: 150px; |
| | | --height: 200px; |
| | | --border-radius: 10px; |
| | | width: var(--width); |
| | | height: var(--height); |
| | | border-radius: var(--border-radius); |
| | | color: var(--text); |
| | | position: relative; |
| | | box-shadow: 0 10px 30px rgba(65, 72, 86, 0.05); |
| | | display: grid; |
| | | grid-template-columns: auto var(--radio-size); |
| | | align-items: center; |
| | | } |
| | | |
| | | .floorSelect label { |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .floorSelect input[type="radio"] { |
| | | -webkit-appearance: none; |
| | | -moz-appearance: none; |
| | | position: relative; |
| | | height: var(--radio-size); |
| | | width: var(--radio-size); |
| | | outline: none; |
| | | margin: 0; |
| | | cursor: pointer; |
| | | border: 2px solid var(--radio); |
| | | background: transparent; |
| | | border-radius: 50%; |
| | | display: grid; |
| | | justify-self: end; |
| | | justify-items: center; |
| | | align-items: center; |
| | | overflow: hidden; |
| | | transition: border .5s ease; |
| | | } |
| | | |
| | | .floorSelect input[type="radio"]::before, |
| | | .floorSelect input[type="radio"]::after { |
| | | content: ""; |
| | | display: flex; |
| | | justify-self: center; |
| | | border-radius: 50%; |
| | | } |
| | | |
| | | .floorSelect input[type="radio"]::before { |
| | | position: absolute; |
| | | width: 100%; |
| | | height: 100%; |
| | | z-index: 1; |
| | | opacity: var(--opacity, 1); |
| | | } |
| | | |
| | | .floorSelect input[type="radio"]::after { |
| | | position: relative; |
| | | width: calc(100% /2); |
| | | height: calc(100% /2); |
| | | background: var(--radio-checked); |
| | | top: var(--y, 100%); |
| | | transition: top 0.5s cubic-bezier(0.48, 1.97, 0.5, 0.63); |
| | | } |
| | | |
| | | .floorSelect input[type="radio"]:checked { |
| | | --radio: var(--radio-checked); |
| | | } |
| | | |
| | | .floorSelect input[type="radio"]:checked::after { |
| | | --y: 0%; |
| | | animation: stretch-animate .3s ease-out .17s; |
| | | } |
| | | |
| | | .floorSelect input[type="radio"]:checked::before { |
| | | --opacity: 0; |
| | | } |
| | | |
| | | .floorSelect input[type="radio"]:checked~input[type="radio"]::after { |
| | | --y: -100%; |
| | | } |
| | | |
| | | .floorSelect input[type="radio"]:not(:checked)::before { |
| | | --opacity: 1; |
| | | transition: opacity 0s linear .5s; |
| | | } |
| | | |
| | | @keyframes stretch-animate { |
| | | 0% { |
| | | transform: scale(1, 1); |
| | | } |
| | | |
| | | 28% { |
| | | transform: scale(1.15, 0.85); |
| | | } |
| | | |
| | | 50% { |
| | | transform: scale(0.9, 1.1); |
| | | } |
| | | |
| | | 100% { |
| | | transform: scale(1, 1); |
| | | } |
| | | } |
| | | |
| | | /*楼层控制end*/ |
| | | |
| | | /*搜索start*/ |
| | | .search-input { |
| | | line-height: 28px; |
| | | border: 2px solid transparent; |
| | | border-bottom-color: #777; |
| | | padding: .2rem 0; |
| | | outline: none; |
| | | background-color: transparent; |
| | | color: #0d0c22; |
| | | transition: .3s cubic-bezier(0.645, 0.045, 0.355, 1); |
| | | } |
| | | |
| | | .search-input:focus, |
| | | .search-input:hover { |
| | | outline: none; |
| | | padding: .2rem 1rem; |
| | | border-radius: 1rem; |
| | | border-color: #7a9cc6; |
| | | } |
| | | |
| | | .search-input::placeholder { |
| | | color: #777; |
| | | } |
| | | |
| | | .search-input:focus::placeholder { |
| | | opacity: 0; |
| | | transition: opacity .3s; |
| | | } |
| | | |
| | | /*搜索end*/ |
| | | |
| | | @keyframes fadeInOut { |
| | | 0% { |
| | | opacity: 0; |
| | | } |
| | | |
| | | 100% { |
| | | opacity: 1; |
| | | } |
| | | } |
| | | </style> |
| | | <script setup>
|
| | | import { ref, onMounted, watch } from 'vue';
|
| | | import { get, post, postBlob } from '@/utils/request.js'
|
| | | import { message, Modal } from 'ant-design-vue';
|
| | | import { logout } from '@/config.js';
|
| | | import { formatMessage } from '@/utils/localeUtils.js';
|
| | | import {
|
| | | CompressOutlined,
|
| | | } from "@ant-design/icons-vue";
|
| | | import * as PIXI from 'pixi.js'
|
| | |
|
| | | let width = 25;
|
| | | let height = 25;
|
| | | let pixiApp;
|
| | | let pixiStageMap = new Map();
|
| | | let objectsContainer;
|
| | | let map = []
|
| | |
|
| | | const pixiView = ref(null)
|
| | | const mapFps = ref(0)
|
| | | const currentLev = ref(1)
|
| | | const levList = ref([])
|
| | | const pointContainerWidth = ref(0)
|
| | | const drawer = ref(false)
|
| | | const drawerLocData = ref(null)
|
| | | const drawerLocDetls = ref([])
|
| | | const drawerLocDetlField = ref([])
|
| | |
|
| | | const drawerOper = ref(false)
|
| | | const drawerOperLocNo = ref(null)
|
| | | const drawerOperMatnr = ref(null)
|
| | | const drawerOperMaktx = ref(null)
|
| | |
|
| | | onMounted(() => {
|
| | | createMap();
|
| | | init(currentLev.value);
|
| | | })
|
| | |
|
| | | watch(drawer, (newVal, oldVal) => {
|
| | | if (!drawer.value) {
|
| | | var rectangle = pixiStageMap.get(drawerLocData.value.locNo)
|
| | | updateColor(rectangle, rectangle.originColor);//恢复颜色
|
| | | }
|
| | | })
|
| | |
|
| | | function switchLev(lev) {
|
| | | currentLev.value = lev;
|
| | | init(lev);
|
| | | }
|
| | |
|
| | | function init(lev) {
|
| | | get('/api/locMap/getData/' + lev + '/auth', {}).then(resp => {
|
| | | let result = resp.data;
|
| | | if (result.code == 200) {
|
| | | let tmp = JSON.parse(result.data);
|
| | | let tmpMap = []
|
| | | tmp.forEach((item, index) => {
|
| | | let data2 = []
|
| | | item.forEach((val, idx) => {
|
| | | val.searchStatus = false//搜索标记
|
| | | val.rectangle = null;
|
| | | data2.push(val)
|
| | | })
|
| | | pointContainerWidth.value = item.length * (40 + 1)
|
| | | tmpMap.push(data2)
|
| | | })
|
| | |
|
| | | createMapData(tmpMap);
|
| | | }
|
| | | })
|
| | |
|
| | | get('/api/locMap/getLev', {}).then(resp => {
|
| | | let result = resp.data;
|
| | | if (result.code == 200) {
|
| | | let tmp = result.data;
|
| | | levList.value = tmp;
|
| | | }
|
| | | })
|
| | | }
|
| | |
|
| | | function createMap() {
|
| | | //Create a Pixi Application
|
| | | pixiApp = new PIXI.Application({
|
| | | width: pixiView.value.offsetWidth,
|
| | | height: window.innerHeight * 0.75,
|
| | | backgroundColor: 0xF5F7F9FF,
|
| | | // resizeTo: window
|
| | | });
|
| | |
|
| | | //Add the canvas that Pixi automatically created for you to the HTML document
|
| | | pixiView.value.appendChild(pixiApp.view)
|
| | |
|
| | | // 创建一个容器来管理大批量的显示对象
|
| | | objectsContainer = new PIXI.Container();
|
| | |
|
| | | pixiApp.stage.addChild(objectsContainer);
|
| | |
|
| | | //*******************拖动画布*******************
|
| | | let stageOriginalPos;
|
| | | let mouseDownPoint;
|
| | | 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;
|
| | | }
|
| | | }
|
| | | );
|
| | |
|
| | | pixiApp.renderer.plugins.interaction.on(
|
| | | 'pointermove',
|
| | | (event) => {
|
| | | const globalPos = event.data.global;
|
| | |
|
| | | if (touchBlank) {
|
| | | // 拖拽画布
|
| | | const dx = globalPos.x - mouseDownPoint[0];
|
| | | const dy = globalPos.y - mouseDownPoint[1];
|
| | | pixiApp.stage.position.set(
|
| | | stageOriginalPos[0] + dx,
|
| | | stageOriginalPos[1] + dy
|
| | | );
|
| | | }
|
| | | }
|
| | | );
|
| | |
|
| | | pixiApp.renderer.plugins.interaction.on(
|
| | | 'pointerup',
|
| | | (event) => {
|
| | | touchBlank = false;
|
| | | }
|
| | | );
|
| | | //*******************拖动画布*******************
|
| | |
|
| | | //*******************缩放画布*******************
|
| | | pixiApp.view.addEventListener('wheel', (event) => {
|
| | | event.stopPropagation();
|
| | | event.preventDefault();
|
| | | // 因为画布是充满视窗的,所以clientX等于mouse point在renderer上的x坐标
|
| | | const globalPos = [event.clientX, event.clientY];
|
| | | const delta = event.deltaY;
|
| | | const oldZoom = pixiApp.stage.scale.x;
|
| | | let newZoom = oldZoom * 0.999 ** delta;
|
| | |
|
| | | // const oldStageMatrix = app.stage.localTransform.clone();
|
| | | // const oldStagePos = oldStageMatrix.applyInverse(pointerGlobalPos);
|
| | | const oldStagePos = globalPos;
|
| | | const dx = oldStagePos[0] * oldZoom - oldStagePos[0] * newZoom;
|
| | | const dy = oldStagePos[1] * oldZoom - oldStagePos[1] * newZoom;
|
| | |
|
| | | pixiApp.stage.setTransform(
|
| | | pixiApp.stage.position.x + dx,
|
| | | pixiApp.stage.position.y + dy,
|
| | | newZoom,
|
| | | newZoom,
|
| | | 0,
|
| | | 0,
|
| | | 0,
|
| | | 0,
|
| | | 0
|
| | | );
|
| | |
|
| | | });
|
| | | //*******************缩放画布*******************
|
| | |
|
| | | //*******************FPS*******************
|
| | | var g_Time = 0;
|
| | | pixiApp.ticker.add((delta) => {
|
| | | var timeNow = (new Date()).getTime();
|
| | | var timeDiff = timeNow - g_Time;
|
| | | g_Time = timeNow;
|
| | | var fps = 1000 / timeDiff;
|
| | | mapFps.value = parseInt(fps)
|
| | | });
|
| | | //*******************FPS*******************
|
| | | }
|
| | |
|
| | | function createMapData(map) {
|
| | | objectsContainer.removeChildren();
|
| | | map.forEach((item, index) => {
|
| | | for (let idx = 0; idx < item.length; idx++) {
|
| | | let val = item[idx]
|
| | | if (val.value < 0) {
|
| | | continue;
|
| | | }
|
| | | let rectangle = getContainer(val.value, idx * width, index * height, map[index][idx].locSts);
|
| | | rectangle.on('click', (e) => {
|
| | | openLocDrawer(index, idx, map[index][idx], { x: e.data.originalEvent.offsetX, y: e.data.originalEvent.offsetY })
|
| | | updateColor(rectangle, 0x9900ff);
|
| | | });
|
| | | rectangle.locX = index;
|
| | | rectangle.locY = idx;
|
| | |
|
| | | pixiStageMap.set(map[index][idx].locNo, rectangle);
|
| | | objectsContainer.addChild(rectangle);
|
| | | }
|
| | | })
|
| | |
|
| | | //视角居中
|
| | | let containerWidth = (pixiApp.view.width - objectsContainer.width) / 2;
|
| | | let containerHeight = (pixiApp.view.height - objectsContainer.height) / 2;
|
| | | pixiApp.stage.position.set(containerWidth, containerHeight);
|
| | | }
|
| | |
|
| | | function containerAppViewCenter() {
|
| | | //视角居中
|
| | | let containerWidth = (pixiApp.view.width - objectsContainer.width) / 2;
|
| | | let containerHeight = (pixiApp.view.height - objectsContainer.height) / 2;
|
| | | pixiApp.stage.position.set(containerWidth, containerHeight);
|
| | | }
|
| | |
|
| | | function openLocDrawer(x, y, loc, e) {
|
| | | drawer.value = true;
|
| | | drawerLocData.value = loc;
|
| | |
|
| | | get('/api/locDetl/locNo/' + loc.locNo, {}).then(resp => {
|
| | | let result = resp.data;
|
| | | if (result.code == 200) {
|
| | | drawerLocDetls.value = result.data;
|
| | | }
|
| | | })
|
| | | }
|
| | |
|
| | | function getContainer(value, x, y, locSts) {
|
| | | let rectangle = new PIXI.Graphics();
|
| | | if (value === 0) {
|
| | | if (locSts === "F") {
|
| | | rectangle.beginFill(0xff0000);
|
| | | rectangle.originColor = 0xff0000;
|
| | | } else if (locSts === "O") {
|
| | | rectangle.beginFill(0x55aaff);
|
| | | rectangle.originColor = 0x55aaff;
|
| | | } else if (locSts === "D") {
|
| | | rectangle.beginFill(0xc2c934);
|
| | | rectangle.originColor = 0xc2c934;
|
| | | } else if (locSts === "P") {
|
| | | rectangle.beginFill(0xf1aa19);
|
| | | rectangle.originColor = 0xf1aa19;
|
| | | } else if (locSts === "R") {
|
| | | rectangle.beginFill(0x618593);
|
| | | rectangle.originColor = 0x618593;
|
| | | } else if (locSts === "S") {
|
| | | rectangle.beginFill(0xfa736f);
|
| | | rectangle.originColor = 0xfa736f;
|
| | | } else {
|
| | | rectangle.beginFill(0x86779d);
|
| | | rectangle.originColor = 0x86779d;
|
| | | }
|
| | | } else if (value === 3) {//母轨道
|
| | | rectangle.beginFill(0x00ff7f);
|
| | | rectangle.originColor = 0x00ff7f;
|
| | | rectangle.visible = false;
|
| | | } else if (value === 4) {//站点
|
| | | rectangle.beginFill(0xffff00);
|
| | | rectangle.originColor = 0xffff00;
|
| | | rectangle.visible = false;
|
| | | } else if (value === 5) {//充电桩
|
| | | rectangle.beginFill(0xffaa7f);
|
| | | rectangle.originColor = 0xffaa7f;
|
| | | rectangle.visible = false;
|
| | | } else if (value === 9) {//轨迹
|
| | | rectangle.beginFill(0xff0000);
|
| | | rectangle.originColor = 0xff0000;
|
| | | }
|
| | | // rectangle.lineStyle(1, 0xffffff, 1);
|
| | | rectangle.drawRect(0, 0, width, height);
|
| | | rectangle.x = x;
|
| | | rectangle.y = y;
|
| | | // 设置是否可以交互
|
| | | rectangle.interactive = true;
|
| | | rectangle.endFill();
|
| | |
|
| | | // 创建文本对象
|
| | | const style = new PIXI.TextStyle({
|
| | | fontSize: 14 * window.devicePixelRatio,
|
| | | fill: 'white',
|
| | | align: 'center', // 设置文本水平居中对齐
|
| | | verticalAlign: 'middle' // 设置文本垂直居中对齐
|
| | | });
|
| | | const text = new PIXI.Text(locSts, style);
|
| | | text.anchor.set(0.5); // 设置文本锚点为中心点
|
| | | text.position.set(rectangle.width / 2, rectangle.height / 2); // 将文本位置设置为Graphics对象的中心点
|
| | | // 将文本对象添加到Graphics对象中
|
| | | rectangle.addChild(text);
|
| | |
|
| | | return rectangle;
|
| | | }
|
| | |
|
| | | /**
|
| | | * 更新颜色
|
| | | */
|
| | | function updateColor(rectangle, color) {
|
| | | rectangle.clear()
|
| | | rectangle.beginFill(color);
|
| | | rectangle.drawRect(0, 0, width, height);
|
| | | }
|
| | |
|
| | | </script>
|
| | |
|
| | | <script>
|
| | | export default {
|
| | | name: '库位地图'
|
| | | }
|
| | | </script>
|
| | |
|
| | | <template>
|
| | | <div style="position: relative;overflow: hidden;">
|
| | | <div ref="pixiView">
|
| | |
|
| | | </div>
|
| | |
|
| | | <!--输出操作和FPS-->
|
| | | <div style="position: absolute;top: 0px;right: 10px;user-select: none;">
|
| | | <div>FPS:{{ mapFps }}</div>
|
| | | <a-button @click="drawerOper = true">操作</a-button>
|
| | | </div>
|
| | |
|
| | | <!--输出操作和FPS-->
|
| | | <div style="position: absolute;bottom: 20px;left: 20px;user-select: none;">
|
| | | <a-button type="dashed" @click="containerAppViewCenter">
|
| | | <CompressOutlined />
|
| | | </a-button>
|
| | | </div>
|
| | |
|
| | | <div>
|
| | | <a-drawer v-model:open="drawer" placement="right" style="background: #f3f3f3;">
|
| | | <div style="margin-top: 10px;">
|
| | | <a-tag>{{ formatMessage('locMap.locNo', '库位号') }}</a-tag>{{ drawerLocData.locNo }}
|
| | | </div>
|
| | | <div style="margin-top: 20px;">
|
| | | <a-tag>{{ formatMessage('locMap.locSts', '库位状态') }}</a-tag>{{ drawerLocData.locSts }}.{{
|
| | | drawerLocData.locSts$ }}
|
| | | </div>
|
| | | <div style="margin-top: 20px;">
|
| | | <div v-for="(item, index) in drawerLocDetls" :key="index" style="margin-top: 20px;">
|
| | | <a-card :title="item.matnr" :bordered="false" style="width: 300px">
|
| | | <div>{{ formatMessage('locMap.batch', '批号') }}:{{ item.batch }}</div>
|
| | | <div>{{ formatMessage('locMap.orderNo', '单据编号') }}:{{ item.orderNo }}</div>
|
| | | <div>{{ formatMessage('locMap.anfme', '数量') }}:{{ item.anfme }}</div>
|
| | | <div v-for="(field, index) in item.dynamicFieldsList" :key="index">{{
|
| | | formatMessage('locMap.' + field.key, field.desc) }}:{{ field.value }}</div>
|
| | | </a-card>
|
| | | </div>
|
| | | </div>
|
| | | </a-drawer>
|
| | | </div>
|
| | |
|
| | | <div>
|
| | | <a-drawer v-model:open="drawerOper" placement="right">
|
| | | <div style="margin-top: 10px;">
|
| | | <div>
|
| | | {{ formatMessage('locMap.locNo', '库位号') }}
|
| | | </div>
|
| | | <div style="margin-top: 10px;">
|
| | | <a-input v-model:value="drawerOperLocNo" :placeholder="formatMessage('locMap.locNo', '库位号')" />
|
| | | </div>
|
| | | </div>
|
| | | <div style="margin-top: 20px;">
|
| | | <div>
|
| | | {{ formatMessage('locMap.matnr', '商品编号') }}
|
| | | </div>
|
| | | <div style="margin-top: 10px;">
|
| | | <a-input v-model:value="drawerOperMatnr" :placeholder="formatMessage('locMap.matnr', '商品编号')" />
|
| | | </div>
|
| | | </div>
|
| | | <div style="margin-top: 20px;">
|
| | | <div>
|
| | | {{ formatMessage('locMap.maktx', '商品名称') }}
|
| | | </div>
|
| | | <div style="margin-top: 10px;">
|
| | | <a-input v-model:value="drawerOperMaktx" :placeholder="formatMessage('locMap.maktx', '商品名称')" />
|
| | | </div>
|
| | | </div>
|
| | | <div style="margin-top: 20px;">
|
| | | <a-button type="primary" @click="drawerOper = true">搜索</a-button>
|
| | | </div>
|
| | |
|
| | | <div style="margin-top: 50px;">
|
| | | <div v-for="(lev, index) in levList" :key="index" style="margin-top: 10px;">
|
| | | <a-button :type="currentLev == lev ? 'primary' : 'dashed'" @click="switchLev(lev)"
|
| | | style="width: 100%;">{{ lev }}F</a-button>
|
| | | </div>
|
| | | </div>
|
| | | </a-drawer>
|
| | | </div>
|
| | | </div>
|
| | | </template>
|
| | |
|
| | | <style>
|
| | | * {
|
| | | margin: 0;
|
| | | padding: 0;
|
| | | }
|
| | |
|
| | | .pointContainer {
|
| | | display: flex;
|
| | | justify-content: center;
|
| | | /*margin-top: 1px;*/
|
| | | }
|
| | |
|
| | | .pointBox {
|
| | | background: #bababa;
|
| | | width: 40px;
|
| | | height: 40px;
|
| | | /*margin-right: 1px;*/
|
| | | display: flex;
|
| | | justify-content: center;
|
| | | align-items: center;
|
| | | font-size: 14px;
|
| | | user-select: none;
|
| | | color: #fff;
|
| | | }
|
| | |
|
| | | .pointBox:hover {
|
| | | background: #00ff7f;
|
| | | }
|
| | |
|
| | | .pointBoxEmpty {
|
| | | background: #c2c934;
|
| | | }
|
| | |
|
| | | .pointBoxOut {
|
| | | background: #f1aa19;
|
| | | }
|
| | |
|
| | | .pointBoxOutYy {
|
| | | background: #618593;
|
| | | }
|
| | |
|
| | | .pointBoxInYy {
|
| | | background: #fa736f;
|
| | | }
|
| | |
|
| | | .pointBoxGreen {
|
| | | background: #00ff7f;
|
| | | }
|
| | |
|
| | | .pointBoxBlue {
|
| | | background: #55aaff;
|
| | | }
|
| | |
|
| | | .pointBoxRed {
|
| | | background: #ff0000;
|
| | | }
|
| | |
|
| | | .pointBoxStart {
|
| | | background: #ffaa00;
|
| | | }
|
| | |
|
| | | .pointBoxEnd {
|
| | | background: #ff55ff;
|
| | | }
|
| | |
|
| | | .pointBoxStation {
|
| | | background: #ffff00;
|
| | | }
|
| | |
|
| | | .chargeStation {
|
| | | background: #ffaa7f;
|
| | | }
|
| | |
|
| | | .pointBoxDefault {
|
| | | background: #86779d;
|
| | | }
|
| | |
|
| | | .pointBoxSelected {
|
| | | background: #00ff7f !important;
|
| | | }
|
| | |
|
| | | .pointBoxSearch {
|
| | | background: #9900ff;
|
| | | }
|
| | |
|
| | | .crnLine {
|
| | | width: auto;
|
| | | height: 2px;
|
| | | margin: 10px 0;
|
| | | background: #000;
|
| | | position: relative;
|
| | | }
|
| | |
|
| | | .popBox {
|
| | | position: absolute;
|
| | | }
|
| | |
|
| | | /*卡片样式start*/
|
| | | .apple-card {
|
| | | width: 190px;
|
| | | height: 254px;
|
| | | margin: 0 auto;
|
| | | background-color: #011522;
|
| | | border-radius: 8px;
|
| | | z-index: 1;
|
| | | animation: fadeInOut 0.5s 1;
|
| | | }
|
| | |
|
| | | .apple-card .tools {
|
| | | display: flex;
|
| | | align-items: center;
|
| | | padding: 9px;
|
| | | }
|
| | |
|
| | | .apple-card .circle {
|
| | | padding: 0 4px;
|
| | | }
|
| | |
|
| | | .apple-card .box {
|
| | | display: inline-block;
|
| | | align-items: center;
|
| | | width: 10px;
|
| | | height: 10px;
|
| | | padding: 1px;
|
| | | border-radius: 50%;
|
| | | }
|
| | |
|
| | | .apple-card .red {
|
| | | background-color: #ff605c;
|
| | | position: relative;
|
| | | }
|
| | |
|
| | | .apple-card .red:hover {
|
| | | background-color: #ff0300;
|
| | | }
|
| | |
|
| | | .apple-card .red:hover::before {
|
| | | content: "x";
|
| | | font-size: 11px;
|
| | | color: #fff;
|
| | | width: 10px;
|
| | | height: 10px;
|
| | | display: flex;
|
| | | justify-content: center;
|
| | | align-items: center;
|
| | | position: absolute;
|
| | | animation: fadeInOut 0.5s 1;
|
| | | }
|
| | |
|
| | | .apple-card .yellow {
|
| | | background-color: #ffbd44;
|
| | | }
|
| | |
|
| | | .apple-card .green {
|
| | | background-color: #00ca4e;
|
| | | }
|
| | |
|
| | | .apple-card .card-content {
|
| | | color: #fff;
|
| | | padding: 10px;
|
| | | }
|
| | |
|
| | | /*卡片样式end*/
|
| | |
|
| | | /*滑动卡片start*/
|
| | | .hoverCard {
|
| | | width: 150px;
|
| | | height: 224px;
|
| | | border-radius: 20px;
|
| | | background: #f5f5f5;
|
| | | position: relative;
|
| | | padding: 1.8rem;
|
| | | border: 2px solid #c3c6ce;
|
| | | transition: 0.5s ease-out;
|
| | | overflow: visible;
|
| | | margin-top: 30px;
|
| | | }
|
| | |
|
| | | .hoverCard .card-details {
|
| | | color: black;
|
| | | height: 100%;
|
| | | gap: .5em;
|
| | | display: grid;
|
| | | place-content: center;
|
| | | }
|
| | |
|
| | | .hoverCard .card-button {
|
| | | transform: translate(-50%, 125%);
|
| | | width: 60%;
|
| | | border-radius: 1rem;
|
| | | border: none;
|
| | | background-color: #008bf8;
|
| | | color: #fff;
|
| | | font-size: 1rem;
|
| | | padding: .5rem 1rem;
|
| | | position: absolute;
|
| | | left: 50%;
|
| | | bottom: 0;
|
| | | opacity: 0;
|
| | | transition: 0.3s ease-out;
|
| | | }
|
| | |
|
| | | .hoverCard .text-body {
|
| | | color: rgb(134, 134, 134);
|
| | | }
|
| | |
|
| | | /*Text*/
|
| | | .hoverCard .text-title {
|
| | | font-size: 1.5em;
|
| | | font-weight: bold;
|
| | | }
|
| | |
|
| | | /*Hover*/
|
| | | .hoverCard:hover {
|
| | | border-color: #008bf8;
|
| | | box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.25);
|
| | | }
|
| | |
|
| | | .hoverCard:hover .card-button {
|
| | | transform: translate(-50%, 50%);
|
| | | opacity: 1;
|
| | | }
|
| | |
|
| | | /*滑动卡片end*/
|
| | |
|
| | | /*楼层控制start*/
|
| | | .floorSelect {
|
| | | --text: #414856;
|
| | | --radio: #7C96B2;
|
| | | --radio-checked: #4F29F0;
|
| | | --radio-size: 20px;
|
| | | --width: 150px;
|
| | | --height: 200px;
|
| | | --border-radius: 10px;
|
| | | width: var(--width);
|
| | | height: var(--height);
|
| | | border-radius: var(--border-radius);
|
| | | color: var(--text);
|
| | | position: relative;
|
| | | box-shadow: 0 10px 30px rgba(65, 72, 86, 0.05);
|
| | | display: grid;
|
| | | grid-template-columns: auto var(--radio-size);
|
| | | align-items: center;
|
| | | }
|
| | |
|
| | | .floorSelect label {
|
| | | cursor: pointer;
|
| | | }
|
| | |
|
| | | .floorSelect input[type="radio"] {
|
| | | -webkit-appearance: none;
|
| | | -moz-appearance: none;
|
| | | position: relative;
|
| | | height: var(--radio-size);
|
| | | width: var(--radio-size);
|
| | | outline: none;
|
| | | margin: 0;
|
| | | cursor: pointer;
|
| | | border: 2px solid var(--radio);
|
| | | background: transparent;
|
| | | border-radius: 50%;
|
| | | display: grid;
|
| | | justify-self: end;
|
| | | justify-items: center;
|
| | | align-items: center;
|
| | | overflow: hidden;
|
| | | transition: border .5s ease;
|
| | | }
|
| | |
|
| | | .floorSelect input[type="radio"]::before,
|
| | | .floorSelect input[type="radio"]::after {
|
| | | content: "";
|
| | | display: flex;
|
| | | justify-self: center;
|
| | | border-radius: 50%;
|
| | | }
|
| | |
|
| | | .floorSelect input[type="radio"]::before {
|
| | | position: absolute;
|
| | | width: 100%;
|
| | | height: 100%;
|
| | | z-index: 1;
|
| | | opacity: var(--opacity, 1);
|
| | | }
|
| | |
|
| | | .floorSelect input[type="radio"]::after {
|
| | | position: relative;
|
| | | width: calc(100% /2);
|
| | | height: calc(100% /2);
|
| | | background: var(--radio-checked);
|
| | | top: var(--y, 100%);
|
| | | transition: top 0.5s cubic-bezier(0.48, 1.97, 0.5, 0.63);
|
| | | }
|
| | |
|
| | | .floorSelect input[type="radio"]:checked {
|
| | | --radio: var(--radio-checked);
|
| | | }
|
| | |
|
| | | .floorSelect input[type="radio"]:checked::after {
|
| | | --y: 0%;
|
| | | animation: stretch-animate .3s ease-out .17s;
|
| | | }
|
| | |
|
| | | .floorSelect input[type="radio"]:checked::before {
|
| | | --opacity: 0;
|
| | | }
|
| | |
|
| | | .floorSelect input[type="radio"]:checked~input[type="radio"]::after {
|
| | | --y: -100%;
|
| | | }
|
| | |
|
| | | .floorSelect input[type="radio"]:not(:checked)::before {
|
| | | --opacity: 1;
|
| | | transition: opacity 0s linear .5s;
|
| | | }
|
| | |
|
| | | @keyframes stretch-animate {
|
| | | 0% {
|
| | | transform: scale(1, 1);
|
| | | }
|
| | |
|
| | | 28% {
|
| | | transform: scale(1.15, 0.85);
|
| | | }
|
| | |
|
| | | 50% {
|
| | | transform: scale(0.9, 1.1);
|
| | | }
|
| | |
|
| | | 100% {
|
| | | transform: scale(1, 1);
|
| | | }
|
| | | }
|
| | |
|
| | | /*楼层控制end*/
|
| | |
|
| | | /*搜索start*/
|
| | | .search-input {
|
| | | line-height: 28px;
|
| | | border: 2px solid transparent;
|
| | | border-bottom-color: #777;
|
| | | padding: .2rem 0;
|
| | | outline: none;
|
| | | background-color: transparent;
|
| | | color: #0d0c22;
|
| | | transition: .3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
| | | }
|
| | |
|
| | | .search-input:focus,
|
| | | .search-input:hover {
|
| | | outline: none;
|
| | | padding: .2rem 1rem;
|
| | | border-radius: 1rem;
|
| | | border-color: #7a9cc6;
|
| | | }
|
| | |
|
| | | .search-input::placeholder {
|
| | | color: #777;
|
| | | }
|
| | |
|
| | | .search-input:focus::placeholder {
|
| | | opacity: 0;
|
| | | transition: opacity .3s;
|
| | | }
|
| | |
|
| | | /*搜索end*/
|
| | |
|
| | | @keyframes fadeInOut {
|
| | | 0% {
|
| | | opacity: 0;
|
| | | }
|
| | |
|
| | | 100% {
|
| | | opacity: 1;
|
| | | }
|
| | | }
|
| | | </style>
|