Compare commits

...

12 Commits

14 changed files with 3386 additions and 909 deletions

View File

@ -6,6 +6,8 @@ ENV = 'development'
# 共享电动车管理系统/开发环境 # 共享电动车管理系统/开发环境
VUE_APP_BASE_API = 'https://dche.ccttiot.com/prod-api' VUE_APP_BASE_API = 'https://dche.ccttiot.com/prod-api'
# VUE_APP_BASE_API = 'https://che.chuangtewl.com/prod-api'
# VUE_APP_BASE_API = 'http://192.168.2.75:8088' # VUE_APP_BASE_API = 'http://192.168.2.75:8088'
# 路由懒加载 # 路由懒加载

View File

@ -5,7 +5,8 @@ VUE_APP_TITLE = 共享电动车管理系统
ENV = 'production' ENV = 'production'
# 共享电动车管理系统/生产环境 # 共享电动车管理系统/生产环境
VUE_APP_BASE_API = 'https://dche.ccttiot.com/prod-api' # VUE_APP_BASE_API = 'https://dche.ccttiot.com/prod-api'
VUE_APP_BASE_API = 'https://che.chuangtewl.com/prod-api'
# 小程序外链跳转设备 # 小程序外链跳转设备
VUE_APP_WX_DEVICE_URL = 'weixin://dl/business/?appid=wx4d178f8c80348214&env_version=release' VUE_APP_WX_DEVICE_URL = 'weixin://dl/business/?appid=wx4d178f8c80348214&env_version=release'

View File

@ -5,6 +5,7 @@ NODE_ENV = production
# 测试环境配置 # 测试环境配置
ENV = 'staging' ENV = 'staging'
# VUE_APP_BASE_API = 'https://dche.ccttiot.com/prod-api'
VUE_APP_BASE_API = 'https://che.chuangtewl.com/prod-api' VUE_APP_BASE_API = 'https://che.chuangtewl.com/prod-api'
# 共享电动车管理系统/测试环境 # 共享电动车管理系统/测试环境
# VUE_APP_BASE_API = 'https://dianche.chuantewulian.cn/stage-api' # VUE_APP_BASE_API = 'https://dianche.chuantewulian.cn/stage-api'

View File

@ -102,3 +102,10 @@ export function selectDeptByAreaId(areaId) {
method: 'get' method: 'get'
}) })
} }
// 根据运营区id获取运营商
export function getleaderboard(type,timeLimit) {
return request({
url: '/index/admim/leaderboard?type='+type+'&timeLimit='+timeLimit,
method: 'get'
})
}

View File

@ -132,6 +132,14 @@ export function reboot(data){
method: 'post' method: 'post'
}) })
} }
// 获取轨迹
export function gettrajectory(data){
console.log(data, 'data');
return request({
url: '/system/device/trajectory?sn=' + data.sn + '&startTime=' + data.startTime + '&endTime=' + data.endTime,
method: 'post'
});
}
// 根据sn查询设备 // 根据sn查询设备
export function getDeviceBySn(sn){ export function getDeviceBySn(sn){
return request({ return request({

View File

@ -86,7 +86,7 @@ export default {
console.log("轨迹=====地图已销毁"); console.log("轨迹=====地图已销毁");
} else { } else {
console.log("轨迹=====地图实例不存在,无需销毁"); console.log("轨迹=====地图实例不存在,无需销毁");
} }
}, },
showMarkers() { showMarkers() {
if (this.show) { if (this.show) {
@ -138,7 +138,7 @@ export default {
AMapLoader.load({ AMapLoader.load({
key: globalConfig.aMap.key, // WebKey load key: globalConfig.aMap.key, // WebKey load
version: "2.0", // JSAPI 1.4.15 version: "2.0", // JSAPI 1.4.15
plugins: ["AMap.MoveAnimation"] plugins: ["AMap.PlaceSearch", "AMap.MoveAnimation"], // "AMap.MoveAnimation"
}).then((AMap) => { }).then((AMap) => {
let abb; let abb;
try { try {

View File

@ -0,0 +1,709 @@
<template>
<div class="place-search-map" :style="{width: width, height: height}" v-loading="loading">
<div id="container"></div>
<el-row class="search-row" :gutter="8" type="flex">
</el-row>
<div id="panel"></div>
<div class="input-card">
<h4>轨迹回放控制</h4>
<div class="input-item">
<input type="button" class="btn" value="开始动画" id="start" @click="startAnimation()" />
<input type="button" class="btn" value="暂停动画" id="pause" @click="pauseAnimation()" />
</div>
<div class="input-item">
<input type="button" class="btn" value="继续动画" id="resume" @click="resumeAnimation()" />
<input type="button" class="btn" value="停止动画" id="stop" @click="stopAnimation()" />
</div>
</div>
</div>
</template>
<script>
import AMapLoader from "@amap/amap-jsapi-loader";
import globalConfig from '@/utils/config/globalConfig'
import { debounce } from '@/utils'
import AreaTextSelect from '@/components/AreaTextSelect/index.vue'
import { getArea } from '@/api/system/area'
import { listParking } from '@/api/system/parking'
import { getDeviceBySn } from '@/api/system/device'
export default {
name: "LocationMap",
components: { AreaTextSelect },
props: {
width: {
type: String,
default: "100%"
},
height: {
type: String,
default: "100%"
},
initLng: {
type: String,
default: '120.250452'
},
initLat: {
type: String,
default: '27.101745'
},
status: {
type: String,
default: '0'
},
onlineStatus: {
type: String,
default: '0'
},
deviceSn: {
type: String,
default: ''
},
tripRouteStr: {
type: String,
default: ''
}
},
data() {
return {
AMap: null,
map: null, //
placeSearch: null, // POI
geocoder: null, //
loading: false,
keyword: null,
markers: [],
area: {
// province: '',
// city: '',
},
// area:{},
parkingList:[],
noParkingList:[],
noridingList:[],
labels: [],
areaId:''
}
},
mounted() {
// this.initAMap();
// this.getAreas(this.areaId)
getDeviceBySn(this.deviceSn).then(response => {
this.areaId=response.data.areaId
console.log(response,'responseresponse');
this.getAreas(this.areaId)
});
},
beforeDestroy() {
console.log("轨迹=====组件将被销毁");
this.destroyMap();
},
methods: {
destroyMap() {
if (this.map) {
console.log("位置=====地图实例存在,销毁地图...");
this.map.destroy();
console.log("位置=====地图已销毁");
} else {
console.log("位置=====地图实例不存在,无需销毁");
}
},
// onChangeArea() {
// this.loadPlaceSearch(this.area.province, this.area.city);
// if (this.keyword != null) {
// this.doPlaceSearch(this.keyword);
// } else {
// let city = this.area.city === '' ? this.area.province : this.area.city;
// this.doPlaceSearch(city + '');
// }
// },
async getAreas(areaId) {
getArea(this.areaId).then(response => {
console.log(response,'responseresponse');
this.area = response.data;
listParking({ areaId: this.area.areaId }).then(response => {
let list = response.rows;
console.log(list,'listlistlist');
list.forEach(item => {
if (item.type == '1') {
this.parkingList.push(item);
} else if (item.type == '2') {
this.noParkingList.push(item);
} else if (item.type == '3') {
this.noridingList.push(item);
}
});
this.initAMap();
});
});
},
initAMap() {
AMapLoader.load({
key: globalConfig.aMap.key, // WebKey load
version: "2.0", // JSAPI 1.4.15
plugins: ["AMap.PlaceSearch", "AMap.MoveAnimation"], // "AMap.MoveAnimation"
}).then((AMap) => {
this.AMap = AMap;
this.map = new AMap.Map("container", {
// id
viewMode: "3D", // 3D
zoom: 16, //
center: [this.initLng == null ? 120.250452 : this.initLng, this.initLat == null ? 27.101745 : this.initLat], //
});
// this.map.on('click', this.onClickMap);
// POI
// this.loadPlaceSearch(this.area.province, this.area.city);
//
// this.loadGeoCoder();
this.addArea(JSON.parse( this.area.boundaryStr) || []);
//
this.parkingList.forEach(parking => {
this.addParking(JSON.parse(parking.boundaryStr) || [], parking.parkingName, parking.longitude, parking.latitude);
this.addMarker2(parking, "https://lxnapi.ccttiot.com/FqcYf6ecsnbC0OT6YYAF5npgu-kh", parking.parkingName, "#1890ff");
});
//
this.noParkingList.forEach(noparking => {
this.addNoParking(JSON.parse(noparking.boundaryStr) || []);
this.addMarker2(noparking, "https://lxnapi.ccttiot.com/FjKE5PWbnEnZUq3k-wVIvV4lv8Ab", noparking.parkingName, "#ff4444");
});
//
this.noridingList.forEach(noriding => {
this.addNoriding(JSON.parse(noriding.boundaryStr) || [], noriding.parkingName, noriding.longitude, noriding.latitude);
this.addMarker2(noriding, "https://lxnapi.ccttiot.com/FmX1diEPPbFYe1vcUfKp6qbKzzh2", noriding.parkingName, "#ffcc00");
});
//
this.initMarker();
// setTimeout(() => {
// this.trajectory()
// }, 500);
}).catch((e) => {
console.log("地图加载失败!!!");
console.log(e);
});
},
trajectory() {
this.marker=null
console.log('轨迹调用了11',this.tripRouteStr);
this.line = JSON.parse(this.tripRouteStr)
console.log("this.line================" + this.line)
let abb;
try {
abb = JSON.parse(this.tripRouteStr);
} catch (error) {
console.error("Error parsing tripRouteStr:", error);
return;
}
console.log(abb);
let latitude = parseFloat(abb[0][1]);
let longitude = parseFloat(abb[0][0]);
let latitude1 = parseFloat(abb[abb.length - 1][1]);
let longitude1 = parseFloat( abb[abb.length - 1][0]);
this.marker = new AMap.Marker({
map: this.map,
position: [longitude, latitude],
icon: new AMap.Icon({
size: new AMap.Size(25, 38), //
image: "https://lxnapi.ccttiot.com/bike/img/static/u06paUGiHLvL08Pw7BGr",
imageSize: new AMap.Size(25, 38) //
}),
offset: new AMap.Pixel(-12.5, -19.5),
});
this.marker = new AMap.Marker({
map: this.map,
position: [longitude1, latitude1],
icon: new AMap.Icon({
size: new AMap.Size(25, 38), //
image: "https://lxnapi.ccttiot.com/bike/img/static/uwpAj9vYtPRmhtTOtflx",
imageSize: new AMap.Size(25, 38) //
}),
offset: new AMap.Pixel(-12.5, -19.5),
});
// console.log(latitude,longitude,'longitudelongitudelongitude');
let line = this.line;
this.marker = new AMap.Marker({
map: this.map,
position: line[0],
icon: new AMap.Icon({
image: globalConfig.icon.trajectory,
size: new AMap.Size(25, 39), //
imageSize: new AMap.Size(25, 39) //
}),
offset: new AMap.Pixel(-12.5, -19.5),
autoRotation: true,
angle: 90
});
//
let polyline = new AMap.Polyline({
map: this.map,
path: line,
showDir: true,
strokeColor: "#28F", //线
// strokeOpacity: 1, //线
strokeWeight: 6 //线
// strokeStyle: "solid" //线
});
let passedPolyline = new AMap.Polyline({
map: this.map,
// path: this.lineArr,
strokeColor: "#AF5", //线
// strokeOpacity: 1, //线
strokeWeight: 6 //线
// strokeStyle: "solid" //线
});
this.marker.on("moving", function (e) {
passedPolyline.setPath(e.passedPath);
});
this.map.setFitView();
},
initMarker() {
this.removeAllMarker();
if (this.initLng != null && this.initLat != null) {
// this.getGeoAddress(this.initLng, this.initLat).then(res => {
//
this.removeAllMarker();
console.log("添加标记点")
console.log("====",this.initLng, this.initLat,this.deviceSn, this.status, this.onlineStatus)
this.addMarker(this.initLng, this.initLat, this.deviceSn, this.status, this.onlineStatus);
// this.$emit('map-geo', res, this.initLng, this.initLat);
//
// let component = res.regeocode.addressComponent;
// this.area = {
// province: component.province,
// city: component.city === '' ? '' : component.city,
// }
// }).finally(() => {
// this.loading = false;
// })
}
},
addMarker2(parking, icon, title, color) {
let marker = new AMap.Marker({
map: this.map,
icon: new AMap.Icon({
image: icon,
size: new AMap.Size(25, 36), //
imageSize: new AMap.Size(25, 36) //
}),
position: [parking.longitude, parking.latitude],
offset: new AMap.Pixel(-12.5, -36)
});
this.markers.push(marker);
// console.log("title============="+title)
// Text
let text = new AMap.Text({
text: title,
anchor: 'center', //
position: [parking.longitude, parking.latitude],
offset: new AMap.Pixel(0, -50),
style: {
'background-color': color, //
'border': 'none', //
'border-radius': '5px', // 5px
'color': 'white', //
'font-size': '12px', //
}
});
//
this.map.add(text);
// console.log("text=============",text)
this.labels.push(text)
},
addMarker: function(lng, lat, title, status, onlineStatus) {
// Marker
console.log('title===========' + title)
let icon = this.formarStatus(status, onlineStatus)
console.log('icon===========' + icon)
let marker = new AMap.Marker({
position: new AMap.LngLat(lng, lat), //
icon: new AMap.Icon({
image: icon,
size: new AMap.Size(25, 30), //
imageSize: new AMap.Size(25, 30) //
}),
title: title,
offset: new AMap.Pixel(-12.5, -35)
})
//
this.map.add(marker)
this.markers.push(marker)
console.log();
// Text
let text = new AMap.Text({
text: title,
anchor: 'center', //
position: new AMap.LngLat(lng, lat), //
offset: new AMap.Pixel(0, -50),
style: {
'background-color': '#1890ff', //
'border': 'none', //
'border-radius': '5px', // 5px
'color': 'white', //
'font-size': '14px', //
'padding': '5px 10px' //
}
})
//
this.map.add(text)
},
addParking(data, title, lon, lat) {
let polygon = new AMap.Polygon({
path: data,
fillColor: '#ccebc5',
strokeOpacity: 1,
fillOpacity: 0.5,
strokeColor: '#3b7ed9', //
strokeWeight: 2, //
strokeStyle: 'solid',
strokeDasharray: [5, 5],
});
polygon.on('mouseover', () => {
polygon.setOptions({
fillOpacity: 0.7,
fillColor: '#71b7cc' //
});
});
polygon.on('mouseout', () => {
polygon.setOptions({
fillOpacity: 0.5,
fillColor: '#a7c1d0' //
});
});
this.map.add(polygon);
},
addNoParking(data) {
let polygon = new AMap.Polygon({
path: data,
fillColor: '#ccebc5',
strokeOpacity: 1,
fillOpacity: 0.5,
strokeColor: '#ff0000', //
strokeWeight: 2, //
strokeStyle: 'solid',
strokeDasharray: [5, 5],
});
polygon.on('mouseover', () => {
polygon.setOptions({
fillOpacity: 0.7,
fillColor: '#ff0000' //
});
});
polygon.on('mouseout', () => {
polygon.setOptions({
fillOpacity: 0.5,
fillColor: '#cc7b7b' //
});
});
this.map.add(polygon);
},
addNoriding(data, title, lon, lat) {
let polygon = new AMap.Polygon({
path: data,
fillColor: '#ccebc5',
strokeOpacity: 1,
fillOpacity: 0.5,
strokeColor: '#ffcc00', //
strokeWeight: 2, //
strokeStyle: 'solid',
strokeDasharray: [5, 5],
});
polygon.on('mouseover', () => {
polygon.setOptions({
fillOpacity: 0.7,
fillColor: '#FFEBA4FF'
});
});
polygon.on('mouseout', () => {
polygon.setOptions({
fillOpacity: 0.5,
fillColor: '#ffeba4'
});
});
this.map.add(polygon);
},
addArea(data) {
let polygon = new AMap.Polygon({
path: data,
fillColor: '#ccebc5',
strokeOpacity: 1,
fillOpacity: 0.5,
strokeColor: '#2b8cbe',
strokeWeight: 1,
strokeStyle: 'dashed',
strokeDasharray: [5, 5],
});
// polygon.on('mouseover', () => {
// polygon.setOptions({
// fillOpacity: 0.7,
// fillColor: '#7bccc4'
// })
// })
// polygon.on('mouseout', () => {
// polygon.setOptions({
// fillOpacity: 0.5,
// fillColor: '#ccebc5'
// })
// })
this.map.add(polygon);
},
formarStatus(status,onlineStatus){
if(onlineStatus == "0"){
if(status == "3"){
return globalConfig.icon.redyellow;
}else if(status == "4"){
return globalConfig.icon.orangered;
}
return globalConfig.icon.red;
}else{
if(status == "0"){
return globalConfig.icon.gray;
}else if(status == "1"){
return globalConfig.icon.blue;
}else if(status == "2"){
return globalConfig.icon.green;
}else if(status == "3"){
return globalConfig.icon.yellow;
}else if(status == "4"){
return globalConfig.icon.orange;
}else if(status == "8"){
return globalConfig.icon.gray;
}else if(status == "9"){
return globalConfig.icon.gray;
}
}
},
//
removeAllMarker() {
this.map.remove(this.markers);
this.markers = [];
},
//
// onClickMap(e) {
// console.log('clickMap', e);
// let lng = e.lnglat.lng;
// let lat = e.lnglat.lat;
// this.changeMarker(lng, lat);
// },
// //
// changeMarker(lng, lat) {
// console.log('changeMarker', lng,lat);
// this.loading = true;
// this.getGeoAddress(lng, lat).then(res => {
// this.removeAllMarker();
// this.addMarker(lng, lat, res.regeocode.formattedAddress);
// this.$emit('map-geo', res, lng, lat);
// }).finally(() => {
// this.loading = false;
// })
// },
// //
// getGeoAddress(lng, lat) {
// console.log('getGeoAddress', lng,lat);
// console.log('this.geocoder', this.geocoder);
// if(this.geocoder){
// return new Promise((resolve, reject) =>{
// this.geocoder.getAddress([lng, lat], (status, result) => {
// if (status === 'complete' && result.info === 'OK') {
// console.log('resolve', result);
// resolve(result);
// } else {
// console.log('reject', status,result);
// reject(status);
// }
// })
// })
// }
// },
// //
// loadGeoCoder() {
// AMap.plugin('AMap.Geocoder', () => {
// this.geocoder = new AMap.Geocoder({
// city: '' // city adcode citycode
// })
// })
// },
// // POI
// loadPlaceSearch(province, city) {
// if (city == null) {
// city = '';
// }
// if (city === '') {
// city = province;
// }
// AMap.plugin(["AMap.PlaceSearch"], () => {
// //
// this.placeSearch = new AMap.PlaceSearch({
// pageSize: 5, //
// pageIndex: 1, //
// city: city, //
// citylimit: true, //
// map: this.map, //
// panel: "panel", //
// autoFitView: true, // 使 Marker
// });
// });
// //
// this.placeSearch.on('selectChanged', (data) => {
// this.$emit('select-changed', data);
// })
// },
//
doPlaceSearch(keyword) {
if (keyword == null) {
return this.$message.warning("请输入关键词");
}
this.loading = true;
this.placeSearch.search(keyword, (status, result) => {
this.loading = false;
console.log("POI", status, result);
});
},
//
doSearchNearBy(keyword, lng, lat, radius = 200) {
if (keyword == null) {
return this.$message.warning("请输入关键词");
}
this.loading = true;
this.placeSearch.searchNearBy(keyword, [lng, lat], radius, (status, result) => {
this.loading = false;
console.log("POI NearBy", status, result);
})
},
//
doSearchNearByCenter(keyword) {
let center = this.map.getCenter();
this.doSearchNearBy(keyword, center.lng, center.lat, 1000);
},
startAnimation() {
console.log(this.marker,'this.markerthis.marker');
this.marker.moveAlong(this.line, {
//
duration: 200,//
});
},
pauseAnimation() {
this.marker.pauseMove();
},
resumeAnimation() {
this.marker.resumeMove();
},
stopAnimation() {
this.marker.stopMove();
},
//
onInputSearch: debounce(function () {
this.doPlaceSearch(this.keyword)
}, 600)
},
};
</script>
<style scoped lang="scss">
.place-search-map {
position: relative;
overflow: hidden;
.search-row {
margin: 0.5em;
}
.input-card .btn {
margin-right: 1.2rem;
width: 9rem;
}
.input-card .btn:last-child {
margin-right: 0;
}
.btn {
display: inline-block;
font-weight: 400;
text-align: center;
white-space: nowrap;
vertical-align: middle;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
border: 1px solid transparent;
transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out;
background-color: transparent;
background-image: none;
color: #25A5F7;
border-color: #25A5F7;
padding: .25rem .5rem;
line-height: 1.5;
border-radius: 1rem;
// -webkit-appearance: button;
cursor: pointer;
}
.input-item {
position: relative;
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-ms-flex-align: center;
align-items: center;
width: 100%;
height: 3rem;
}
.input-card {
position: absolute !important;
display: flex;
flex-direction: column;
min-width: 0;
word-wrap: break-word;
background-color: #fff;
background-clip: border-box;
border-radius: .25rem;
width: 22rem;
border-width: 0;
border-radius: 0.4rem;
box-shadow: 0 2px 6px 0 rgba(114, 124, 245, .5);
position: fixed;
bottom: 1rem;
right: 1rem;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
padding: 0.75rem 1.25rem;
}
#container {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
#panel {
position: absolute;
background-color: white;
max-height: 90%;
overflow-y: auto;
top: 10px;
right: 10px;
width: 280px;
}
}
</style>

View File

@ -246,6 +246,20 @@ export const dynamicRoutes = [
} }
] ]
}, },
{
path: '/system/areaRange',
component: Layout,
hidden: true,
permissions: ['system:parking:list'],
children: [
{
path: 'index/:areaId(\\d+)?', // 将参数设置为可选
component: () => import('@/views/system/area/areaRange'),
name: 'Data',
meta: { title: '运营区范围设置', activeMenu: '/system/areaRange' }
}
]
},
{ {
path: '/system/area-noriding', path: '/system/area-noriding',
component: Layout, component: Layout,

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,942 @@
<template>
<div class="container">
<div id="container"></div>
<div class="input-card-left">
<div style="color: red">tips双击区域可重新编辑</div>
<div style="color: red">
点击新建--左键开始建立停车区--右键完成建立--点击结束编辑才会最终生效
</div>
</div>
<div class="input-card">
<el-button @click="setFitView()">全局查看</el-button>
<el-button @click="changeMapStyle()">切换地图</el-button>
<el-button @click="showMarkers">显示/隐藏文字</el-button>
</div>
<div class="input-card-right">
<el-button @click="createPolygon()">新建</el-button>
<!-- <el-button @click="polyEditor.open()">开始编辑</el-button> -->
<el-button @click="editClose">结束编辑</el-button>
<el-button @click="changeMapStyle()">切换地图</el-button>
<el-button @click="clearPolygon()">清除多边形</el-button>
</div>
<el-form
:model="queryParams"
ref="queryForm"
size="small"
:inline="true"
v-show="showSearch"
label-width="68px"
style="margin-top: 20px"
>
<el-form-item label="停车区" label-width="100" prop="parkingName">
<el-input
v-model="queryParams.parkingName"
placeholder="请输入停车区"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select v-model="queryParams.type" clearable>
<el-option
v-for="dict in dict.type.et_parking_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button
type="primary"
icon="el-icon-search"
size="mini"
@click="handleQuery"
>搜索</el-button
>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"
>重置</el-button
>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['system:parking:add']"
>新增</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['system:parking:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['system:parking:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['system:parking:export']"
>导出</el-button
>
</el-col>
<right-toolbar
:showSearch.sync="showSearch"
@queryTable="getList"
></right-toolbar>
</el-row>
<el-table
v-if="showtable"
v-loading="loading"
:data="parkingList"
@selection-change="handleSelectionChange"
ref="myTable"
:cell-style="columnbackgroundStyle"
@current-change="handleCurrentChange"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="id" align="center" prop="parkingId" />
<el-table-column label="名称" align="center" prop="parkingName" />
<el-table-column
label="误差距离"
align="center"
prop="error"
:formatter="formatDistance"
/>
<el-table-column label="类型" align="center" prop="type">
<template slot-scope="scope">
<dict-tag
:options="dict.type.et_parking_type"
:value="scope.row.type"
/>
</template>
</el-table-column>
<el-table-column label="照片" align="center" prop="picture" width="100">
<template slot-scope="scope">
<image-preview :src="scope.row.picture" :width="50" :height="50" />
</template>
</el-table-column>
<el-table-column label="状态" align="center" key="status">
<template slot-scope="scope">
<el-switch
v-model="scope.row.status"
active-value="0"
inactive-value="1"
@change="handleStatusChange(scope.row)"
></el-switch>
</template>
</el-table-column>
<!-- <el-table-column label="区域" align="center" prop="areaId" />-->
<el-table-column
label="创建时间"
align="center"
prop="createTime"
width="180"
>
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime, "{y}-{m}-{d}") }}</span>
</template>
</el-table-column>
<el-table-column label="id" align="center" prop="parkingId">
<template slot-scope="scope">
<div :ref="'row_' + scope.row.parkingId">{{ scope.row.parkingId }}</div>
</template>
</el-table-column>
<el-table-column
label="操作"
align="center"
class-name="small-padding fixed-width"
>
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['system:parking:edit']"
>修改</el-button
>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['system:parking:remove']"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</div>
</template>
<script>
import AMapLoader from "@amap/amap-jsapi-loader";
import globalConfig from "@/utils/config/globalConfig";
var polyEditor = "";
function calculateCenter(mapList) {
var total = mapList.length;
var X = 0,
Y = 0,
Z = 0;
mapList.map((item) => {
var lng = (item[0] * Math.PI) / 180;
var lat = (item[1] * Math.PI) / 180;
var x, y, z;
x = Math.cos(lat) * Math.cos(lng);
y = Math.cos(lat) * Math.sin(lng);
z = Math.sin(lat);
X += x;
Y += y;
Z += z;
});
X = X / total;
Y = Y / total;
Z = Z / total;
var Lng = Math.atan2(Y, X);
var Hyp = Math.sqrt(X * X + Y * Y);
var Lat = Math.atan2(Z, Hyp);
let center = new AMap.LngLat((Lng * 180) / Math.PI, (Lat * 180) / Math.PI);
return center;
}
import {
listParking,
getParking,
delParking,
addParking,
updateParking,
changeParkingStatus,
} from "@/api/system/parking";
import {
getArea,
optionselect as getAreaOptionselect,
} from "@/api/system/area";
export default {
name: "AreaMap",
dicts: ["et_parking_type"],
data() {
return {
map: null,
// polyEditor: null,
coordList: "",
timer: "",
status: true,
lon2: null,
lat2: null,
lon: "",
lat: "",
pathList: null,
areaId: null,
parkingList: [],
noParkingList: [],
noridingList: [],
show: false,
labels: [],
markers: [],
areaList: [],
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
parkingList: [],
// id
defaultAreaId: "",
//
title: "",
//
open: false,
// keyarea-map
key: 0,
areaLon: null,
areaLat: null,
//
areaOptions: [],
//
typeOptions: [],
//
queryParams: {
pageNum: 1,
pageSize: 20,
parkingName: null,
areaId: null,
type: null,
},
//
form: {},
highlightedId: null, //
showtable: true,
currentRow: null,
//
rules: {
parkingName: [
{ required: true, message: "禁停区不能为空", trigger: "blur" },
],
type: [{ required: true, message: "类型不能为空", trigger: "blur" }],
boundaryStr: [
{ required: true, message: "边界不能为空", trigger: "blur" },
],
error: [
{
pattern: /^[0-9]*$/,
message: "还车误差必须为正整数",
trigger: "blur",
},
],
},
};
},
// props: ["pathList", "areaId","lon","lat","zoom"],
mounted() {
// if (this.areaId) {
// this.start();
// console.log("");
// } else {
// this.echart();
// console.log("");
// }
},
created() {
const areaId = this.$route.params && this.$route.params.areaId;
this.getArea(areaId);
this.getAreaList();
},
methods: {
getArea(areaId) {
getArea(areaId).then((response) => {
this.queryParams.areaId = response.data.areaId;
this.areaId = response.data.areaId;
this.lon = response.data.longitude;
this.lat = response.data.latitude;
this.areaList = response.data.boundaryStr;
this.defaultAreaId = response.data.areaId;
this.getList();
listParking({ areaId: this.areaId }).then((response) => {
let list = response.rows;
list.forEach((item) => {
if (item.type === "1") {
this.parkingList.push(item);
} else if (item.type === "2") {
this.noParkingList.push(item);
} else if (item.type === "3") {
this.noridingList.push(item);
}
});
this.initAMap();
// setTimeout(() => {
// this.showmap = true;
// this.initAMap();
// }, 300);
});
// this.getList();
});
},
//
cancel() {
this.open = false;
this.reset();
},
//
reset() {
this.form = {
parkingId: null,
parkingName: null,
areaId: null,
boundaryStr: null,
longitude: null,
latitude: null,
createBy: null,
createTime: null,
type: "1",
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
//
handleSelectionChange(selection) {
this.ids = selection.map((item) => item.parkingId);
this.single = selection.length !== 1;
this.multiple = !selection.length;
},
/** 查询字典类型列表 */
getAreaList() {
getAreaOptionselect().then((response) => {
this.areaOptions = response.data;
});
},
formatDistance(row) {
if (typeof row.error === "number") {
return `${row.error}`;
} else {
return "-";
}
},
/** 查询停车区列表 */
getList() {
this.loading = true;
listParking(this.queryParams).then((response) => {
this.parkingList = response.rows;
this.total = response.total;
this.defaultAreaId = this.queryParams.areaId;
getArea(this.defaultAreaId).then((response) => {
this.queryParams.areaId = response.data.areaId;
this.areaLon = response.data.longitude;
this.areaLat = response.data.latitude;
});
this.loading = false;
});
},
setFitView() {
this.map.setFitView(null, false, [150, 60, 100, 60]);
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加停车区";
this.key++;
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const parkingId = row.parkingId || this.ids;
getParking(parkingId).then((response) => {
this.form = response.data;
this.areaLon = response.data.longitude;
this.areaLat = response.data.latitude;
this.open = true;
this.title = "修改停车区";
this.key++;
});
},
/** 删除按钮操作 */
handleDelete(row) {
const parkingIds = row.parkingId || this.ids;
this.$modal
.confirm('是否确认删除停车区编号为"' + parkingIds + '"的数据项?')
.then(function () {
return delParking(parkingIds);
})
.then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
})
.catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
this.download(
"system/parking/export",
{
...this.queryParams,
},
`parking_${new Date().getTime()}.xlsx`
);
},
handleSelectionChange(selection) {
this.ids = selection.map((item) => item.parkingId);
this.single = selection.length !== 1;
this.multiple = !selection.length;
},
showMarkers() {
if (this.show) {
this.labels.forEach((label) => {
label.show();
});
} else {
this.labels.forEach((label) => {
label.hide();
});
}
this.show = !this.show;
const elements = document.querySelectorAll(".apiary-marker-name");
elements.forEach((element) => {
element.style.visibility = this.show ? "hidden" : "visible";
});
},
clearPolygon() {
this.status = false;
if (polyEditor) {
polyEditor.close(); //
}
//
this.map.getAllOverlays("polygon").forEach((polygon) => {
this.map.remove(polygon);
});
//
this.coordList = "";
this.$emit("mapList", "");
this.$emit("center", "");
},
changeMapStyle() {
//
let defaultLayer = new AMap.TileLayer();
//
let satelliteLayer = new AMap.TileLayer.Satellite();
let roadNetLayer = new AMap.TileLayer.RoadNet();
//
this.map.add(defaultLayer);
//
let currentLayer = this.map.getLayers()[0]; //
//
if (this.type == "default") {
// console.log(1111111)
//
this.map.setLayers([defaultLayer]); //
this.type = "Satellite";
} else {
console.log(222222);
this.map.setLayers([satelliteLayer, roadNetLayer]); //
this.type = "default";
}
},
start() {
this.timer = setInterval(this.echart, 1000); // : ;
},
async echart() {
clearInterval(this.timer);
console.log("接收参数", this.pathList);
console.log("接收参数", this.lon);
console.log("接收参数", this.lat);
this.lon2 = this.lon === null ? 120.35218 : this.lon;
this.lat2 = this.lat === null ? 26.944335 : this.lat;
// console.log(typeof JSON.parse(this.pathList));
await AMapLoader.load({
key: globalConfig.aMap.key, // WebKey load
version: "2.0", // JSAPI 1.4.15
plugins: [
"AMap.ToolBar",
"AMap.Driving",
"AMap.PolygonEditor",
"AMap.PlaceSearch",
], // 使'AMap.Scale'
})
.then((AMap) => {
this.map = new AMap.Map("container", {
//id
viewMode: "3D", //3D
zoom: this.zoom ? this.zoom : 13, //
center: [this.lon2, this.lat2], //
});
this.parkingList.forEach((parking) => {
this.addParking(
JSON.parse(parking.boundaryStr) || [],
parking.parkingName,
parking.longitude,
parking.latitude
);
this.addMarker2(
parking,
"https://lxnapi.ccttiot.com/FqcYf6ecsnbC0OT6YYAF5npgu-kh",
parking.parkingName,
"#1890ff"
);
});
this.noParkingList.forEach((noparking) => {
this.addNoParking(JSON.parse(noparking.boundaryStr) || []);
this.addMarker2(
noparking,
"https://lxnapi.ccttiot.com/FjKE5PWbnEnZUq3k-wVIvV4lv8Ab",
noparking.parkingName,
"#ff4444"
);
});
this.noridingList.forEach((noriding) => {
this.addNoriding(
JSON.parse(noriding.boundaryStr) || [],
noriding.parkingName,
noriding.longitude,
noriding.latitude
);
this.addMarker2(
noriding,
"https://lxnapi.ccttiot.com/FmX1diEPPbFYe1vcUfKp6qbKzzh2",
noriding.parkingName,
"#ffcc00"
);
});
})
.catch((e) => {
console.log(e);
});
this.initEditor();
},
initAMap() {
AMapLoader.load({
key: globalConfig.aMap.key,
version: "2.0",
plugins: [
"AMap.ToolBar",
"AMap.Driving",
"AMap.PolygonEditor",
"AMap.PlaceSearch",
], // 使'AMap.Scale'
})
.then((AMap) => {
this.map = new AMap.Map("container", {
viewMode: "3D",
zoom: 13,
center: [this.lon, this.lat],
});
this.infoWindow = new AMap.InfoWindow({
offset: new AMap.Pixel(0, -30),
});
// this.deviceMarker(this.areaId);
// this.areaList.forEach(area => {
// });
this.addArea(JSON.parse(this.areaList) || []);
this.parkingList.forEach((parking) => {
this.addParking(
JSON.parse(parking.boundaryStr) || [],
parking.parkingName,
parking.longitude,
parking.latitude,
parking.parkingId
);
this.addMarker2(
parking,
"https://lxnapi.ccttiot.com/FqcYf6ecsnbC0OT6YYAF5npgu-kh",
parking.parkingName,
"#1890ff"
);
});
this.noParkingList.forEach((noparking) => {
this.addNoParking(JSON.parse(noparking.boundaryStr) || [], noparking.parkingId);
this.addMarker2(
noparking,
"https://lxnapi.ccttiot.com/FjKE5PWbnEnZUq3k-wVIvV4lv8Ab",
noparking.parkingName,
"#ff4444"
);
});
this.noridingList.forEach((noriding) => {
this.addNoriding(
JSON.parse(noriding.boundaryStr) || [],
noriding.parkingName,
noriding.longitude,
noriding.latitude,
noriding.parkingId
);
this.addMarker2(
noriding,
"https://lxnapi.ccttiot.com/FmX1diEPPbFYe1vcUfKp6qbKzzh2",
noriding.parkingName,
"#ffcc00"
);
});
})
.catch((e) => {
console.log(e);
});
},
addArea(data) {
let polygon = new AMap.Polygon({
path: data,
fillColor: "#ccebc5",
strokeOpacity: 1,
fillOpacity: 0.5,
strokeColor: "#2b8cbe",
strokeWeight: 1,
strokeStyle: "dashed",
strokeDasharray: [5, 5],
});
this.map.add(polygon);
},
addParking(data, title, lon, lat, parkingId) {
let polygon = new AMap.Polygon({
path: data,
fillColor: "#ccebc5",
strokeOpacity: 1,
fillOpacity: 0.5,
strokeColor: "#3b7ed9",
strokeWeight: 2,
strokeStyle: "solid",
strokeDasharray: [5, 5],
});
polygon.on("mouseover", () => {
polygon.setOptions({
fillOpacity: 0.7,
fillColor: "#71b7cc",
});
});
polygon.on("mouseout", () => {
polygon.setOptions({
fillOpacity: 0.5,
fillColor: "#a7c1d0",
});
});
polygon.on("click", () => {
//
this.highlightRow(parkingId);
});
this.map.add(polygon);
},
highlightRow(parkingId) {
this.$set(this, "highlightedId", parkingId);
// this.showtable = false;
this.$refs.myTable.setCurrentRow(this.parkingList[6]);
},
handleCurrentChange(val) {
this.currentRow = val;
},
columnbackgroundStyle({ row }) {
if (row.parkingId == this.highlightedId) {
//1
return "background:#f0f9eb;";
}
},
addNoParking(data,parkingId) {
let polygon = new AMap.Polygon({
path: data,
fillColor: "#ccebc5",
strokeOpacity: 1,
fillOpacity: 0.5,
strokeColor: "#ff0000",
strokeWeight: 2,
strokeStyle: "solid",
strokeDasharray: [5, 5],
});
polygon.on("mouseover", () => {
polygon.setOptions({
fillOpacity: 0.7,
fillColor: "#ff0000",
});
});
polygon.on("mouseout", () => {
polygon.setOptions({
fillOpacity: 0.5,
fillColor: "#cc7b7b",
});
});
polygon.on("click", () => {
//
this.highlightRow(parkingId);
});
this.map.add(polygon);
},
addMarker2(parking, icon, title, color) {
let marker = new AMap.Marker({
map: this.map,
icon: new AMap.Icon({
image: icon,
size: new AMap.Size(25, 36),
imageSize: new AMap.Size(25, 36),
}),
position: [parking.longitude, parking.latitude],
offset: new AMap.Pixel(-12.5, -36),
});
this.markers.push(marker);
let text = new AMap.Text({
text: title,
anchor: "center",
position: [parking.longitude, parking.latitude],
offset: new AMap.Pixel(0, -50),
style: {
"background-color": color,
border: "none",
"border-radius": "5px",
color: "white",
"font-size": "12px",
},
});
this.map.add(text);
this.labels.push(text);
},
addNoriding(data, title, lon, lat,parkingId) {
let polygon = new AMap.Polygon({
path: data,
fillColor: "#ccebc5",
strokeOpacity: 1,
fillOpacity: 0.5,
strokeColor: "#ffcc00",
strokeWeight: 2,
strokeStyle: "solid",
strokeDasharray: [5, 5],
});
polygon.on("mouseover", () => {
polygon.setOptions({
fillOpacity: 0.7,
fillColor: "#FFEBA4FF",
});
});
polygon.on("mouseout", () => {
polygon.setOptions({
fillOpacity: 0.5,
fillColor: "#ffeba4",
});
});
polygon.on("click", () => {
//
this.highlightRow(parkingId);
});
this.map.add(polygon);
},
initEditor() {
var path1 = [];
if (this.areaId) {
path1 = JSON.parse(this.pathList) || [];
}
var polygon1 = new AMap.Polygon({
path: path1,
});
this.map.add([polygon1]);
polyEditor = new AMap.PolygonEditor(this.map);
// console.log(polyEditor);
// console.dir(polyEditor);
polyEditor.on("add", (data) => {
// console.log(data);
this.coordList = data.lnglat;
var polygon = data.target;
polygon.on("dblclick", () => {
polyEditor.setTarget(polygon);
polyEditor.open();
});
});
polygon1.on("dblclick", () => {
polyEditor.setTarget(polygon1);
polyEditor.open();
});
return polyEditor;
},
createPolygon() {
this.status = true;
polyEditor.close();
polyEditor.setTarget();
polyEditor.open();
},
editClose: function () {
// console.log("this", this);
let that = this;
polyEditor.on("end", function (event) {
// event.target
//
this.coordList = event.target.getPath();
// console.log("coordList", this.coordList);
let mapList = [];
this.coordList.forEach((v) => {
// console.log("v", v.lng, "--", v.lat);
mapList.push([v.lng, v.lat]);
});
let center = calculateCenter(mapList);
console.log(mapList, "mapListmapList");
that.$emit("mapList", mapList);
that.$emit("center", center);
});
polyEditor.close();
},
},
beforeDestroy() {
clearInterval(this.timer);
},
};
</script>
<style lang="scss" scoped>
#container {
width: 100%;
height: 30rem;
}
.input-card {
position: absolute;
top: 15px;
right: 15px;
}
.highlight-row {
background-color: #f0f9eb; /* 浅绿色背景 */
}
.container {
position: relative;
border: 1px solid rgb(204, 204, 204);
padding-bottom: 50px;
.input-card-right {
position: absolute;
bottom: 15px;
right: 15px;
}
.input-card-left {
position: absolute;
top: 5px;
left: 20px;
font-size: 13px;
line-height: 20px;
}
}
</style>

View File

@ -95,6 +95,13 @@
<span>去设置</span> <span>去设置</span>
</router-link> </router-link>
</template> </template>
</el-table-column>
<el-table-column label="运营区区域范围设置" align="center" :show-overflow-tooltip="true">
<template slot-scope="scope">
<router-link :to="'/system/areaRange/index/' + scope.row.areaId" class="link-type">
<span>去设置</span>
</router-link>
</template>
</el-table-column> </el-table-column>
<el-table-column label="收费方式" align="center" prop="ruleStr" :show-overflow-tooltip="true" /> <el-table-column label="收费方式" align="center" prop="ruleStr" :show-overflow-tooltip="true" />
<!-- <el-table-column label="联系人" align="center" prop="contact" />--> <!-- <el-table-column label="联系人" align="center" prop="contact" />-->

View File

@ -76,7 +76,7 @@
<el-table-column prop="areaName" label="名下运营区" align="center" ></el-table-column> <el-table-column prop="areaName" label="名下运营区" align="center" ></el-table-column>
<el-table-column prop="phone" label="联系电话" align="center" ></el-table-column> <el-table-column prop="phone" label="联系电话" align="center" ></el-table-column>
<el-table-column prop="appName" label="小程序" align="center" ></el-table-column> <el-table-column prop="appName" label="小程序" align="center" ></el-table-column>
<el-table-column prop="userName" label="绑定提现账户" align="center" ></el-table-column> <!-- <el-table-column prop="userName" label="绑定提现账户" align="center" ></el-table-column>-->
<el-table-column prop="separateAccount" label="独立支付" align="center" width="100"> <el-table-column prop="separateAccount" label="独立支付" align="center" width="100">
<template slot-scope="scope"> <template slot-scope="scope">
<dict-tag :options="dict.type.sys_yes_no" :value="scope.row.separateAccount"/> <dict-tag :options="dict.type.sys_yes_no" :value="scope.row.separateAccount"/>
@ -156,28 +156,28 @@
<el-input v-model="form.deptName" placeholder="请输入运营商名称" /> <el-input v-model="form.deptName" placeholder="请输入运营商名称" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <!-- <el-col :span="12">-->
<el-form-item label="绑定提现账户" prop="phonenumber" label-width="120"> <!-- <el-form-item label="绑定提现账户" prop="phonenumber" label-width="120">-->
<el-select <!-- <el-select-->
style="width: 80%" <!-- style="width: 80%"-->
ref="headerSearchSelect" <!-- ref="headerSearchSelect"-->
v-model="form.appUserId" <!-- v-model="form.appUserId"-->
:remote-method="queryPhonenumber" <!-- :remote-method="queryPhonenumber"-->
filterable <!-- filterable-->
default-first-option <!-- default-first-option-->
remote <!-- remote-->
:loading="loading2" <!-- :loading="loading2"-->
placeholder="输入手机号搜索" <!-- placeholder="输入手机号搜索"-->
class="header-search-select" > <!-- class="header-search-select" >-->
<el-option <!-- <el-option-->
v-for="item in options" <!-- v-for="item in options"-->
:key="item.value" <!-- :key="item.value"-->
:label="item.label+'---'+item.appName" <!-- :label="item.label+'-&#45;&#45;'+item.appName"-->
:value="item.value"> <!-- :value="item.value">-->
</el-option> <!-- </el-option>-->
</el-select> <!-- </el-select>-->
</el-form-item> <!-- </el-form-item>-->
</el-col> <!-- </el-col>-->
</el-row> </el-row>
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">

File diff suppressed because it is too large Load Diff

View File

@ -702,12 +702,25 @@ export default {
}, },
returnVehicle(row){ returnVehicle(row){
// console.log('returnVehicle--------------'+JSON.stringify(row)) // console.log('returnVehicle--------------'+JSON.stringify(row))
this.$modal.confirm('是否确认辅助还车MAC为' + row.sn + '"的设备吗?').then(function() { this.$modal.confirm('是否确认辅助还车MAC为' + row.sn + '的设备吗?').then(() => {
return returnVehicle({orderNo:row.orderNo,returnType:"2"}); //
}).then(() => { const loading = this.$loading({
this.getList(); lock: true,
this.$modal.msgSuccess("操作成功"); text: '加载中...',
spinner: 'custom-loading-spinner',
background: 'rgba(0, 0, 0, 0.7)'
});
return returnVehicle({ orderNo: row.orderNo, returnType: "2" })
.then(() => {
this.getList();
this.$modal.msgSuccess("操作成功");
}).finally(() => {
//
loading.close();
});
}).catch(() => { }).catch(() => {
//
}); });
}, },
formatDistance(row){ formatDistance(row){
@ -979,4 +992,24 @@ export default {
padding: 10px !important; padding: 10px !important;
} }
.custom-loading-spinner {
border: 6px solid rgba(0, 0, 0, 0.1);
border-left-color: #409EFF;
border-radius: 50%;
display: inline-block;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style> </style>