| | |
| | | <script setup name="Realtime"> |
| | | import { ref, onMounted, reactive, computed, watchEffect } from "vue"; |
| | | import lockRecord from "./lockRecord.vue"; |
| | | import useWebSocket from "@/hooks/useWebsocket.js"; |
| | | import { ref, onMounted, reactive, watch, computed, watchEffect, onActivated } from "vue"; |
| | | import lockRecord from "./lockRecord.vue"; |
| | | import useWebSocket from "@/hooks/useWebsocket.js"; |
| | | import stationList from '@/components/stationList.vue'; |
| | | import useElement from "@/hooks/useElement.js"; |
| | | import { useRouter, useRoute } from "vue-router"; |
| | | const router = useRouter(); |
| | | const route = useRoute(); |
| | | |
| | | import useElement from "@/hooks/useElement.js"; |
| | | import settings from './settings.vue'; |
| | | |
| | | const { $loading, $message, $confirm } = useElement(); |
| | | const { sendData, message: listMessage } = useWebSocket("real"); |
| | | import { storeToRefs } from "pinia"; |
| | | import { getStationBaojiInfo } from "@/api/station.js"; |
| | | |
| | | const currentAreaId = ref(); |
| | | const tableData = ref([]); |
| | | const pageNum = ref(1); |
| | | const pageSize = ref(10); |
| | | const total = ref(0); |
| | | const offline_num = ref(0); |
| | | const online_num = ref(0); |
| | | const sum = ref(0); |
| | | const open_num = ref(0); |
| | | const close_num = ref(0); |
| | | const unLoad_num = ref(0); |
| | | const recordVisible = ref(false); |
| | | const recordTitle = ref('开启关闭记录'); |
| | | const currLockId = ref(); |
| | | import { useUserStore } from '@/store/user.js'; |
| | | const userStore = useUserStore(); |
| | | const { urole, permits } = storeToRefs(userStore); |
| | | |
| | | function itemClickHandler(item) { |
| | | // console.log(item, '====item', item.data); |
| | | // areaId lockName lockState lockType pageNum pageSize |
| | | currentAreaId.value = item.data.id; |
| | | sendMessage(); |
| | | import isHasPermit from '@/utils/isHasPermit'; |
| | | import { |
| | | lockOpen, |
| | | openLockBl, |
| | | closeLockBl, |
| | | } from '@/api/lockManager.js'; |
| | | import svgStation from '@/components/svg/svgStation.vue'; |
| | | |
| | | // 是否有控制权限 |
| | | let isCanControl = isHasPermit("control_permit", permits.value); |
| | | |
| | | const { $loading, $message, $confirm, $confirmPwdDo } = useElement(); |
| | | const { sendData, message: listMessage } = useWebSocket("real"); |
| | | |
| | | const baojiId = ref(); |
| | | const stationId = ref(); |
| | | const tableData = ref([]); |
| | | const pageNum = ref(1); |
| | | const pageSize = ref(10); |
| | | const total = ref(0); |
| | | const offline_num = ref(0); |
| | | const online_num = ref(0); |
| | | const sum = ref(0); |
| | | const open_num = ref(0); |
| | | const close_num = ref(0); |
| | | const unLoad_num = ref(0); |
| | | const recordVisible = ref(false); |
| | | const recordTitle = ref('开启关闭记录'); |
| | | const currLockId = ref(); |
| | | const queryFlag = ref(0); |
| | | const activeName = ref('topology'); |
| | | const settingsVisible = ref(false); |
| | | |
| | | function itemClickHandler(item) { |
| | | console.log(item, '====item', item); |
| | | const [_baojiId, _stationId] = item.id.split('-'); |
| | | // TODO |
| | | // areaId lockName lockState lockType pageNum pageSize |
| | | baojiId.value = _baojiId; |
| | | stationId.value = _stationId; |
| | | getStationInfo(); |
| | | sendMessage(); |
| | | } |
| | | |
| | | const stationName = ref(''); |
| | | const locationInfo = ref(); |
| | | |
| | | watchEffect(() => { |
| | | let _total = 0; |
| | | if (listMessage.value) { |
| | | let { |
| | | pageInfo, |
| | | offLineNum, |
| | | unLoadNum, |
| | | sumLinf, |
| | | openNum, |
| | | onlineNum, |
| | | closeNum |
| | | } = JSON.parse(listMessage.value)?.data2; |
| | | |
| | | let control = JSON.parse(listMessage.value)?.data3 || []; |
| | | let all = JSON.parse(listMessage.value)?.data4 || []; |
| | | |
| | | locationInfo.value = { |
| | | control, |
| | | all |
| | | }; |
| | | |
| | | offline_num.value = offLineNum; |
| | | online_num.value = onlineNum; |
| | | sum.value = sumLinf; |
| | | open_num.value = openNum; |
| | | close_num.value = closeNum; |
| | | unLoad_num.value = unLoadNum; |
| | | |
| | | _total = pageInfo.total; |
| | | tableData.value = pageInfo.list.map(v => ({ |
| | | ...v, |
| | | onlineState: ['离线', '在线'][v.lockOnline], |
| | | state: { 0: '已闭锁', 1: '已开锁' }[v.lockState], |
| | | onlineState: { 0: '离线', 1: '在线' }[v.lockOnline], |
| | | modelStr: v.model == 1 ? '在线模式' : '离线模式', |
| | | blStateStr: v.blState == 0 ? '蓝牙关闭' : '蓝牙开启', |
| | | openTime: v.lockState == 1 ? v.lastUpdateTime : '--', |
| | | closeTime: v.lockState == 0 ? v.lastUpdateTime : '--', |
| | | })); |
| | | } |
| | | |
| | | watchEffect(() => { |
| | | let _total = 0; |
| | | if (listMessage.value) { |
| | | let { |
| | | pageInfo, |
| | | offLineNum, |
| | | unLoadNum, |
| | | sumLinf, |
| | | openNum, |
| | | onlineNum, |
| | | closeNum |
| | | } = JSON.parse(listMessage.value)?.data2; |
| | | total.value = _total; |
| | | }); |
| | | |
| | | offline_num.value = offLineNum; |
| | | online_num.value = onlineNum; |
| | | sum.value = sumLinf; |
| | | open_num.value = openNum; |
| | | close_num.value = closeNum; |
| | | unLoad_num.value = unLoadNum; |
| | | |
| | | _total = pageInfo.total; |
| | | tableData.value = pageInfo.list.map(v => ({ |
| | | ...v, |
| | | onlineState: ['离线', '在线'][v.lockOnline], |
| | | state: { '-1': '未安装', 0: '已闭锁', 1: '已开锁' }[v.lockState], |
| | | openTime: v.lockState == 1 ? v.lastUpdateTime : '--', |
| | | closeTime: v.lockState == 0 ? v.lastUpdateTime : '--', |
| | | })); |
| | | function getStationInfo() { |
| | | getStationBaojiInfo({ |
| | | baojiId: baojiId.value, |
| | | stationId: stationId.value |
| | | }).then((res) => { |
| | | let { code, data, data2, data3 } = res; |
| | | if (code && data) { |
| | | console.log(data); |
| | | stationName.value = data2.stationName + ' ' + data3.baojiName; |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | console.log(err); |
| | | }); |
| | | |
| | | total.value = _total; |
| | | } |
| | | |
| | | function open(scope) { |
| | | console.log('scope', scope, '============='); |
| | | let { |
| | | row |
| | | } = scope; |
| | | |
| | | $confirmPwdDo(() => { |
| | | let loading = $loading(); |
| | | lockOpen(scope.row.lockId).then((res) => { |
| | | let { code, data } = res; |
| | | loading.close(); |
| | | if (code && data) { |
| | | $message.success("操作成功"); |
| | | sendMessage(); |
| | | } else { |
| | | $message.error("操作失败"); |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | console.log(err); |
| | | loading.close(); |
| | | }); |
| | | |
| | | }); |
| | | } |
| | | |
| | | function viewRecord(data) { |
| | | console.log(data, '999999999999-------'); |
| | | let { row } = data; |
| | | // recordTitle.value = `开启关闭记录 - ${row.areaPath} ${row.lockName}`; |
| | | // currLockId.value = row.lockId; |
| | | // recordVisible.value = true; |
| | | router.push({ |
| | | path: '/device/history', |
| | | query: { |
| | | stationId: stationId.value, |
| | | baojiId: baojiId.value, |
| | | lockId: row.lockId, |
| | | flag: Math.random() |
| | | } |
| | | }); |
| | | |
| | | function test() { |
| | | } |
| | | |
| | | } |
| | | function sendMessage() { |
| | | let params = { |
| | | stationId: stationId.value, |
| | | baojiId: baojiId.value, |
| | | pageNum: pageNum.value, |
| | | pageSize: pageSize.value, |
| | | }; |
| | | sendData(JSON.stringify(params)); |
| | | } |
| | | function handleSizeChange(val) { |
| | | pageSize.value = val; |
| | | sendMessage(); |
| | | } |
| | | |
| | | function viewRecord(data) { |
| | | console.log(data); |
| | | let { row } = data; |
| | | recordTitle.value = `开启关闭记录 - ${row.areaPath} ${row.lockName}`; |
| | | currLockId.value = row.lockId; |
| | | recordVisible.value = true; |
| | | } |
| | | function handleCurrentChange(val) { |
| | | pageNum.value = val; |
| | | sendMessage(); |
| | | } |
| | | |
| | | function sendMessage() { |
| | | let params = { |
| | | areaId: currentAreaId.value, |
| | | pageNum: pageNum.value, |
| | | pageSize: pageSize.value, |
| | | }; |
| | | sendData(JSON.stringify(params)); |
| | | } |
| | | function handleSizeChange(val) { |
| | | pageSize.value = val; |
| | | sendMessage(); |
| | | } |
| | | function openBl(scope) { |
| | | $confirmPwdDo(() => { |
| | | let loading = $loading(); |
| | | openLockBl( |
| | | scope.row.lockId |
| | | ).then((res) => { |
| | | let { code, data } = res; |
| | | loading.close(); |
| | | if (code && data) { |
| | | console.log(data); |
| | | $message.success("操作成功"); |
| | | sendMessage(); |
| | | } else { |
| | | $message.error("操作失败"); |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | loading.close(); |
| | | console.log(err); |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | function handleCurrentChange(val) { |
| | | pageNum.value = val; |
| | | sendMessage(); |
| | | function closeBl(scope) { |
| | | $confirmPwdDo(() => { |
| | | let loading = $loading(); |
| | | closeLockBl( |
| | | scope.row.lockId |
| | | ).then((res) => { |
| | | let { code, data } = res; |
| | | loading.close(); |
| | | if (code && data) { |
| | | console.log(data); |
| | | $message.success("操作成功"); |
| | | sendMessage(); |
| | | } else { |
| | | $message.error("操作失败"); |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | loading.close(); |
| | | console.log(err); |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | function goHistory() { |
| | | router.push({ |
| | | path: '/device/history', |
| | | query: { |
| | | stationId: stationId.value, |
| | | baojiId: baojiId.value, |
| | | flag: Math.random() |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const tree = ref(null); |
| | | |
| | | function settingsOk() { |
| | | settingsVisible.value = false; |
| | | sendMessage(); |
| | | } |
| | | |
| | | onActivated(() => { |
| | | if (queryFlag.value == route.query.flag) { |
| | | return; |
| | | } |
| | | queryFlag.value = route.query.flag || 0; |
| | | if (route.query.stationId && route.query.baojiId) { |
| | | baojiId.value = route.query.baojiId; |
| | | stationId.value = route.query.stationId; |
| | | |
| | | tree.value.setCurrent(baojiId.value + '-' + stationId.value); |
| | | } |
| | | console.log('baojiId, stationId', baojiId.value, stationId.value, '=============real'); |
| | | |
| | | }) |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="page-wrapper"> |
| | | <div class="page-header"> |
| | | <div class="hdw-card-container"> |
| | | <hdw-card title="区域列表" is-full> |
| | | <hdw-tree @item-click="itemClickHandler"></hdw-tree> |
| | | </hdw-card> |
| | | <yc-card title="区域列表" is-full> |
| | | <hdw-tree ref="tree" @item-click="itemClickHandler"></hdw-tree> |
| | | </yc-card> |
| | | </div> |
| | | </div> |
| | | <div class="page-content"> |
| | | <hdw-card is-full> |
| | | <yc-card is-full> |
| | | <div class="page-content-wrapper"> |
| | | <div class="p-title"> |
| | | {{ stationName }} |
| | | <div class="btn-grp"> |
| | | <el-tooltip |
| | | class="item" |
| | | effect="dark" |
| | | content="历史数据" |
| | | placement="bottom" |
| | | > |
| | | <div class="btn" @click="goHistory"> |
| | | <svg-icon icon-class="history"></svg-icon> |
| | | </div> |
| | | </el-tooltip> |
| | | </div> |
| | | </div> |
| | | <div class="page-content-tools"> |
| | | <div class="item"> |
| | | <div class="label">设备总数量</div> |
| | |
| | | <div class="label">离线数量</div> |
| | | <div class="value">{{ offline_num }}</div> |
| | | </div> |
| | | <div class="item"> |
| | | <!-- <div class="item"> |
| | | <div class="label">未安装数量</div> |
| | | <div class="value">{{ unLoad_num }}</div> |
| | | </div> |
| | | </div> --> |
| | | <div class="item"> |
| | | <div class="label">已开锁</div> |
| | | <div class="value">{{ open_num }}</div> |
| | |
| | | <div class="value">{{ close_num }}</div> |
| | | </div> |
| | | </div> |
| | | <div class="page-content-table"> |
| | | <div class="pos-rel"> |
| | | <div class="pos-abs"> |
| | | <el-table stripe :data="tableData" border style="width: 100%; height: 100%"> |
| | | <el-table-column type="index" width="50" /> |
| | | <el-table-column prop="lockId" label="锁具ID" width="180" /> |
| | | <el-table-column prop="lockName" label="锁具名称" width="180" /> |
| | | <el-table-column prop="areaPath" label="关联区域" /> |
| | | <el-table-column prop="onlineState" label="在线状态" width="180" /> |
| | | <el-table-column prop="state" label="状态" width="180" /> |
| | | <el-table-column prop="openTime" label="开启时间" width="180" /> |
| | | <el-table-column prop="closeTime" label="关闭时间" width="180" /> |
| | | <el-table-column align="center" fixed="right" label="操作" width="200"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" size="small" :disabled="scope.row.lockState == -1" |
| | | @click="viewRecord(scope)">历史开启关闭记录</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <el-tabs type="border-card" v-model="activeName" class="tabs"> |
| | | <el-tab-pane name="topology" label="拓扑图"> |
| | | <div class="btn-settings" title="配置" @click="settingsVisible = true"></div> |
| | | <svg-station :rtData="tableData" :locationInfo="locationInfo" class="svg-station"></svg-station> |
| | | </el-tab-pane> |
| | | <el-tab-pane name="list" label="列表"> |
| | | <div class="page-content-table"> |
| | | <div class="pos-rel"> |
| | | <div class="pos-abs"> |
| | | <el-table |
| | | stripe |
| | | :data="tableData" |
| | | border |
| | | style="width: 100%; height: 100%" |
| | | > |
| | | <el-table-column type="index" fixed="left" width="50" /> |
| | | <el-table-column prop="lockId" label="锁具ID" width="180" /> |
| | | <el-table-column |
| | | prop="lockName" |
| | | label="锁具名称" |
| | | width="180" |
| | | /> |
| | | <el-table-column |
| | | prop="onlineState" |
| | | label="在线状态" |
| | | width="180" |
| | | /> |
| | | <el-table-column |
| | | prop="blStateStr" |
| | | label="蓝牙状态" |
| | | width="180" |
| | | /> |
| | | <el-table-column prop="state" label="状态" width="180" /> |
| | | <el-table-column |
| | | prop="openTime" |
| | | label="开启时间" |
| | | width="180" |
| | | /> |
| | | <el-table-column |
| | | prop="closeTime" |
| | | label="关闭时间" |
| | | width="180" |
| | | /> |
| | | <el-table-column |
| | | align="center" |
| | | fixed="right" |
| | | label="操作" |
| | | width="320" |
| | | > |
| | | <template #default="scope"> |
| | | <el-button |
| | | type="danger" |
| | | size="small" |
| | | v-if="isCanControl && scope.row.lockOnline == 1 && scope.row.lockState == 0" |
| | | @click="open(scope)" |
| | | >远程开锁</el-button |
| | | > |
| | | <el-button |
| | | type="primary" |
| | | v-if="isCanControl && scope.row.lockOnline == 1 && scope.row.blState == 0" |
| | | size="small" |
| | | @click="openBl(scope)" |
| | | >开启蓝牙</el-button |
| | | > |
| | | <el-button |
| | | type="primary" |
| | | v-if="isCanControl && scope.row.lockOnline == 1 && scope.row.blState == 1" |
| | | size="small" |
| | | @click="closeBl(scope)" |
| | | >关闭蓝牙</el-button |
| | | > |
| | | <el-button |
| | | type="primary" |
| | | size="small" |
| | | :disabled="scope.row.lockState == -1" |
| | | @click="viewRecord(scope)" |
| | | >历史开启关闭记录</el-button |
| | | > |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="page-content-page"> |
| | | <div class="page-tool"></div> |
| | | <div class="el-page-container"> |
| | | <el-pagination v-model:current-page="pageNum" v-model:page-size="pageSize" |
| | | :page-sizes="[10,20, 40, 60, 80, 100, 200, 300, 400]" size="small" |
| | | layout="total, sizes, prev, pager, next, jumper" :total="total" @size-change="handleSizeChange" |
| | | @current-change="handleCurrentChange" /> |
| | | </div> |
| | | <div class="page-tool"></div> |
| | | </div> |
| | | <div class="page-content-page"> |
| | | <div class="page-tool"></div> |
| | | <div class="el-page-container"> |
| | | <el-pagination |
| | | v-model:current-page="pageNum" |
| | | v-model:page-size="pageSize" |
| | | :page-sizes="[10,20, 40, 60, 80, 100, 200, 300, 400]" |
| | | size="small" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :total="total" |
| | | @size-change="handleSizeChange" |
| | | @current-change="handleCurrentChange" |
| | | /> |
| | | </div> |
| | | <div class="page-tool"></div> |
| | | </div> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | </div> |
| | | </hdw-card> |
| | | </yc-card> |
| | | </div> |
| | | <div class="page-footer"></div> |
| | | <!-- 编辑 新建 --> |
| | | <el-dialog v-model="addEditVisible" :title="addEditTitle" width="500" align-center :close-on-click-modal="false"> |
| | | <add-edit v-if="addEditVisible" :isBatch="isBatch" :info="editLock" :isAdd="isAdd" @close="addEditVisible = false" |
| | | @ok="okHandle"></add-edit> |
| | | </el-dialog> |
| | | <!-- 开闭锁记录 --> |
| | | <el-dialog class="dialog-record" v-model="recordVisible" :close-on-click-modal="false" |
| | | style="display: flex; flex-direction: column; padding: 16px 0 0; height: 500px; width: 1100px;"> |
| | | <template #header> |
| | | <div class="dialog-title"> |
| | | {{ recordTitle }} |
| | | </div> |
| | | </template> |
| | | <lock-record v-if="recordVisible" :lockId="currLockId"></lock-record> |
| | | <!-- 配置机柜 --> |
| | | <el-dialog |
| | | v-model="settingsVisible" |
| | | title="配置" |
| | | width="780" |
| | | align-center |
| | | :close-on-click-modal="false" |
| | | > |
| | | <settings v-if="settingsVisible" :locationInfo="locationInfo" @cancel="settingsVisible = false" @success="settingsOk"></settings> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | |
| | | } |
| | | } |
| | | |
| | | .p-title { |
| | | background: #0ff; |
| | | color: #000; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | height: 36px; |
| | | border-radius: 6px; |
| | | margin-bottom: 8px; |
| | | font-weight: 700; |
| | | position: relative; |
| | | |
| | | .btn-grp { |
| | | position: absolute; |
| | | right: 10px; |
| | | |
| | | .btn { |
| | | cursor: pointer; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .page-content-wrapper { |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: 100%; |
| | | .tabs { |
| | | flex: 1; |
| | | :deep(.el-tab-pane) { |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: 100%; |
| | | position: relative; |
| | | } |
| | | } |
| | | |
| | | .page-content-tools { |
| | | padding-bottom: 8px; |
| | |
| | | } |
| | | |
| | | .hdw-card-container { |
| | | width: 240px; |
| | | width: 320px; |
| | | padding-right: 8px; |
| | | height: 100%; |
| | | } |
| | |
| | | :deep(.el-dialog__body) { |
| | | flex: 1; |
| | | } |
| | | </style> |
| | | :deep(.el-tabs--border-card>.el-tabs__content) { |
| | | padding: 0; |
| | | position: relative; |
| | | } |
| | | .svg-station { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | } |
| | | |
| | | .btn-settings { |
| | | position: absolute; |
| | | z-index: 1; |
| | | right: 200px; |
| | | top: 20px; |
| | | cursor: pointer; |
| | | width: 36px; |
| | | height: 36px; |
| | | background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 1024 1024' %3e%3cpath d='M950.857143 402.285714h-51.748572a403.2 403.2 0 0 0-36.571428-86.308571l36.571428-36.571429a73.142857 73.142857 0 0 0 0-103.314285l-51.017142-51.931429a73.142857 73.142857 0 0 0-103.314286 0l-36.571429 36.571429A393.691429 393.691429 0 0 0 621.714286 125.074286V73.142857a73.142857 73.142857 0 0 0-73.142857-73.142857h-73.142858a73.142857 73.142857 0 0 0-73.142857 73.142857v51.931429a393.691429 393.691429 0 0 0-86.308571 35.657143l-36.571429-36.571429a73.142857 73.142857 0 0 0-103.314285 0L124.16 175.908571a73.142857 73.142857 0 0 0 0 103.314286l36.571429 36.571429a403.2 403.2 0 0 0-36.571429 86.308571H73.142857a73.142857 73.142857 0 0 0-73.142857 73.142857v73.142857a73.142857 73.142857 0 0 0 73.142857 73.142858h51.748572a403.2 403.2 0 0 0 36.571428 86.308571l-36.571428 36.571429a73.142857 73.142857 0 0 0 0 103.314285l51.748571 51.748572a73.142857 73.142857 0 0 0 103.314286 0l36.571428-36.571429A393.691429 393.691429 0 0 0 402.285714 898.925714V950.857143a73.142857 73.142857 0 0 0 73.142857 73.142857h73.142858a73.142857 73.142857 0 0 0 73.142857-73.142857v-51.931429a393.691429 393.691429 0 0 0 86.308571-35.657143l36.571429 36.571429a73.142857 73.142857 0 0 0 103.314285 0l51.748572-51.748571a73.142857 73.142857 0 0 0 0-103.314286l-36.571429-36.571429a403.2 403.2 0 0 0 36.571429-86.308571H950.857143a73.142857 73.142857 0 0 0 73.142857-73.142857V475.428571a73.142857 73.142857 0 0 0-73.142857-73.142857zM617.142857 613.668571a146.285714 146.285714 0 1 1-3.474286-206.811428 146.285714 146.285714 0 0 1 3.474286 206.811428z' fill='%230ff' %3e%3c/path%3e%3c/svg%3e") center center / contain no-repeat; |
| | | transform-origin: 50% 50%; |
| | | &:hover { |
| | | animation: rotate 12s linear infinite; |
| | | } |
| | | } |
| | | |
| | | @keyframes rotate { |
| | | from { |
| | | transform: rotate(0deg); |
| | | } |
| | | to { |
| | | transform: rotate(360deg); |
| | | } |
| | | } |
| | | </style> |