|
|
<script setup lang="ts">
|
|
|
import SceneViewer from "@/components/SceneViewer.vue";
|
|
|
import BottomBar from "@/components/BottomBar.vue";
|
|
|
import {ArrowBackOutline,ArrowForwardOutline,Save,Add, SaveOutline} from "@vicons/ionicons5";
|
|
|
import {nextTick, onBeforeUnmount, onMounted, ref} from "vue";
|
|
|
import {darkTheme, useMessage} from "naive-ui";
|
|
|
import {route,route2,route3} from "@/assets/js/testData.ts";
|
|
|
import {useRouteStore} from "@/store/RouteStore.ts";
|
|
|
import {Airline} from "@/types/entityoptions.ts";
|
|
|
import {newAirline, newAirlinePt} from "@/assets/js/TypeInitial.ts";
|
|
|
import * as echarts from "echarts";
|
|
|
import {EChartsType} from "echarts";
|
|
|
import {transLonlat2Car3} from "@/utils/map/geocomputation.ts";
|
|
|
import {drawEcharts_RouteDetection, profileAnalyse} from "@/utils/map/SpatialAnalysis.ts";
|
|
|
import RouteManageViewer from "@/assets/js/RouteManageViewer.js";
|
|
|
import {query_surface_forecast, query_upper_forecast, requireWeatherLevel} from "@/assets/js/weatherRequest.ts";
|
|
|
import {setChartOptions} from "@/assets/js/weatherCharts.ts";
|
|
|
import type { NotificationType } from 'naive-ui'
|
|
|
import { useNotification } from 'naive-ui'
|
|
|
import {useStaticStore} from "@/store/staticOptions";
|
|
|
import {useRouter} from "vue-router";
|
|
|
|
|
|
let myChart: EChartsType = undefined
|
|
|
let weatherChart: EChartsType = undefined
|
|
|
let routeStore = useRouteStore()
|
|
|
let selectedRouteCode = ref(null)
|
|
|
let selectedLevel = ref(null)
|
|
|
let showPtList = ref(true)
|
|
|
let selectedRoute = ref<Airline>(newAirline())
|
|
|
let routesListRef = ref<{ value: string|number; label: string}[]>(null)
|
|
|
let routeViewer = null
|
|
|
let routePtNumber = ref(1)
|
|
|
let uiMsg = useMessage()
|
|
|
let nowTime = ref('')
|
|
|
let timer = null
|
|
|
let PtOptions = [
|
|
|
{label: '删除航点', key: 'deletePt'},
|
|
|
{label: '插入航点', key: 'insertPt'},
|
|
|
]
|
|
|
let routeCode = [
|
|
|
{
|
|
|
label: "任务航线1",
|
|
|
value: 1,
|
|
|
},{
|
|
|
label: "任务航线2",
|
|
|
value: 2,
|
|
|
},{
|
|
|
label: "任务航线3",
|
|
|
value: 3,
|
|
|
},{
|
|
|
label: "任务航线4",
|
|
|
value: 4,
|
|
|
},{
|
|
|
label: "任务航线5",
|
|
|
value: 5,
|
|
|
}
|
|
|
]
|
|
|
let levelOptions = ref<any[]>()
|
|
|
let sStore = useStaticStore();
|
|
|
const router = useRouter()
|
|
|
|
|
|
const notification = useNotification()
|
|
|
const notify = (type: NotificationType, title: string, detail: string) =>{
|
|
|
notification[type]({
|
|
|
title: title,
|
|
|
meta: detail,
|
|
|
duration: 3000,
|
|
|
closable: true,
|
|
|
keepAliveOnHover: true
|
|
|
})
|
|
|
}
|
|
|
onBeforeUnmount(()=>{
|
|
|
clearInterval(timer)
|
|
|
})
|
|
|
onMounted(()=>{
|
|
|
routeViewer = new RouteManageViewer(window.viewer)
|
|
|
myChart = echarts.init(document.getElementById('echarts-profile'),'dark')
|
|
|
weatherChart = echarts.init(document.getElementById('weatherChart'),'dark')
|
|
|
|
|
|
timer = setInterval(getTimesInterval, 1000);
|
|
|
|
|
|
routeStore.addRoute(route)
|
|
|
routeStore.addRoute(route2)
|
|
|
routeStore.addRoute(route3)
|
|
|
|
|
|
routesListRef.value = routeStore.flyRoute.map((route)=> {
|
|
|
return {
|
|
|
value: route.unicode,
|
|
|
label: route.name
|
|
|
}
|
|
|
})
|
|
|
requireWeatherLevel().then(res=>{
|
|
|
console.log(res)
|
|
|
// 查询失败
|
|
|
if(!res.data.success){
|
|
|
notify('error', '气象信息查询失败', res.data.message)
|
|
|
return
|
|
|
}
|
|
|
levelOptions.value = res.data.data.levelsAvailableList[0].pressureLevels.map((level,index)=>{
|
|
|
return {
|
|
|
value: level,
|
|
|
label: level+'hPa/ '+ res.data.data.levelsAvailableList[0].heightLevels[index] + 'm'
|
|
|
}
|
|
|
})
|
|
|
levelOptions.value.reverse()
|
|
|
levelOptions.value.unshift({value: -1, label: '地面'})
|
|
|
selectedLevel.value = -1
|
|
|
})
|
|
|
})
|
|
|
|
|
|
|
|
|
function getTimesInterval() {
|
|
|
let now = new Date();
|
|
|
let year = now.getFullYear(); // 年
|
|
|
let month = (now.getMonth() + 1).toString().padStart(2, '0'); // 月
|
|
|
let day = now.getDate().toString().padStart(2, '0'); // 日
|
|
|
let hours = now.getHours().toString().padStart(2, '0'); // 时
|
|
|
let minutes = now.getMinutes().toString().padStart(2, '0'); // 分
|
|
|
let seconds = now.getSeconds().toString().padStart(2, '0'); // 秒
|
|
|
|
|
|
// 格式化时间
|
|
|
nowTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
|
|
}
|
|
|
|
|
|
// 航线选择回调
|
|
|
function checkRoute(key:number|string){
|
|
|
new Promise((resolve,reject)=>{
|
|
|
selectedRoute.value = routeStore.flyRoute.filter(element => element.unicode === String(key))[0]
|
|
|
if(selectedRoute.value == null) reject('未找到航线')
|
|
|
routeViewer.routeParams.isClose = selectedRoute.value.isClose
|
|
|
window.viewer.flyTo(routeViewer.addAirLine(selectedRoute.value), {duration: 2})
|
|
|
queryWeather(selectedRoute.value.points[0].lon,selectedRoute.value.points[0].lat)
|
|
|
setTimeout(()=>resolve('success'),2500)
|
|
|
}).then((result)=>{
|
|
|
//绘制地形图表
|
|
|
let Cartain3 = transLonlat2Car3(selectedRoute.value.points)
|
|
|
// 计算地形剖面,默认采样间隔为1km
|
|
|
let res = profileAnalyse(window.viewer, Cartain3, 1000)
|
|
|
// 计算航线距离数组
|
|
|
let orderArr = []
|
|
|
let hArr = []
|
|
|
Cartain3.forEach((_,index) => {
|
|
|
orderArr.push(index + 1)
|
|
|
hArr.push(selectedRoute.value.points[index].alt)
|
|
|
})
|
|
|
console.log(orderArr,hArr)
|
|
|
// 弹出地形图表
|
|
|
drawEcharts_RouteDetection(myChart, res.distanceArray, res.elevationArray, orderArr, hArr)
|
|
|
|
|
|
}).catch((err)=>{
|
|
|
uiMsg.error(err)
|
|
|
})
|
|
|
}
|
|
|
|
|
|
// 在尾部添加航点
|
|
|
function addPtToTail() {
|
|
|
let newPt = newAirlinePt()
|
|
|
newPt.nPt = selectedRoute.value.points.at(-1).nPt + 1
|
|
|
selectedRoute.value.points.push(newPt)
|
|
|
}
|
|
|
|
|
|
// 保存航点编辑
|
|
|
// 1 重新编辑航点的ch2属性
|
|
|
// 2 刷新航线属性
|
|
|
// 3 将改动更新到store中
|
|
|
function savePt() {
|
|
|
selectedRoute.value.points.forEach((pt,index) => {
|
|
|
pt.ch2 = 3
|
|
|
pt.nPt = index+1 //航点序号从1起
|
|
|
})
|
|
|
selectedRoute.value.points.at(-1).ch2 = 1
|
|
|
selectedRoute.value.PtNum = selectedRoute.value.points.length
|
|
|
routeStore.addRoute(selectedRoute.value)
|
|
|
}
|
|
|
|
|
|
/** 删除或在此前插入航点
|
|
|
*
|
|
|
* @param key 下拉菜单key
|
|
|
* @param index 航点索引
|
|
|
*/
|
|
|
function handlePtSelect(key:any, index:number) {
|
|
|
if(key=='deletePt'){
|
|
|
selectedRoute.value.points.splice(index,1)
|
|
|
selectedRoute.value.points.forEach((pt,i) => {
|
|
|
pt.nPt = i+1
|
|
|
})
|
|
|
showPtList.value = false
|
|
|
nextTick(()=>{
|
|
|
showPtList.value = true
|
|
|
})
|
|
|
}
|
|
|
else if(key=='insertPt'){
|
|
|
selectedRoute.value.points.forEach((pt,i) => {
|
|
|
if(i>=index) pt.nPt = i + 2
|
|
|
})
|
|
|
let newPt = newAirlinePt()
|
|
|
newPt.nPt = index + 1
|
|
|
showPtList.value = false
|
|
|
nextTick(()=>{
|
|
|
selectedRoute.value.points.splice(index,0,newPt)
|
|
|
showPtList.value = true
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
|
|
|
//查询上一航点的气象信息
|
|
|
function prevPtWeather() {
|
|
|
routePtNumber.value -= 1
|
|
|
window.viewer.flyTo(window.viewer.entities.getById(selectedRoute.value.unicode + "-航点" + (routePtNumber.value).toString()),
|
|
|
{duration: 1})
|
|
|
let lat = selectedRoute.value.points[routePtNumber.value-1].lat
|
|
|
let lon = selectedRoute.value.points[routePtNumber.value-1].lon
|
|
|
if(lat && lon){
|
|
|
queryWeather(lon,lat, selectedLevel.value)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
//查询下一航点的气象信息
|
|
|
function nextPtWeather() {
|
|
|
routePtNumber.value += 1
|
|
|
window.viewer.flyTo(window.viewer.entities.getById(selectedRoute.value.unicode + "-航点" + (routePtNumber.value).toString()),
|
|
|
{duration: 1})
|
|
|
|
|
|
let lat = selectedRoute.value.points[routePtNumber.value-1].lat
|
|
|
let lon = selectedRoute.value.points[routePtNumber.value-1].lon
|
|
|
if(lat && lon){
|
|
|
queryWeather(lon,lat, selectedLevel.value)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function queryWeather(lon:number,lat:number,level?: number) {
|
|
|
let response : Promise<any>
|
|
|
if(level==null|| level==-1){ //地面
|
|
|
response = query_surface_forecast(lon,lat)
|
|
|
}else{
|
|
|
response = query_upper_forecast(lon,lat, level)
|
|
|
}
|
|
|
response.then(res=>{
|
|
|
// 查询失败
|
|
|
if(!res.data.success){
|
|
|
notify('error', '气象信息查询失败', res.data.message)
|
|
|
return
|
|
|
}
|
|
|
let weatherData = res.data.data
|
|
|
let timeArr = weatherData.time.map(item=>{
|
|
|
let i = item.indexOf('T')
|
|
|
return item.slice(i+1,i+3) + '时'
|
|
|
})
|
|
|
if(!Reflect.has(weatherData,'precip')){ //高空数据无precip(降水)属性,需添加数组并补0
|
|
|
weatherData.precip = Array.from({length:timeArr.length},()=>0)
|
|
|
}
|
|
|
setChartOptions(weatherChart, timeArr.slice(0,8),weatherData.temp.slice(0,8),weatherData.windSpeed.slice(0,8),
|
|
|
weatherData.wind360.slice(0,8), weatherData.precip.slice(0,8),weatherData.humidity.slice(0,8))
|
|
|
}).catch(e=>{
|
|
|
console.log()
|
|
|
uiMsg.error('气象信息查询失败:' + e.message,{duration:3000, keepAliveOnHover: true})
|
|
|
|
|
|
})
|
|
|
}
|
|
|
|
|
|
function levelChanged(key: string | number) {
|
|
|
selectedLevel.value = key
|
|
|
let lat = selectedRoute.value.points[routePtNumber.value-1].lat
|
|
|
let lon = selectedRoute.value.points[routePtNumber.value-1].lon
|
|
|
if(lat && lon){
|
|
|
queryWeather(lon,lat, Number(key))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 将航线信息发送给QT端(每次发送一条航线)
|
|
|
* @param routeUnicode 航线唯一码
|
|
|
*/
|
|
|
function sendRouteToQT(routeUnicode: string|number){
|
|
|
let selectedRoute = routeStore.flyRoute.filter(element => element.unicode === String(routeUnicode))[0]
|
|
|
if(selectedRoute){
|
|
|
if(sStore.webskt.ws?.readyState == WebSocket.OPEN){
|
|
|
console.log(selectedRoute)
|
|
|
sStore.webskt.ws.send(JSON.stringify(selectedRoute))
|
|
|
uiMsg.info('航线' + selectedRoute.name + '已下发')
|
|
|
}else {
|
|
|
uiMsg.info('请先开启WebSocket')
|
|
|
}
|
|
|
}else
|
|
|
uiMsg.warning('未找到相应的航线')
|
|
|
}
|
|
|
|
|
|
function delRoute(routeUnicode: string|number){
|
|
|
|
|
|
}
|
|
|
|
|
|
function backToHomePage(){
|
|
|
router.push({name: 'Home'})
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
<template>
|
|
|
<n-config-provider :theme="darkTheme">
|
|
|
<div style="display: flex;">
|
|
|
<div id="left">
|
|
|
<n-layout style="height: 100%;">
|
|
|
<n-layout-header :bordered="true">
|
|
|
<n-popover trigger="hover">
|
|
|
<template #trigger>
|
|
|
<n-button quaternary type="success" size="large" @click="backToHomePage">
|
|
|
<template #icon>
|
|
|
<n-icon><ArrowBackOutline/></n-icon>
|
|
|
</template>
|
|
|
</n-button>
|
|
|
</template>
|
|
|
返回
|
|
|
</n-popover>
|
|
|
<n-popover trigger="hover">
|
|
|
<template #trigger>
|
|
|
<n-button quaternary type="success" size="large">
|
|
|
<template #icon>
|
|
|
<n-icon><Save/></n-icon>
|
|
|
</template>
|
|
|
</n-button>
|
|
|
</template>
|
|
|
保存编辑
|
|
|
</n-popover>
|
|
|
</n-layout-header>
|
|
|
<n-layout-content>
|
|
|
<n-flex vertical style="margin-left: 1.2rem; margin-top: .5rem;overflow: hidden">
|
|
|
<n-ellipsis><h3>航线列表</h3></n-ellipsis>
|
|
|
<n-radio-group v-model:value="selectedRouteCode" name="radiogroup" @update:value="checkRoute">
|
|
|
<n-scrollbar style="height: 20vh">
|
|
|
<n-space vertical>
|
|
|
<n-space v-for="r in routesListRef" justify="space-between"
|
|
|
style="margin-right: 2rem; border-bottom: #383737 1px solid">
|
|
|
<n-radio :key="r.value" :value="r.value">
|
|
|
{{ r.label }}
|
|
|
</n-radio>
|
|
|
<n-button-group>
|
|
|
<n-button secondary size="small" type="info" style="height: 1.3rem" @click="sendRouteToQT(r.value)">下发</n-button>
|
|
|
<n-button secondary size="small" type="error" style="height: 1.3rem; margin-bottom: 4px" @click="delRoute(r.value)">删除</n-button>
|
|
|
</n-button-group>
|
|
|
</n-space>
|
|
|
|
|
|
</n-space>
|
|
|
</n-scrollbar>
|
|
|
</n-radio-group>
|
|
|
|
|
|
<n-divider style="margin: .2rem -.5rem"></n-divider>
|
|
|
<n-space vertical style="margin: .2rem 1rem">
|
|
|
<n-space>
|
|
|
<n-space>
|
|
|
<n-ellipsis>开闭</n-ellipsis>
|
|
|
<n-switch v-model:value="selectedRoute.isClose" size="small">
|
|
|
<template #checked>闭</template>
|
|
|
<template #unchecked>开</template>
|
|
|
</n-switch>
|
|
|
</n-space>
|
|
|
<n-space style="margin-left: .5rem">
|
|
|
<n-ellipsis>航点数</n-ellipsis>
|
|
|
<n-input-number :show-button="false" disabled size="tiny" style="width: 5rem"
|
|
|
v-model:value="selectedRoute.PtNum"></n-input-number>
|
|
|
</n-space>
|
|
|
</n-space>
|
|
|
<n-space>
|
|
|
<n-ellipsis style="width: 4rem">航线名称</n-ellipsis>
|
|
|
<n-input size="tiny" style="width:10rem" v-model:value="selectedRoute.name"></n-input>
|
|
|
</n-space>
|
|
|
<n-space>
|
|
|
<n-ellipsis style="width: 4rem">航线类型</n-ellipsis>
|
|
|
<n-select style="width:10rem" size="tiny" v-model:value="selectedRoute.code" :options="routeCode"/>
|
|
|
</n-space>
|
|
|
</n-space>
|
|
|
|
|
|
<n-divider style="margin: .2rem -.5rem"></n-divider>
|
|
|
<n-ellipsis><h3>地形剖面图</h3></n-ellipsis>
|
|
|
<n-space justify="space-evenly">
|
|
|
<n-ellipsis>采样间隔</n-ellipsis>
|
|
|
<n-input-number size="tiny" placeholder="默认1000米"
|
|
|
style="width:8vw;" :min="100" :step="100">
|
|
|
</n-input-number>
|
|
|
<n-button type="success" size="tiny">重新绘图</n-button>
|
|
|
</n-space>
|
|
|
<div id="echarts-profile">
|
|
|
<!-- Echarts地形剖面绘图区 -->
|
|
|
</div>
|
|
|
</n-flex>
|
|
|
</n-layout-content>
|
|
|
</n-layout>
|
|
|
</div>
|
|
|
|
|
|
<div id="map2">
|
|
|
<SceneViewer id="scene-viewer-route"></SceneViewer>
|
|
|
<BottomBar></BottomBar>
|
|
|
</div>
|
|
|
|
|
|
<div id="right">
|
|
|
<n-layout style="height: 100%">
|
|
|
<n-layout-header :bordered="true">
|
|
|
<n-space justify="center">
|
|
|
<n-button quaternary type="default" size="large"><h4>航点详情 {{'— '+selectedRoute.name}}</h4></n-button>
|
|
|
</n-space>
|
|
|
</n-layout-header>
|
|
|
<n-layout-content>
|
|
|
<n-flex vertical >
|
|
|
<n-space justify="space-around" style="margin-top:.5rem;">
|
|
|
<n-ellipsis style="margin-left:.2rem; width: 2rem">序号</n-ellipsis>
|
|
|
<n-ellipsis style="width: 5.5rem">经度</n-ellipsis>
|
|
|
<n-ellipsis style="width: 5.5rem">纬度</n-ellipsis>
|
|
|
<n-ellipsis style="width: 3rem">高度</n-ellipsis>
|
|
|
</n-space>
|
|
|
<n-scrollbar style="height: 30vh">
|
|
|
<div v-if="selectedRoute.points.length>0 && showPtList" >
|
|
|
<n-space justify="space-around" style="margin: .2rem 0" v-for="(pt,index) in selectedRoute.points" >
|
|
|
<n-dropdown trigger="hover" size="small" placement="top-start" :show-arrow="true"
|
|
|
:options="PtOptions" @select="handlePtSelect($event,index)">
|
|
|
<n-button secondary type="info" size="tiny" style="margin-left:.2rem; width: 1.7rem">{{ pt.nPt }}</n-button>
|
|
|
</n-dropdown>
|
|
|
<n-input-number size="tiny" v-model:value="pt.lon" :show-button="false" style="width: 5.5rem"
|
|
|
placeholder="经度" max="180" min="-180"></n-input-number>
|
|
|
<n-input-number size="tiny" v-model:value="pt.lat" :show-button="false" style="width: 5.5rem"
|
|
|
placeholder="纬度" max="90" min="-90"></n-input-number>
|
|
|
<n-input-number size="tiny" v-model:value="pt.alt" :show-button="false" style="width: 3rem"
|
|
|
placeholder="高度" max="12000" min="-500"></n-input-number>
|
|
|
</n-space>
|
|
|
|
|
|
<n-space justify="center" style="margin: .5rem 0">
|
|
|
<n-button type="info" size="small" @click="addPtToTail">
|
|
|
<template #icon>
|
|
|
<Add/>
|
|
|
</template>
|
|
|
添加点
|
|
|
</n-button>
|
|
|
<n-button type="info" size="small" @click="savePt">
|
|
|
<template #icon>
|
|
|
<SaveOutline/>
|
|
|
</template>
|
|
|
保存编辑
|
|
|
</n-button>
|
|
|
</n-space>
|
|
|
</div>
|
|
|
|
|
|
<n-space v-else justify="space-around">
|
|
|
<n-empty style="margin-top:3rem " description="无内容">
|
|
|
</n-empty>
|
|
|
</n-space>
|
|
|
</n-scrollbar>
|
|
|
|
|
|
</n-flex>
|
|
|
<n-divider style="margin: .2rem -.5rem"></n-divider>
|
|
|
|
|
|
<n-flex vertical>
|
|
|
<n-row style="margin-left: 1rem; margin-top: .5rem;"><n-ellipsis><h3>气象信息</h3></n-ellipsis></n-row>
|
|
|
<n-space justify="space-around" class="routePtSelector">
|
|
|
<n-button size="small" @click="prevPtWeather" :disabled="routePtNumber<=1">
|
|
|
<template #icon><ArrowBackOutline/></template>
|
|
|
</n-button>
|
|
|
<n-ellipsis>航点 {{routePtNumber}}</n-ellipsis>
|
|
|
<n-button size="small" @click="nextPtWeather" :disabled="selectedRoute.points.length<=routePtNumber">
|
|
|
<template #icon><ArrowForwardOutline/></template>
|
|
|
</n-button>
|
|
|
</n-space>
|
|
|
|
|
|
<n-space justify="center">
|
|
|
<n-ellipsis>{{nowTime}}</n-ellipsis>
|
|
|
<n-select style="width: 10rem" size="tiny" :options="levelOptions" v-model:value="selectedLevel"
|
|
|
@update:value="levelChanged" placeholder="选择高度层"></n-select>
|
|
|
</n-space>
|
|
|
<n-scrollbar x-scrollable id="weatherChart" style="height:41.5vh; width: 100%">
|
|
|
<!-- Echarts气象信息绘图区 -->
|
|
|
</n-scrollbar>
|
|
|
</n-flex>
|
|
|
</n-layout-content>
|
|
|
</n-layout>
|
|
|
</div>
|
|
|
</div>
|
|
|
</n-config-provider>
|
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<style scoped>
|
|
|
#left{
|
|
|
width: 22vw;
|
|
|
height: 100vh;
|
|
|
}
|
|
|
#map2 {
|
|
|
width: 55vw;
|
|
|
height: 100vh;
|
|
|
position: relative;
|
|
|
}
|
|
|
#right{
|
|
|
width: 23vw;
|
|
|
height: 100vh;
|
|
|
}
|
|
|
|
|
|
#echarts-profile{
|
|
|
margin-left: -1rem;
|
|
|
margin-top: -.5rem;
|
|
|
height: 40vh;
|
|
|
}
|
|
|
#scene-viewer-route {
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
.routePtSelector {
|
|
|
width: 110%;
|
|
|
color: #63e2b7;
|
|
|
font-weight: bold;
|
|
|
margin-left: -1rem
|
|
|
}
|
|
|
.routePtSelector:hover{
|
|
|
background: rgb(24, 24, 28);
|
|
|
}
|
|
|
|
|
|
.border{
|
|
|
border: #22ee22 solid 1px;
|
|
|
}
|
|
|
</style> |