Explorar o código

Merge branch 'feature/2025-03-03-ning' into develop

wenningning hai 2 meses
pai
achega
4be14abd52

+ 2 - 1
src/utils/checkToken.js

@@ -7,6 +7,7 @@ import store from '@/store'
 import router from '@/router'
 import * as API_User from '@/api/user'
 import { MessageBox } from 'element-ui'
+import i18n from '@/utils/i18n'
 
 /**
  * 检查token:
@@ -40,7 +41,7 @@ export default function checkToken(options) {
      * 说明登录已失效、或者cookie有问题,需要重新登录。
      */
     if (!refreshToken || !user) {
-      MessageBox.alert(this.$t('action.loginError'), this.$t('action.permissionError'), {
+      MessageBox.alert(i18n.t('action.loginError'), i18n.t('action.permissionError'), {
         type: 'error',
         callback: () => {
           store.dispatch('user/logout').then(() => {

+ 35 - 109
src/views/customer/components/patientManager.vue

@@ -762,8 +762,9 @@
                     valueGetter: this.hashValueGetter
                 },
                 {   headerName: 'ID', field: 'id', sortable: true, filter: 'agNumberColumnFilter', width: 100},
-                {   headerName: this.$t('customerManage.named'), field: 'named', sortable: true, filter: 'agTextColumnFilter', width: 160},
-                {   headerName: this.$t('customerManage.nickname'), field: 'nickname', sortable: true, filter: 'agTextColumnFilter', width: 130},
+                {   headerName: this.$t('customerManage.named'), field: 'named', sortable: true, filter: 'agTextColumnFilter', width: 160 },
+                // {   headerName: this.$t('customerManage.nickname'), field: 'nickname', sortable: true, filter: 'agTextColumnFilter', width: 130 },
+                {   headerName: this.$t('customerManage.frame'), field: 'full_name', sortable: true, filter: 'agTextColumnFilter', width: 130 },
                 {
                     headerName: this.$t('customerManage.customerStatus'), field: 'status', sortable: true, filterFramework: 'RadioFilter', width: 130,
                     filterParams: {
@@ -775,44 +776,33 @@
                 // lockPinned = true 不能拖动然后固定
                 // resizeable 单元个大小是否可以调整
                 {
-                    headerName: this.$t('member.sex'), field: 'sex', sortable: true, filterFramework: 'RadioFilter', width: 130,
+                    headerName: this.$t('member.sex'), field: 'sex', sortable: true, filterFramework: 'RadioFilter', width: 80,
                     filterParams: {
                         listData: this.sexTransfer
                     },
                     cellRenderer: this.sexRenderer
                 },
 
+                // {
+                //     headerName: this.$t('member.birthday'),
+                //     field: 'birthday',
+                //     sortable: true,
+                //     filter: 'agDateColumnFilter',
+                //     valueFormatter: this.unixDateFormatter,
+                //     filterParams: {
+                //         comparator: (filterLocalDateAtMidnight, cellValue) => { // 所有数据都由服务器端过滤,此处只需返回0 即可
+                //             const celldate = unixToDate(cellValue, 'yyyy-MM-dd 00:00:00')
+                //             return (new Date(celldate).getTime() / 1000) - (filterLocalDateAtMidnight.getTime() / 1000)
+                //         }
+                //     }
+                // },
                 {
-                    headerName: this.$t('member.birthday'),
-                    field: 'birthday',
-                    sortable: true,
-                    filter: 'agDateColumnFilter',
-                    valueFormatter: this.unixDateFormatter,
-                    filterParams: {
-                        comparator: (filterLocalDateAtMidnight, cellValue) => { // 所有数据都由服务器端过滤,此处只需返回0 即可
-                            const celldate = unixToDate(cellValue, 'yyyy-MM-dd 00:00:00')
-                            return (new Date(celldate).getTime() / 1000) - (filterLocalDateAtMidnight.getTime() / 1000)
-                        }
-                    }
-                },
-                {
-                    headerName: this.$t('customerManage.age'), field: 'age', sortable: true, filter: 'agNumberColumnFilter', width: 130
-                },
-                {
-                    headerName: this.$t('customerManage.ageUnit'), field: 'age_unit', sortable: true, filter: 'agTextColumnFilter', width: 130
+                    headerName: this.$t('customerManage.age'), field: 'age', sortable: true, filter: 'agNumberColumnFilter', valueFormatter: this.ageFormatter, width: 110
                 },
-                {headerName: this.$t('customerManage.cardNo'), field: 'card_no', sortable: true, filter: 'agTextColumnFilter'},
-                {headerName: this.$t('customerManage.idType'),
-                  field: 'id_type',
-                  sortable: true,
-                  filter: 'agTextColumnFilter',
-                  valueFormatter: this.idCardFormatter
-                },
-                {headerName: this.$t('customerManage.idNo'), field: 'id_no', sortable: true, filter: 'agNumberColumnFilter', width: 160},
-                {headerName: this.$t('customerManage.frame'), field: 'full_name', sortable: true, filter: 'agTextColumnFilter'},
+                {headerName: this.$t('customerManage.cardNo'), field: 'card_no', sortable: true, filter: 'agTextColumnFilter', width: 130},
                 {headerName: this.$t('member.mobile'), field: 'mobile', sortable: true, filter: 'agTextColumnFilter'},
+                {headerName: this.$t('customerManage.idNo'), field: 'id_no', sortable: true, filter: 'agNumberColumnFilter', valueFormatter: this.idCardFormatter, width: 160},
                 {headerName: this.$t('member.homeAddress'), field: 'address', sortable: true, filter: 'agTextColumnFilter', width: 260},
-
                 {
                     headerName: this.$t('customerManage.inDate'),
                     field: 'in_date',
@@ -839,79 +829,6 @@
                         }
                     }
                 },
-                // {
-                //     headerName: this.$t('action.edit'), field: 'id',
-                //     cellRendererFramework: 'ButtonCellRender',
-                //     cellRendererParams: {
-                //         onClick: this.handleEdit,
-                //         label: this.$t('action.edit'),
-                //         buttonType: 'primary',
-                //         buttonSize: 'mini'
-                //     },
-                //     filter: false,
-                //     pinned: 'right',
-                //     lockPinned: true,
-                //     width: 80,
-                //     resizable: false,
-                //     sortable: false
-                // },
-                // {
-                //     headerName: this.$t('customerManage.outBed'), field: 'id',
-                //     cellRendererFramework: 'ButtonCellRender',
-                //     cellRendererParams: param => {
-                //         return {
-                //             onClick: this.handleOut,
-                //             label: this.$t('customerManage.outBed'),
-                //             buttonType: 'warning',
-                //             buttonSize: 'mini',
-                //             disabled: param.data['status'] !== 0
-                //         }
-                //     },
-                //     filter: false,
-                //     pinned: 'right',
-                //     lockPinned: true,
-                //     width: 80,
-                //     resizable: false,
-                //     sortable: false
-                // },
-                // {
-                //     headerName: this.$t('customerManage.changeBed'), field: 'id',
-                //     cellRendererFramework: 'ButtonCellRender',
-                //     cellRendererParams: param => {
-                //         return {
-                //             onClick: this.handleChangeBed,
-                //             label: this.$t('customerManage.changeBed'),
-                //             buttonType: 'success',
-                //             buttonSize: 'mini',
-                //             disabled: param.data['status'] !== 0
-                //         }
-                //     },
-                //     filter: false,
-                //     pinned: 'right',
-                //     lockPinned: true,
-                //     width: 80,
-                //     resizable: false,
-                //     sortable: false
-                // },
-                // {
-                //     headerName: this.$t('action.delete'), field: 'shop_id',
-                //     cellRendererFramework: 'ButtonCellRender',
-                //     cellRendererParams: param => {
-                //         return {
-                //             onClick: this.deleteSingle,
-                //             label: this.$t('action.delete'),
-                //             buttonType: 'danger',
-                //             buttonSize: 'mini',
-                //             disabled: param.data['member_name'] === 'superadmin'
-                //         }
-                //     },
-                //     pinned: 'right',
-                //     lockPinned: true,
-                //     width: 80,
-                //     resizable: false,
-                //     filter: false,
-                //     sortable: false
-                // }
               { headerName: this.$t('action.handle'), field: 'id',
                 cellRendererFramework: 'ButtonCellRenderList',
                 cellRendererParams: param => {
@@ -973,9 +890,11 @@
             this.rowSelection = 'multiple'
         },
         mounted() {
+            
             window.onresize = this.windowResize()
             this.gridApi = this.gridOptions.api
             this.gridColumnApi = this.gridOptions.columnApi
+            
             this.gridColumnApi.applyColumnState({
                 state: [
                     {
@@ -984,6 +903,7 @@
                     }
                 ]
             })
+           
           this.getShop(this.$store.getters.partId)
 
             this.getCloud()
@@ -1149,16 +1069,22 @@
            * 身份证转换
            */
           idCardFormatter(param) {
-            if (param.value === '身份证') {
-              return this.$t('customerManage.idCard');
-            } else if (param.value === '护照') {
-              return this.$t('customerManage.passport');
-            } else if (param.value === '军人证') {
-              return this.$t('customerManage.servicemanCard');
+            if (param.value === null || param.value === undefined) {
+              return this.$t('customerManage.null');
+            }
+            if (param.data.id_type  === '身份证') {
+              return this.$t('customerManage.idCard') + '-' + param.value;
+            } else if (param.data.id_type === '护照') {
+              return this.$t('customerManage.passport') + '-' + param.value;
+            } else if (param.data.id_type === '军人证') {
+              return this.$t('customerManage.servicemanCard') + '-' + param.value;
             } else {
               return this.$t('customerManage.null');
             }
           },
+          ageFormatter(param) {
+            return param.value + param.data.age_unit
+          },
             handleClick() {
               if (this.activeName === 'footprint') {
                 this.$router.push({path:'/device/map', query: {mapUrl: this.mapUrl, uuid: this.formmodel.uuid, frameId: this.formmodel.frame_id}})

+ 8 - 9
src/views/ncs-device/components/deviceManager.vue

@@ -700,9 +700,6 @@
               })
               _this.allDeviceTypeList = res
               _this.deviceTypeList = data
-              console.log(_this.deviceTypeList)
-              console.log("===================")
-              console.log(this.deviceTypeList)
               if (this.frameType) {
                 _this.filterDeviceType(_this.frameType)
               } else {
@@ -743,7 +740,8 @@
                   filterParams: {
                     listData: this.deviceStatusTransfer
                   },
-                  cellRenderer: this.deviceStatusFormatter
+                  cellRenderer: this.deviceStatusFormatter,
+                  width: 110
                 },
                 {
                   headerName: this.$t('deviceManage.connect'),
@@ -778,7 +776,8 @@
                       }
                     }
                   },
-                  cellRenderer: this.onlineStateFormatter
+                  cellRenderer: this.onlineStateFormatter,
+                  width: 110
                 },
                 {
                   headerName: this.$t('deviceManage.frameFullName'),
@@ -787,14 +786,14 @@
                   filter: 'agTextColumnFilter'
                 },
                 {
-                  headerName: this.$t('deviceManage.phoneNumber'),
-                  field: 'phone_number',
+                  headerName: this.$t('deviceManage.ethMac'),
+                  field: 'eth_mac',
                   sortable: true,
                   filter: 'agTextColumnFilter'
                 },
                 {
-                  headerName: this.$t('deviceManage.ethMac'),
-                  field: 'eth_mac',
+                  headerName: this.$t('deviceManage.phoneNumber'),
+                  field: 'phone_number',
                   sortable: true,
                   filter: 'agTextColumnFilter'
                 },

+ 337 - 44
src/views/ncs-device/watch_location.vue

@@ -11,32 +11,84 @@
         {{ this.$t('watch.getNewPlace') }}</el-button>
       <el-tag v-if="myTitle" style="margin-left: 20px;" type="warning">{{ myTitle }}</el-tag>
     </div>
-    <div>
-      <el-row>
-        <el-col :span="6" v-for="(item, index) in beaconDevices" :key="index">
-          <el-card :id="'myFrame'+item.id" shadow="never" class="myClass">
-            <div :style="item.device_type === getDeviceList().ELECTRONIC_FENCE ? 'color: red' : ''">
-              <svg-icon :id="'myIcon' + item.id" icon-class="footmark" class-name="footmark" style="font-size: 30px;display: none" />
-              <svg-icon icon-class="sickroom" style="font-size: 40px;padding-right: 20px" />{{ item.name }}
+    <div class="location-map">
+      <div class="grid-background"></div>
+      
+      <div class="beacon-grid">
+        <el-card 
+          v-for="item in beaconDevices" 
+          :key="item.id"
+          :id="'myFrame'+item.id" 
+          class="beacon-point"
+          :class="{
+            'active': isBeaconActive(item.id),
+            'fence': item.device_type === 39
+          }"
+          :style="getBeaconStyle(item)">
+          <div :class="['beacon-content', {
+            'active': isBeaconActive(item.id),
+            'fence': item.device_type === 39
+          }]">
+            <div class="beacon-icon">
+              <div :class="['radar-wave', {
+                'fence-wave': item.device_type === 39
+              }]" v-if="isBeaconActive(item.id)"></div>
+              <svg-icon 
+                :id="'myIcon' + item.id" 
+                icon-class="footmark" 
+                class="footmark-icon"
+                :style="{ 
+                  display: isBeaconActive(item.id) ? 'inline-block' : 'none',
+                  color: item.device_type === 39 ? '#fff' : ''
+                }" />
             </div>
-            <div style="float:right">
-              <span :id="'myText' + item.id" ></span>
+            <div class="beacon-name">{{ item.name }}</div>
+            <div :id="'myText' + item.id" class="sequence-number"></div>
+          </div>
+        </el-card>
+      </div>
+      
+      <svg class="path-overlay" :viewBox="viewBox">
+        <path 
+          :d="pathData" 
+          class="movement-path-bg"
+          stroke="#E1E1E1"
+          stroke-width="3"
+          stroke-dasharray="5,5"
+          fill="none" />
+        <path 
+          :d="pathData" 
+          class="movement-path" 
+          fill="none" />
+        <circle 
+          v-if="pathData"
+          class="moving-dot"
+          r="6"
+          fill="#409EFF">
+          <animateMotion
+            :path="pathData"
+            dur="3s"
+            repeatCount="indefinite" />
+        </circle>
+      </svg>
+    </div>
+    <div v-if="locationList.length > 0" class="timeline-container">
+      <el-timeline>
+        <el-timeline-item
+          v-for="(item, index) in locationList"
+          :key="index"
+          :timestamp="formatterCreateTime(item.create_time)"
+          :type="getTimelineItemType(index)">
+          <el-card class="timeline-card" :class="{'current-location': index === 0}">
+            <div class="location-info">
+              <i class="el-icon-location-outline"></i>
+              <span>{{ item.full_name }}</span>
+              <span v-if="index === 0" class="current-badge">当前位置</span>
             </div>
-
           </el-card>
-        </el-col>
-      </el-row>
-    </div>
-    <div v-if="locationList.length > 0" style="margin-top: 20px;">
-      <el-card>
-        <div v-for="(item, index) in locationList" :key="index" class="text item">
-          <i class="el-icon-user-solid"></i> {{ $t('watch.you') }} <el-tag><i class="el-icon-time"></i>{{ formatterCreateTime(item.create_time )}}</el-tag> 在
-          <el-tag type="success"><i class="el-icon-location-outline"></i>{{ item.full_name }}</el-tag>
-          {{ $t('watch.nearby') }}
-        </div>
-      </el-card>
+        </el-timeline-item>
+      </el-timeline>
     </div>
-    <!--翻页-->
     <el-pagination
         v-if="pageData"
         slot="pagination"
@@ -90,7 +142,10 @@ export default {
       queryTime: null,
       websock: null,
       fullscreenLoading: false,
-      myTitle: ''
+      myTitle: '',
+      pathData: '',
+      viewBox: '0 0 1000 1000',
+      gridPositions: null
     }
   },
   watch: {
@@ -120,6 +175,7 @@ export default {
       const _this = this
       getLocationDeviceList(this.$store.getters.partId).then(res => {
         _this.beaconDevices = res
+        _this.initGridPositions()
         _this.API_GetList()
       })
     },
@@ -147,12 +203,31 @@ export default {
       }
       this.tableData.reverse()
       for (let i = 0; i< _this.tableData.length; i++) {
-        (function (t, data) {   // 注意这里是形参
+        (function (t, data) {
           setTimeout(function () {
             if (myId) {
-              document.getElementById(myId).style = 'background: #d3dce6'
+              const prevElement = document.getElementById(myId)
+              if (prevElement) {
+                const prevBeacon = _this.beaconDevices.find(b => b.id === parseInt(myId.replace('myFrame', '')))
+                if (prevBeacon && prevBeacon.device_type === 39) {
+                  prevElement.style.background = '#F56C6C'
+                } else {
+                  prevElement.style.background = '#d3dce6'
+                }
+                prevElement.style.color = prevBeacon?.device_type === 39 ? 'white' : '#333'
+              }
+            }
+            const currentElement = document.getElementById('myFrame'+data.beacon_device_id)
+            if (currentElement) {
+              const beacon = _this.beaconDevices.find(b => b.id === data.beacon_device_id)
+              if (beacon && beacon.device_type === 39) {
+                currentElement.style.background = '#F56C6C'
+                currentElement.style.color = 'white'
+              } else {
+                currentElement.style.background = '#3DCB0A'
+                currentElement.style.color = 'white'
+              }
             }
-            document.getElementById('myFrame'+data.beacon_device_id).style = 'color: white;background: #3DCB0A'
             document.getElementById('myIcon'+data.beacon_device_id).style.display='inline'
             _this.$notify({
               title: data.full_name,
@@ -169,14 +244,13 @@ export default {
             } else {
               text.innerText = (i+1)
             }
-            // document.getElementById('myText'+data.beacon_device_id).innerText = i+1+1
             if (_this.clearList.find(p => p === data.beacon_device_id) == null) {
               _this.clearList.push(data.beacon_device_id)
             }
-
-          }, 250 * t);	// 还是每秒执行一次,不是累加的
-        })(i, _this.tableData[i])   // 注意这里是实参,这里把要用的参数传进去
+          }, 250 * t)
+        })(i, _this.tableData[i])
       }
+      this.updateMovementPath()
     },
     sx() {
       this.queryTime = null
@@ -207,7 +281,14 @@ export default {
     },
     clearStyle() {
       this.clearList.forEach(item => {
-        document.getElementById('myFrame'+item).style = ''
+        const element = document.getElementById('myFrame'+item)
+        const beacon = this.beaconDevices.find(b => b.id === item)
+        if (beacon && beacon.device_type === 39) {
+          element.style.background = '#F56C6C'
+          element.style.color = 'white'
+        } else {
+          element.style = ''
+        }
         document.getElementById('myIcon'+item).style.display='none'
         document.getElementById('myText'+item).innerText = ''
       })
@@ -275,27 +356,239 @@ export default {
     },
     getDeviceList() {
       return DEVICE_TYPE;
+    },
+    initGridPositions() {
+      const totalBeacons = this.beaconDevices.length
+      const cols = Math.ceil(Math.sqrt(totalBeacons))
+      this.gridPositions = this.beaconDevices.reduce((acc, device, index) => {
+        acc[device.id] = {
+          x: (index % cols) + 1,
+          y: Math.floor(index / cols) + 1
+        }
+        return acc
+      }, {})
+    },
+    getBeaconStyle(beacon) {
+      if (!this.gridPositions) this.initGridPositions()
+      const pos = this.gridPositions[beacon.id]
+      return {
+        gridColumn: `${pos.x} / span 1`,
+        gridRow: `${pos.y} / span 1`
+      }
+    },
+    isBeaconActive(beaconId) {
+      return this.locationList.length > 0 && 
+        this.locationList[0].beacon_device_id === beaconId
+    },
+    updateMovementPath() {
+      if (this.locationList.length < 2 || !this.gridPositions) return
+      
+      const points = this.locationList.map(location => {
+        const pos = this.gridPositions[location.beacon_device_id]
+        return `${pos.x * 100},${pos.y * 100}`
+      })
+      
+      this.pathData = `M ${points.join(' L ')}`
+    },
+    getTimelineItemType(index) {
+      if (index === 0) return 'success'
+      if (index === 1) return 'primary'
+      return ''
     }
   }
 }
 </script>
 
-<style scoped type="text/scss">
-.el-row {
-  margin-bottom: 20px;
-&:last-child {
-   margin-bottom: 0;
- }
-}
-.el-col {
-  border-radius: 30px;
+<style lang="scss" scoped>
+.location-map {
+  position: relative;
+  margin: 20px 0;
+  height: 600px;
+  background: #f8fafc;
+  border-radius: 8px;
   padding: 20px;
+  overflow: hidden;
 }
-.text {
-  font-size: 14px;
+
+.grid-background {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-image: 
+    linear-gradient(rgba(200, 200, 200, 0.1) 1px, transparent 1px),
+    linear-gradient(90deg, rgba(200, 200, 200, 0.1) 1px, transparent 1px);
+  background-size: 20px 20px;
+}
+
+.beacon-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+  gap: 20px;
+  height: 100%;
+}
+
+.beacon-point {
+  position: relative;
+  transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
+  background: white;
+  border: none;
+  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
+  
+  &:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 8px 15px -3px rgba(0, 0, 0, 0.1);
+  }
+  
+  &.active {
+    transform: scale(1.05);
+    background: #3DCB0A;
+    box-shadow: 0 0 25px rgba(61, 203, 10, 0.4);
+    
+    .beacon-content {
+      color: white;
+      
+      .sequence-number {
+        color: rgba(255, 255, 255, 0.8);
+      }
+    }
+  }
+  
+  &.fence {
+    background: #F56C6C !important;
+    
+    .beacon-content {
+      color: white !important;
+    }
+    
+    &:hover {
+      box-shadow: 0 8px 15px -3px rgba(245, 108, 108, 0.4);
+    }
+    
+    &.active {
+      box-shadow: 0 0 25px rgba(245, 108, 108, 0.6);
+    }
+  }
+}
+
+.beacon-content {
+  text-align: center;
+  padding: 10px;
+  
+  &.active {
+    color: white;
+  }
+  
+  &.fence {
+    color: white;
+  }
 }
 
-.item {
-  margin-bottom: 18px;
+.beacon-name {
+  margin: 8px 0;
+  font-weight: bold;
+}
+
+.beacon-icon {
+  position: relative;
+  width: 48px;
+  height: 48px;
+  margin: 0 auto 12px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.radar-wave {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  border-radius: 50%;
+  background: rgba(61, 203, 10, 0.2);
+  animation: radar-pulse 2s infinite;
+  
+  &.fence-wave {
+    background: rgba(245, 108, 108, 0.2);
+  }
+}
+
+.footmark-icon {
+  font-size: 24px;
+  margin-bottom: 8px;
+}
+
+.sequence-number {
+  font-size: 12px;
+  color: #666;
+}
+
+.path-overlay {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  pointer-events: none;
+}
+
+.movement-path {
+  stroke: #409EFF;
+  stroke-width: 3;
+  stroke-linecap: round;
+  animation: drawPath 2s ease-out forwards;
+}
+
+.moving-dot {
+  filter: drop-shadow(0 0 4px rgba(64, 158, 255, 0.6));
+}
+
+.timeline-container {
+  margin: 20px 0;
+  max-height: 300px;
+  overflow-y: auto;
+}
+
+.timeline-card {
+  &.current-location {
+    border-left: 4px solid #3DCB0A;
+  }
+}
+
+.location-info {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.current-badge {
+  background: #3DCB0A;
+  color: white;
+  padding: 2px 8px;
+  border-radius: 12px;
+  font-size: 12px;
+  margin-left: 8px;
+}
+
+@keyframes radar-pulse {
+  0% {
+    transform: scale(0.5);
+    opacity: 0.8;
+  }
+  100% {
+    transform: scale(2);
+    opacity: 0;
+  }
+}
+
+@keyframes drawPath {
+  from {
+    stroke-dasharray: 1000;
+    stroke-dashoffset: 1000;
+  }
+  to {
+    stroke-dasharray: 1000;
+    stroke-dashoffset: 0;
+  }
 }
 </style>