| | |
| | | v-model:opSceneType="opSceneType" @create-ok="onCreateSceneOk"></SceneCreateInfo> --> |
| | | <view class="map-content" v-show="mapOperationType !='scene_create'"> |
| | | <view class="content"> |
| | | <view class="fabric" :message="ctxDataStr" :change:message="ctx.receiveMsg" id="canvasMap"></view> |
| | | <view ref="canvasCtx" class="fabric" id="canvasMap" :message="ctxDataStr" |
| | | :change:message="ctx.receiveMsg"> |
| | | </view> |
| | | <view class="loading-overlay" v-if="bgLoading"> |
| | | <view class="loading-content"> |
| | | <view class="loading-spinner"></view> |
| | |
| | | </view> |
| | | </template> |
| | | <script src="./js/ctx.js" module="ctx" lang="renderjs"></script> |
| | | |
| | | |
| | | <script> |
| | | import { |
| | | ref |
| | | } from "vue"; |
| | | import { |
| | | showToast, |
| | | showModal, |
| | | session, |
| | | showError, |
| | | showInfo |
| | | } from "@/comm/utils.js" |
| | | // import OIFabric from "@/components/oi-fabric/index.vue" |
| | | import { |
| | |
| | | list: [] |
| | | }, |
| | | sceneList: [], |
| | | |
| | | ctxDataStr: "[]", |
| | | mapOperationType: "", |
| | | mapOperationStatus: "", |
| | |
| | | img_x: 1, |
| | | img_y: 1 |
| | | }, |
| | | positioningAgv: false, |
| | | isPageVisible: true, |
| | | destroyFlag: false, |
| | | } |
| | | }, |
| | | computed: { |
| | |
| | | }, |
| | | onHide() { |
| | | this.isPageVisible = false |
| | | |
| | | }, |
| | | onUnload() { |
| | | async onUnload() { |
| | | |
| | | this.isPageVisible = false |
| | | |
| | | console.log("onUnload") |
| | | |
| | | |
| | | }, |
| | | onBackPress() { |
| | | this.isPageVisible = false |
| | | if (this.destroyFlag) |
| | | return false |
| | | else { |
| | | this.ctxDataStr = JSON.stringify([{ |
| | | method: "destroy", |
| | | }]) |
| | | return true |
| | | } |
| | | |
| | | }, |
| | | |
| | | methods: { |
| | | setData(obj) { |
| | | let that = this; |
| | |
| | | this.setData({ |
| | | unlinked: true |
| | | }) |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } |
| | | }, |
| | | |
| | | clickShowMenu() { |
| | | // const list = [...this.sceneList] |
| | | // for(let i =0; i < 20;i++){ |
| | | // list.push("test" + i) |
| | | // } |
| | | this.menuPopup = { |
| | | type: "scene", |
| | | list: this.sceneList, |
| | |
| | | |
| | | showModal("已记录的路径将会被删除。", "是否要退出示教?").then(async (res) => { |
| | | if (res) { |
| | | if (mapOperationStatus == 'end' || mapOperationStatus == 'save') { |
| | | if (this.mapOperationStatus == 'end' || this.mapOperationStatus == |
| | | 'save') { |
| | | try { |
| | | await delTeachingMode(this.vehicleIp, [_this.teachingModeCur]) |
| | | await delTeachingMode(this.vehicleIp, [this.teachingModeCur]) |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } |
| | | |
| | | } |
| | |
| | | // this.mapOperationType = 'scene_create' |
| | | // this.opSceneType = 'add_name' |
| | | uni.navigateTo({ |
| | | url: `/pages/map/scene?ip=${this.vehicleIp}`, |
| | | url: `/pages/map/scene?ip=${_this.vehicleIp}`, |
| | | events: { |
| | | // 为指定事件添加一个监听器,获取被打开页面传送到当前页面的数据 |
| | | create_finish: function(data) { |
| | |
| | | const info = await getAgvState(this.vehicleIp) |
| | | return info |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | return {} |
| | | } |
| | | }, |
| | |
| | | const paths = await getCurrentTeachingData(this.vehicleIp) || [] |
| | | return paths |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | return [] |
| | | } |
| | | }, |
| | |
| | | const info = await stations(this.vehicleIp) |
| | | return info.station_list || [] |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | return [] |
| | | } |
| | | }, |
| | |
| | | const info = await getMapUrl(this.vehicleIp, id) |
| | | return info |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | return {} |
| | | } |
| | | }, |
| | |
| | | try { |
| | | this.setData({ |
| | | bgProgressPercent: 0, |
| | | bgLoading: true |
| | | }) |
| | | const infoAgv = await this.loadAgvState() |
| | | const stationLst = await this.loadStations() |
| | | |
| | | const infoMap = await this.loadMapInfo(id) |
| | | this.curMapInfo = { |
| | | proportion: infoMap.proportion || 1, |
| | | img_proportion: infoMap.img_proportion || 1, |
| | | max_x: infoMap.max_x || 1, |
| | | max_y: infoMap.max_y || 1, |
| | | min_x: infoMap.min_x || 0, |
| | | min_y: infoMap.min_y || 0, |
| | | img_x: infoMap.img_x || 1, |
| | | img_y: infoMap.img_y || 1 |
| | | proportion: parseInt(infoMap.proportion) || 1, |
| | | img_proportion: parseInt(infoMap.img_proportion) || 1, |
| | | max_x: parseInt(infoMap.max_x) || 1, |
| | | max_y: parseInt(infoMap.max_y) || 1, |
| | | min_x: parseInt(infoMap.min_x) || 0, |
| | | min_y: parseInt(infoMap.min_y) || 0, |
| | | img_x: parseInt(infoMap.img_x) || 1, |
| | | img_y: parseInt(infoMap.img_y) || 1 |
| | | } |
| | | // getApp().globalData.curScene = infoMap |
| | | this.setData({ |
| | |
| | | }, |
| | | { |
| | | method: "public_teaching_path", |
| | | param: this.teachingMode.Public || [] |
| | | param: { |
| | | list: this.teachingMode.Public || [], |
| | | show: this.showTeachingPathFlag |
| | | } |
| | | |
| | | }, |
| | | { |
| | | method: "station_teaching_path", |
| | | param: this.teachingMode.Stations || [] |
| | | param: { |
| | | list: this.teachingMode.Stations || [], |
| | | show: this.showTeachingPathFlag |
| | | } |
| | | |
| | | }, |
| | | { |
| | | method: "move_canvas", |
| | | method: "move_pt_center", |
| | | param: { |
| | | x: infoAgv.x, |
| | | y: infoAgv.y |
| | | } |
| | | } |
| | | ]) |
| | | this.positioningAgv = true |
| | | this.mapShowAgv = true |
| | | } catch (ex) { |
| | | this.setData({ |
| | | bgProgressPercent: 0, |
| | | bgLoading: false |
| | | }) |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } |
| | | }, |
| | | |
| | | receiveRenderData(param) { |
| | | console.log('接收到视图层的数据:', param); |
| | | if (param.method == "set_backgroud_progress") { |
| | | if (param.method === "destroy_complete") { |
| | | if (param.param) { |
| | | this.destroyFlag = true |
| | | uni.navigateBack({ |
| | | delta: 1 |
| | | }) |
| | | } |
| | | } else if (param.method == "set_backgroud_progress") { |
| | | if (param.type == "start") { |
| | | this.setData({ |
| | | bgProgressPercent: 50, |
| | |
| | | } |
| | | |
| | | this.$refs.refPopupOperateTeaching.open("bottom") |
| | | } else if (param.method == "cancel_positioning_agv") { |
| | | this.positioningAgv = false |
| | | } else if (param.method == "show_log") { |
| | | const listLog = session.getValue("request_log") || [] |
| | | listLog.unshift(param.data) |
| | | session.setValue("request_log", listLog) |
| | | } |
| | | |
| | | |
| | | }, |
| | | clickMapStation() { |
| | |
| | | this.mapOperationType = "edit_map" |
| | | }, |
| | | clickExtendMap() { |
| | | const _this = this |
| | | uni.navigateTo({ |
| | | url: `/pages/map/scene?ip=${this.vehicleIp}&opType=extend&sceneId=${this.sceneId}`, |
| | | events: { |
| | |
| | | mode: "Public", |
| | | main_road: 1, |
| | | } |
| | | this.positioningAgv = true |
| | | this.mapOperationType = "public_teaching" |
| | | this.ctxDataStr = JSON.stringify([{ |
| | | method: "set_selectable", |
| | | param: false, |
| | | }, { |
| | | method: "public_teaching", |
| | | }]) |
| | | |
| | |
| | | }, |
| | | async loadSceneList() { |
| | | try { |
| | | |
| | | uni.showLoading({ |
| | | title: "加载场景中" |
| | | }) |
| | |
| | | } |
| | | |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } finally { |
| | | uni.hideLoading() |
| | | } |
| | |
| | | if (this.sceneList.length > 0) |
| | | this.changeMap(this.sceneList[0]) |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } finally { |
| | | uni.hideLoading() |
| | | } |
| | |
| | | param: [this.regionEdit] |
| | | }, |
| | | { |
| | | method: "move_canvas", |
| | | method: "move_pt_center", |
| | | param: { |
| | | x: agv.x, |
| | | y: agv.y |
| | |
| | | ]) |
| | | } |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } |
| | | }, |
| | | async clickPlanProhibitionRegion() { |
| | |
| | | param: [this.regionEdit] |
| | | }, |
| | | { |
| | | method: "move_canvas", |
| | | method: "move_pt_center", |
| | | param: { |
| | | x: agv.x, |
| | | y: agv.y |
| | |
| | | ]) |
| | | } |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } |
| | | }, |
| | | |
| | |
| | | param: [this.wallEdit] |
| | | }, |
| | | { |
| | | method: "move_canvas", |
| | | method: "move_pt_center", |
| | | param: { |
| | | x: agv.x, |
| | | y: agv.y |
| | |
| | | } |
| | | ]) |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } |
| | | |
| | | }, |
| | |
| | | showToast("场景重命名成功!") |
| | | } catch (ex) { |
| | | this.mapOperationType = "edit_scene_name" |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } finally { |
| | | uni.hideLoading() |
| | | this.loading = false |
| | |
| | | this.mapOperationType = "" |
| | | }, |
| | | clickStationDelete() { |
| | | const _this = this |
| | | showModal("该站点已绑定任务,删除站点后绑定的任务会停止并删除", "是否确认删除?").then((res) => { |
| | | if (res) { |
| | | this.stationDelete(this.stationEdit) |
| | | } |
| | | }) |
| | | this.stationDelete(this.stationEdit) |
| | | // const _this = this |
| | | // showModal("该站点已绑定任务,删除站点后绑定的任务会停止并删除", "是否确认删除?").then((res) => { |
| | | // if (res) { |
| | | |
| | | // } |
| | | // }) |
| | | }, |
| | | async stationAdd(item) { |
| | | try { |
| | |
| | | |
| | | |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } finally { |
| | | uni.hideLoading() |
| | | } |
| | |
| | | } |
| | | |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } finally { |
| | | uni.hideLoading() |
| | | } |
| | |
| | | this.stationAdd(this.stationEdit) |
| | | this.mapOperationType = "public_teaching" |
| | | this.mapOperationStatus = "teaching" |
| | | this.positioningAgv = true |
| | | return |
| | | } else if (this.mapOperationType == "edit_station") { |
| | | this.stationUpdate(this.stationEdit) |
| | |
| | | }]) |
| | | this.mapOperationType = '' |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } |
| | | }, |
| | | async stationDelete(item) { |
| | |
| | | }]) |
| | | |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } finally { |
| | | uni.hideLoading() |
| | | } |
| | |
| | | }, |
| | | async clickStationNameOK() { |
| | | try { |
| | | this.loading = true |
| | | |
| | | const name = this.stationEdit.name.trim() |
| | | if (!name) { |
| | | showToast("站点名称还未输入") |
| | | return |
| | | } |
| | | if (this.mapOperationType == 'add_station' || this |
| | | .mapOperationType == "teaching_add_station") { |
| | | this.loading = true |
| | | if (this.mapOperationType == 'add_station' || this.mapOperationType == "teaching_add_station") { |
| | | const agv = await this.loadAgvState() |
| | | this.stationEdit = { |
| | | stationID: this.getMaxStationNo + 1, |
| | |
| | | method: "edit_station_pos", |
| | | param: this.stationEdit |
| | | }, { |
| | | method: "move_canvas", |
| | | method: "move_pt_center", |
| | | param: { |
| | | x: agv.x, |
| | | y: agv.y |
| | |
| | | } |
| | | |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } finally { |
| | | this.loading = false |
| | | } |
| | |
| | | method: "update_station", |
| | | param: [this.stationEdit] |
| | | }, { |
| | | method: "move_canvas", |
| | | method: "move_pt_center", |
| | | param: { |
| | | x: this.stationEdit.x, |
| | | y: this.stationEdit.y |
| | |
| | | method: "update_station", |
| | | param: [this.stationEdit] |
| | | }, { |
| | | method: "move_canvas", |
| | | method: "move_pt_center", |
| | | param: { |
| | | x: this.stationEdit.x, |
| | | y: this.stationEdit.y |
| | |
| | | method: "update_station", |
| | | param: [this.stationEdit] |
| | | }, { |
| | | method: "move_canvas", |
| | | method: "move_pt_center", |
| | | param: { |
| | | x: this.stationEdit.x, |
| | | y: this.stationEdit.y |
| | |
| | | method: "update_station", |
| | | param: [this.stationEdit] |
| | | }, { |
| | | method: "move_canvas", |
| | | method: "move_pt_center", |
| | | param: { |
| | | x: this.stationEdit.x, |
| | | y: this.stationEdit.y |
| | |
| | | |
| | | async clickVehiclePosition() { |
| | | try { |
| | | this.positioningAgv = true |
| | | const infoAgv = await this.loadAgvState() |
| | | this.ctxDataStr = JSON.stringify([{ |
| | | method: "update_agv_state", |
| | | param: infoAgv |
| | | }, { |
| | | method: "move_canvas", |
| | | method: "move_pt_center", |
| | | param: { |
| | | x: infoAgv.x, |
| | | y: infoAgv.y |
| | | } |
| | | }]) |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } |
| | | }, |
| | | async clickPositionStation() { |
| | |
| | | param: [this.stationEdit] |
| | | }, |
| | | { |
| | | method: "move_canvas", |
| | | method: "move_pt_center", |
| | | param: { |
| | | x: infoAgv.x, |
| | | y: infoAgv.y |
| | |
| | | |
| | | ]) |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } |
| | | }, |
| | | async loadTeachingMode() { |
| | |
| | | } |
| | | |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | return { |
| | | Public: [], |
| | | Stations: [] |
| | | } |
| | | } |
| | | }, |
| | | async reloadTeachingMode() { |
| | | async reloadTeachingMode(finish) { |
| | | try { |
| | | this.teachingMode = await this.loadTeachingMode() |
| | | this.ctxDataStr = JSON.stringify([{ |
| | | |
| | | const list = [{ |
| | | method: "clear_teaching_path", |
| | | }, |
| | | { |
| | | method: "public_teaching_path", |
| | | param: this.teachingMode.Public || [] |
| | | param: { |
| | | list: this.teachingMode.Public || [], |
| | | show: this.showTeachingPathFlag |
| | | } |
| | | }, |
| | | { |
| | | method: "station_teaching_path", |
| | | param: this.teachingMode.Stations || [] |
| | | }, { |
| | | method: "show_teaching_path", |
| | | param: { |
| | | list: this.teachingMode.Stations || [], |
| | | show: this.showTeachingPathFlag |
| | | } |
| | | } |
| | | ]) |
| | | // , { |
| | | // method: "show_teaching_path", |
| | | // param: { |
| | | // show: this.showTeachingPathFlag |
| | | // } |
| | | // } |
| | | ] |
| | | if (finish) { |
| | | list.push({ |
| | | method: "teaching_finish", |
| | | }) |
| | | } |
| | | this.ctxDataStr = JSON.stringify(list) |
| | | |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | |
| | | } |
| | | }, |
| | |
| | | } |
| | | ]) |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } |
| | | }, |
| | | async refreshAgvPosition() { |
| | | try { |
| | | if (this.isPageVisible && this.mapShowAgv) { |
| | | const agv = await this.loadAgvState() |
| | | |
| | | const listCtrData = [{ |
| | | method: "update_agv_state", |
| | | param: agv |
| | |
| | | } |
| | | }) |
| | | } |
| | | if (this.positioningAgv) { |
| | | listCtrData.push({ |
| | | method: "move_pt_visible", |
| | | param: { |
| | | x: agv.x, |
| | | y: agv.y, |
| | | width: 80, |
| | | height: 80, |
| | | } |
| | | }) |
| | | |
| | | } |
| | | this.ctxDataStr = JSON.stringify(listCtrData) |
| | | } |
| | | setTimeout(this.refreshAgvPosition, 1000); |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex).then((res) => { |
| | | setTimeout(this.refreshAgvPosition, 1000); |
| | | }) |
| | | } finally { |
| | | // 无论成功失败,1 秒后再来 |
| | | setTimeout(this.refreshAgvPosition, 1000); |
| | | |
| | | } |
| | | |
| | | }, |
| | |
| | | uni.showLoading({ |
| | | title: "示教结束" |
| | | }) |
| | | await teachingModeFlag(this.vehicleIp, |
| | | teachingMode) |
| | | await teachingModeFlag(this.vehicleIp, teachingMode) |
| | | |
| | | this.mapOperationStatus = "end" |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } finally { |
| | | uni.hideLoading() |
| | | } |
| | |
| | | await _this.teachingStart("Public") |
| | | |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } finally { |
| | | this.loading = false |
| | | uni.hideLoading() |
| | |
| | | _this.mapOperationStatus = "" |
| | | |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } finally { |
| | | this.loading = false |
| | | } |
| | |
| | | |
| | | this.mapOperationStatus = "save" |
| | | try {} catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } |
| | | |
| | | }, |
| | | clickTeachingFinish() { |
| | | this.mapOperationType = "" |
| | | this.ctxDataStr = JSON.stringify([{ |
| | | method: "teaching_finish", |
| | | }]) |
| | | this.reloadTeachingMode() |
| | | |
| | | this.reloadTeachingMode(true) |
| | | |
| | | }, |
| | | async teachingStart(mode) { |
| | |
| | | } |
| | | |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } |
| | | |
| | | }, |
| | |
| | | .teachingModeCur |
| | | ) |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } finally { |
| | | this.loading = false |
| | | } |
| | | |
| | | }, |
| | | async onTeachingModeMainRoad( |
| | | val) { |
| | | async onTeachingModeMainRoad(val) { |
| | | try { |
| | | if (this |
| | | .mapOperationStatus |
| | | ) { |
| | | if (this.mapOperationStatus) { |
| | | |
| | | if (this |
| | | .teachingModeCur |
| | | .main_road == |
| | | val) { |
| | | if (this.teachingModeCur.main_road == val) { |
| | | return |
| | | } |
| | | this.teachingModeCur |
| | | .main_road = |
| | | val |
| | | this.teachingModeCur |
| | | .teaching_flag = |
| | | 1 |
| | | this.teachingModeCur |
| | | .mode = |
| | | "Public" |
| | | const res = |
| | | await teachingModeFlag( |
| | | this |
| | | .vehicleIp, |
| | | this |
| | | .teachingModeCur |
| | | ) |
| | | this.teachingModeCur.teaching_flag = 0 |
| | | const oldName = this.teachingModeCur.name |
| | | await teachingModeFlag(this.vehicleIp, this.teachingModeCur) |
| | | this.teachingModeCur.main_road = val |
| | | this.teachingModeCur.teaching_flag = 1 |
| | | this.teachingModeCur.mode = "Public" |
| | | this.teachingModeCur.name = "" |
| | | const res = await teachingModeFlag(this.vehicleIp, this.teachingModeCur) |
| | | if (val == 0) |
| | | showToast( |
| | | "已将该路段路径保存为主路示教路线" |
| | |
| | | "已将该路段路径保存为支路示教路线" |
| | | ) |
| | | if (res?.name) |
| | | this |
| | | .teachingModeCur |
| | | .name = res |
| | | .name |
| | | this.teachingModeCur.name = res.name |
| | | |
| | | const { |
| | | data |
| | | } = await getTeachingMode(this.vehicleIp) |
| | | |
| | | const publicList = data.Public || [] |
| | | const curIndex = publicList.findIndex((a) => a.name == oldName) |
| | | |
| | | if (curIndex > -1) { |
| | | this.ctxDataStr = JSON.stringify([{ |
| | | method: "public_teaching_path", |
| | | param: { |
| | | list: [publicList[curIndex]], |
| | | show: true |
| | | } |
| | | }]) |
| | | |
| | | } |
| | | |
| | | |
| | | } else { |
| | | this.teachingModeCur |
| | | .main_road = |
| | |
| | | } |
| | | |
| | | } catch (ex) { |
| | | this.showError(ex) |
| | | showError(ex) |
| | | } |
| | | }, |
| | | clickTeachingDelete() { |
| | |
| | | param: item, |
| | | }]) |
| | | } catch (ex) { |
| | | this.showError( |
| | | showError( |
| | | ex) |
| | | } finally { |
| | | uni.hideLoading() |
| | |
| | | await delTeachingModeData(this.vehicleIp, data) |
| | | this.reloadTeachingMode() |
| | | } catch (ex) { |
| | | this.showError( |
| | | ex) |
| | | showError(ex) |
| | | } finally { |
| | | uni.hideLoading() |
| | | } |
| | | }, |
| | | |
| | | showError(ex) { |
| | | let exStr = JSON.stringify(ex) |
| | | if (exStr == "{}") |
| | | exStr = ex |
| | | let tip = typeof ex.msg == "string" ? ex.msg : typeof ex.errMsg == "string" ? ex.errMsg : exStr |
| | | showModal(tip, |
| | | "错误", |
| | | false, |
| | | "确定") |
| | | }, |
| | | |
| | | |
| | | } |
| | | } |
| | |
| | | margin-top: 140rpx; |
| | | margin-left: 225rpx; |
| | | width: 300rpx; |
| | | max-height: 50vh; |
| | | overflow: auto; |
| | | align-items: center; |
| | | justify-content: center; |
| | | flex-direction: column; |