|
|
@@ -0,0 +1,1161 @@
|
|
|
+<template>
|
|
|
+ <div class="dashboard">
|
|
|
+ <div class="dashboard-header">
|
|
|
+ <div class="community-name">
|
|
|
+ <div class="fixedName"> 智慧大屏演示版</div>
|
|
|
+ <div class="tenantName">{{ tenantName }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="running-days"
|
|
|
+ >已安全守护
|
|
|
+ <RollingNumber :value="todayData.systemGuardDay" :itemHeight="50" :itemWidth="40" />
|
|
|
+ 天</div
|
|
|
+ >
|
|
|
+
|
|
|
+ <div v-if="isSuperAdmin" class="tenant-switcher">
|
|
|
+ <div class="time-info">{{ currentTime }}</div>
|
|
|
+ <a-select
|
|
|
+ v-model:value="selectedTenant"
|
|
|
+ placeholder="请选择"
|
|
|
+ style="width: 150px"
|
|
|
+ :options="tenantList"
|
|
|
+ :getPopupContainer="(trigger: HTMLElement) => trigger.parentNode"
|
|
|
+ @change="handleTenantChange"
|
|
|
+ />
|
|
|
+ <div style="cursor: pointer; font-weight: 600" @click="goToHome">返回首页</div>
|
|
|
+ </div>
|
|
|
+ <div class="data-flow header-flow"></div>
|
|
|
+ </div>
|
|
|
+ <div class="dashboard-content">
|
|
|
+ <div class="block custom-scroll">
|
|
|
+ <div class="data-row">
|
|
|
+ <GuardObjectAgeCard :ageList="todayData.ageList" title="入住老人年龄分布" />
|
|
|
+ <GuardObjectTypeCard :guardList="todayData.guardList" title="检测对象分级" />
|
|
|
+ <MonitorPeopleCountCard :detectedCount="todayData.detectedCount" title="今日检测到人数" />
|
|
|
+ <ElderActivityCard :activity-rate="todayData.activeRate" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="editor-note">
|
|
|
+ <TechCard>
|
|
|
+ <div class="editor-note-hd">长者基础事件备忘录</div>
|
|
|
+ <div class="editor-note-bd">
|
|
|
+ <div>305房间,昨日发生滞留</div>
|
|
|
+ <div>802老人,昨日发生摔倒</div>
|
|
|
+ <div>601房间,昨日如厕异常</div>
|
|
|
+ <div>708房间,昨日发生摔倒</div>
|
|
|
+ <div>503房间,昨日发生滞留</div>
|
|
|
+ <div>915房间,昨日如厕异常</div>
|
|
|
+ <!-- 重复一遍内容以实现无缝滚动 -->
|
|
|
+ <div>305房间,昨日发生滞留</div>
|
|
|
+ <div>802老人,昨日发生摔倒</div>
|
|
|
+ <div>601房间,昨日如厕异常</div>
|
|
|
+ <div>708房间,昨日发生摔倒</div>
|
|
|
+ <div>503房间,昨日发生滞留</div>
|
|
|
+ <div>915房间,昨日如厕异常</div>
|
|
|
+ </div>
|
|
|
+ </TechCard>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- <div class="data-row">
|
|
|
+ <DeviceOnlineRateCard
|
|
|
+ :online-count="todayData.onlineCount"
|
|
|
+ :device-count="todayData.deviceCount"
|
|
|
+ ></DeviceOnlineRateCard>
|
|
|
+ <MonitorPeopleCountCard :detectedCount="todayData.detectedCount"></MonitorPeopleCountCard>
|
|
|
+ </div> -->
|
|
|
+ <!-- <div class="data-row">
|
|
|
+ <GuardObjectTypeCard :guardList="todayData.guardList"></GuardObjectTypeCard>
|
|
|
+ <AlertFallCompareCard
|
|
|
+ :fall-count="todayData.fallingCount"
|
|
|
+ :alert-count="todayData.alarmCount"
|
|
|
+ ></AlertFallCompareCard>
|
|
|
+ </div> -->
|
|
|
+ <!-- <DeviceLocationCard :data="todayData.installPositionList"></DeviceLocationCard> -->
|
|
|
+ </div>
|
|
|
+ <div class="block block-center custom-scroll">
|
|
|
+ <div class="map-container">
|
|
|
+ <div
|
|
|
+ class="map-container-wrapper"
|
|
|
+ :style="{ transform: `scale(${scale})`, transformOrigin: 'center center' }"
|
|
|
+ @click="toDeviceList"
|
|
|
+ >
|
|
|
+ <img class="map-img" src="./assets/img/map.jpg" alt="" />
|
|
|
+ <div class="map-label building-1" @click="toDeviceList">1号楼</div>
|
|
|
+ <div class="map-label building-2" @click="toDeviceList">2号楼</div>
|
|
|
+ <div class="map-label building-3" @click="toDeviceList">3号楼</div>
|
|
|
+ <div class="map-label building-4" @click="toDeviceList">4号楼</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <a-space class="zoom-controls">
|
|
|
+ <ZoomOutOutlined @click="zoomOut" />
|
|
|
+ <RedoOutlined @click="zoomReset" />
|
|
|
+ <ZoomInOutlined @click="zoomIn" />
|
|
|
+ </a-space>
|
|
|
+ </div>
|
|
|
+ <div class="block custom-scroll" style="padding: 10px">
|
|
|
+ <div class="data-row">
|
|
|
+ <DeviceOnlineRateCard
|
|
|
+ :online-count="todayData.onlineCount"
|
|
|
+ :device-count="todayData.deviceCount"
|
|
|
+ />
|
|
|
+ <DeviceLocationCard :data="todayData.installPositionList" />
|
|
|
+ </div>
|
|
|
+ <AlertFallCompareCard
|
|
|
+ :fall-count="todayData.fallingCount"
|
|
|
+ :alert-count="todayData.alarmCount"
|
|
|
+ style="margin-bottom: 10px"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- <div class="data-row">
|
|
|
+ <GuardObjectAgeCard :ageList="todayData.ageList"></GuardObjectAgeCard>
|
|
|
+ <ElderActivityCard :activity-rate="todayData.activeRate"></ElderActivityCard>
|
|
|
+ </div> -->
|
|
|
+
|
|
|
+ <div class="data-line">
|
|
|
+ <HistoryChartCard
|
|
|
+ title="历史告警统计"
|
|
|
+ :dayStatInfo="historyData.alarmHistoryData.dayStatInfo"
|
|
|
+ :monthStatInfo="historyData.alarmHistoryData.monthStatInfo"
|
|
|
+ :mode="alarmMode"
|
|
|
+ :loading="alarmLoading"
|
|
|
+ color="#f39c12"
|
|
|
+ seriesName="告警次数"
|
|
|
+ >
|
|
|
+ <template #extra>
|
|
|
+ <div class="toggle-group">
|
|
|
+ <button
|
|
|
+ :class="['alarm-button', { active: alarmMode === 'day' }]"
|
|
|
+ @click="changeAlarmMode('day')"
|
|
|
+ >最近7天</button
|
|
|
+ >
|
|
|
+ <button
|
|
|
+ :class="['alarm-button', { active: alarmMode === 'month' }]"
|
|
|
+ @click="changeAlarmMode('month')"
|
|
|
+ >最近180天</button
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </HistoryChartCard>
|
|
|
+
|
|
|
+ <HistoryChartCard
|
|
|
+ title="历史跌倒统计"
|
|
|
+ :dayStatInfo="historyData.fallHistoryData.dayStatInfo"
|
|
|
+ :monthStatInfo="historyData.fallHistoryData.monthStatInfo"
|
|
|
+ :mode="fallMode"
|
|
|
+ :loading="fallLoading"
|
|
|
+ color="#e74c3c"
|
|
|
+ seriesName="跌倒次数"
|
|
|
+ >
|
|
|
+ <template #extra>
|
|
|
+ <div class="toggle-group">
|
|
|
+ <button
|
|
|
+ :class="['fall-button', { active: fallMode === 'day' }]"
|
|
|
+ @click="changeFallMode('day')"
|
|
|
+ >最近7天</button
|
|
|
+ >
|
|
|
+ <button
|
|
|
+ :class="['fall-button', { active: fallMode === 'month' }]"
|
|
|
+ @click="changeFallMode('month')"
|
|
|
+ >最近180天</button
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </HistoryChartCard>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="dashboard-footer">
|
|
|
+ <!-- <Copyright
|
|
|
+ company="合肥雷能信息技术有限公司"
|
|
|
+ icp="皖ICP备2024060056号-3"
|
|
|
+ icp-link="https://beian.miit.gov.cn"
|
|
|
+ icp-text="皖ICP备2024060056号-3"
|
|
|
+ font-color="#4774a7"
|
|
|
+ /> -->
|
|
|
+ <div>305房间19:00 发生跌倒事件,请及时处理;</div>
|
|
|
+ <div>802老人17:00 发生异常消失,已处理;</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, onMounted, onUnmounted, watch, computed, watchEffect } from 'vue'
|
|
|
+import DeviceOnlineRateCard from './components/DeviceOnlineRateCard/index.vue'
|
|
|
+import AlertFallCompareCard from './components/AlertFallCompareCard/index.vue'
|
|
|
+import ElderActivityCard from './components/ElderActivityCard/index.vue'
|
|
|
+import MonitorPeopleCountCard from './components/MonitorPeopleCountCard/index.vue'
|
|
|
+import DeviceLocationCard from './components/DeviceLocationCard/index.vue'
|
|
|
+import GuardObjectAgeCard from './components/GuardObjectAgeCard/index.vue'
|
|
|
+import GuardObjectTypeCard from './components/GuardObjectTypeCard/index.vue'
|
|
|
+import HistoryChartCard from './components/HistoryChartCard/index.vue'
|
|
|
+import RollingNumber from './components/RollingNumber/index.vue'
|
|
|
+import { useUserStore } from '@/stores/user'
|
|
|
+import type { TodayData } from './types'
|
|
|
+import { ZoomInOutlined, ZoomOutOutlined, RedoOutlined } from '@ant-design/icons-vue'
|
|
|
+import { useResponsiveLayout } from '@/utils/chartManager'
|
|
|
+import { useDashboardPolling } from '@/hooks/useDashboardPolling'
|
|
|
+import { useDict } from '@/hooks/useDict'
|
|
|
+import * as tenantAPI from '@/api/tenant'
|
|
|
+import type {
|
|
|
+ StatsHomeScreenAlarmHistory,
|
|
|
+ StatsHomeScreenFallHistory,
|
|
|
+ StatsHomeScreenQueryData,
|
|
|
+} from '@/api/stats/types'
|
|
|
+import TechCard from './components/TechCard/index.vue'
|
|
|
+import { set } from 'lodash-es'
|
|
|
+
|
|
|
+const userStore = useUserStore()
|
|
|
+
|
|
|
+const tenantName = computed(() => {
|
|
|
+ if (isSuperAdmin.value) {
|
|
|
+ const selected = tenantList.value.find((item) => item.value === selectedTenant.value)
|
|
|
+ return selected?.label ?? '未知租户'
|
|
|
+ } else {
|
|
|
+ return userStore.userInfo.tenantName ?? '雷能技术'
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+defineOptions({
|
|
|
+ name: 'DashboardPage',
|
|
|
+})
|
|
|
+
|
|
|
+const currentTime = ref('')
|
|
|
+const formatTime = () => {
|
|
|
+ const now = new Date()
|
|
|
+ const year = now.getFullYear()
|
|
|
+ const month = String(now.getMonth() + 1).padStart(2, '0')
|
|
|
+ const day = String(now.getDate()).padStart(2, '0')
|
|
|
+ const hours = String(now.getHours()).padStart(2, '0')
|
|
|
+ const minutes = String(now.getMinutes()).padStart(2, '0')
|
|
|
+ const seconds = String(now.getSeconds()).padStart(2, '0')
|
|
|
+ currentTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ formatTime()
|
|
|
+ const timeInterval = setInterval(formatTime, 1000)
|
|
|
+
|
|
|
+ onUnmounted(() => {
|
|
|
+ clearInterval(timeInterval)
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+const todayData = ref<TodayData>({
|
|
|
+ deviceCount: 0, // 设备总数
|
|
|
+ onlineCount: 0, // 在线设备数
|
|
|
+ systemGuardDay: 0, // 系统守护天数
|
|
|
+ alarmCount: 0, // 告警次数
|
|
|
+ fallingCount: 0, // 跌倒次数
|
|
|
+ detectedCount: 0, // 检测人数
|
|
|
+ activeRate: 0, // 活跃度
|
|
|
+ guardList: [], // 检测对象
|
|
|
+ ageList: [], // 年龄层次
|
|
|
+ installPositionList: [], // 安装位置
|
|
|
+})
|
|
|
+
|
|
|
+useResponsiveLayout()
|
|
|
+
|
|
|
+const isSuperAdmin = ref(
|
|
|
+ userStore.userInfo.userType === 'admin' || userStore.userInfo.userType === 'manager'
|
|
|
+)
|
|
|
+
|
|
|
+const selectedTenant = ref<string | number>(userStore.userInfo.tenantId || '')
|
|
|
+const tenantList = ref<{ label: string; value: string | number }[]>([])
|
|
|
+const currentTenantId = ref<string | number>(selectedTenant.value)
|
|
|
+
|
|
|
+const fetchTenantList = async () => {
|
|
|
+ if (!isSuperAdmin.value) {
|
|
|
+ currentTenantId.value = userStore.userInfo.tenantId || ''
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const res = await tenantAPI.queryTenant({
|
|
|
+ pageNo: 1,
|
|
|
+ pageSize: 1000,
|
|
|
+ })
|
|
|
+
|
|
|
+ tenantList.value = res.data.rows.map((item) => ({
|
|
|
+ label: item.tenantName ?? '',
|
|
|
+ value: item.tenantId ?? '',
|
|
|
+ }))
|
|
|
+
|
|
|
+ selectedTenant.value = tenantList.value[0]?.value || ''
|
|
|
+ currentTenantId.value = selectedTenant.value
|
|
|
+}
|
|
|
+
|
|
|
+fetchTenantList()
|
|
|
+
|
|
|
+const handleTenantChange = () => {
|
|
|
+ if (selectedTenant.value) {
|
|
|
+ currentTenantId.value = selectedTenant.value
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+type RawDayItem = { date: string; fallingCount?: number; alarmCount?: number }
|
|
|
+type RawMonthItem = { month: string; fallingCount?: number; alarmCount?: number }
|
|
|
+
|
|
|
+const todayScreenData = ref<StatsHomeScreenQueryData | null>(null)
|
|
|
+const fallHistoryData = ref<StatsHomeScreenFallHistory | null>(null)
|
|
|
+const alarmHistoryData = ref<StatsHomeScreenAlarmHistory | null>(null)
|
|
|
+let updateFallQueryType: ((mode: 'day' | 'month') => Promise<void>) | null = null
|
|
|
+let updateAlarmQueryType: ((mode: 'day' | 'month') => Promise<void>) | null = null
|
|
|
+
|
|
|
+let pollingInstance: ReturnType<typeof useDashboardPolling> | null = null
|
|
|
+
|
|
|
+watch(
|
|
|
+ currentTenantId,
|
|
|
+ (newTenantId) => {
|
|
|
+ if (pollingInstance) {
|
|
|
+ pollingInstance.stop()
|
|
|
+ pollingInstance = null
|
|
|
+ alarmMode.value = 'day'
|
|
|
+ fallMode.value = 'day'
|
|
|
+ }
|
|
|
+
|
|
|
+ if (newTenantId) {
|
|
|
+ pollingInstance = useDashboardPolling({ tenantId: String(newTenantId) })
|
|
|
+ // pollingInstance.start()
|
|
|
+
|
|
|
+ // 赋值响应式数据
|
|
|
+ watchEffect(() => {
|
|
|
+ todayScreenData.value = pollingInstance?.todayScreenData.value ?? null
|
|
|
+ fallHistoryData.value = pollingInstance?.fallHistoryData.value ?? null
|
|
|
+ alarmHistoryData.value = pollingInstance?.alarmHistoryData.value ?? null
|
|
|
+ })
|
|
|
+
|
|
|
+ updateFallQueryType = pollingInstance.updateFallQueryType
|
|
|
+ updateAlarmQueryType = pollingInstance.updateAlarmQueryType
|
|
|
+
|
|
|
+ todayData.value.systemGuardDay = Math.round(Math.random() * 100) // 守护天数
|
|
|
+
|
|
|
+ setInterval(() => {
|
|
|
+ console.log('🚀🚀🚀 mock 数据更新了')
|
|
|
+ localStorage.setItem('todayData', JSON.stringify(todayData.value))
|
|
|
+ mockData()
|
|
|
+ }, 2000)
|
|
|
+
|
|
|
+ setInterval(() => {
|
|
|
+ todayData.value.systemGuardDay = Math.round(Math.random() * 100) // 守护天数
|
|
|
+ }, 5000)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+)
|
|
|
+
|
|
|
+// mock 数据
|
|
|
+const mockData = () => {
|
|
|
+ // todayData.value.systemGuardDay = Math.round(Math.random() * 100) // 守护天数
|
|
|
+ // 年龄分布
|
|
|
+ todayData.value.ageList = [
|
|
|
+ { ageRange: '60-69岁', count: Math.floor(Math.random() * 50) + 10 },
|
|
|
+ { ageRange: '70-79岁', count: Math.floor(Math.random() * 50) + 10 },
|
|
|
+ { ageRange: '80-89岁', count: Math.floor(Math.random() * 50) + 10 },
|
|
|
+ { ageRange: '90-99岁', count: Math.floor(Math.random() * 50) + 10 },
|
|
|
+ { ageRange: '100岁以上', count: Math.floor(Math.random() * 50) + 10 },
|
|
|
+ ]
|
|
|
+ // 检测对象分级
|
|
|
+ todayData.value.guardList = [
|
|
|
+ { guardType: 'parent', name: '重点对象', count: Math.floor(Math.random() * 50) + 10 },
|
|
|
+ { guardType: 'child', name: '一般对象', count: Math.floor(Math.random() * 50) + 10 },
|
|
|
+ { guardType: 'guardian', name: '普通对象', count: Math.floor(Math.random() * 50) + 10 },
|
|
|
+ ]
|
|
|
+ // 检测人数
|
|
|
+ todayData.value.detectedCount = Math.floor(Math.random() * 500) + 100
|
|
|
+ // 长者活跃度
|
|
|
+ todayData.value.activeRate = Math.round(Math.random() * 100)
|
|
|
+ // 设备在线率
|
|
|
+ todayData.value.onlineCount = Math.round(Math.random() * 2500)
|
|
|
+ todayData.value.deviceCount = 2568
|
|
|
+ // 设备安装位置
|
|
|
+ todayData.value.installPositionList = [
|
|
|
+ { installPosition: 'Bedroom', name: '卧室', count: Math.floor(Math.random() * 50) + 10 },
|
|
|
+ { installPosition: 'LivingRoom', name: '客厅', count: Math.floor(Math.random() * 50) + 10 },
|
|
|
+ { installPosition: 'Restaurant', name: '餐厅', count: Math.floor(Math.random() * 50) + 10 },
|
|
|
+ { installPosition: 'Toilet', name: '卫生间', count: Math.floor(Math.random() * 50) + 10 },
|
|
|
+ ]
|
|
|
+ // 跌倒次数
|
|
|
+ todayData.value.fallingCount = Math.floor(Math.random() * 500) + 100
|
|
|
+ // 报警次数
|
|
|
+ todayData.value.alarmCount = Math.floor(Math.random() * 500) + 100
|
|
|
+
|
|
|
+ const generateRecent7DaysData = () => {
|
|
|
+ const today = new Date()
|
|
|
+ const dayStatInfo = []
|
|
|
+
|
|
|
+ for (let i = 6; i >= 0; i--) {
|
|
|
+ const date = new Date(today)
|
|
|
+ date.setDate(today.getDate() - i)
|
|
|
+ const formattedDate = date.toISOString().split('T')[0]
|
|
|
+ dayStatInfo.push({
|
|
|
+ date: formattedDate,
|
|
|
+ fallingCount: Math.floor(Math.random() * 50) + 10,
|
|
|
+ alarmCount: Math.floor(Math.random() * 50) + 10,
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ return dayStatInfo
|
|
|
+ }
|
|
|
+
|
|
|
+ const generateRecent180DaysMonthlyData = () => {
|
|
|
+ const today = new Date()
|
|
|
+ const monthStatInfo = []
|
|
|
+
|
|
|
+ for (let i = 5; i >= 0; i--) {
|
|
|
+ const date = new Date(today)
|
|
|
+ date.setMonth(today.getMonth() - i)
|
|
|
+ const formattedMonth = date.toISOString().slice(0, 7)
|
|
|
+ monthStatInfo.push({
|
|
|
+ month: formattedMonth,
|
|
|
+ fallingCount: Math.floor(Math.random() * 200) + 50,
|
|
|
+ alarmCount: Math.floor(Math.random() * 200) + 50,
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ return monthStatInfo
|
|
|
+ }
|
|
|
+
|
|
|
+ fallHistoryData.value = {
|
|
|
+ dayStatInfo: generateRecent7DaysData().map((d) => ({
|
|
|
+ date: d.date,
|
|
|
+ fallingCount: d.fallingCount,
|
|
|
+ })),
|
|
|
+ monthStatInfo: generateRecent180DaysMonthlyData().map((m) => ({
|
|
|
+ month: m.month,
|
|
|
+ fallingCount: m.fallingCount,
|
|
|
+ })),
|
|
|
+ }
|
|
|
+
|
|
|
+ alarmHistoryData.value = {
|
|
|
+ dayStatInfo: generateRecent7DaysData().map((d) => ({
|
|
|
+ date: d.date,
|
|
|
+ alarmCount: d.alarmCount,
|
|
|
+ })),
|
|
|
+ monthStatInfo: generateRecent180DaysMonthlyData().map((m) => ({
|
|
|
+ month: m.month,
|
|
|
+ alarmCount: m.alarmCount,
|
|
|
+ })),
|
|
|
+ }
|
|
|
+}
|
|
|
+mockData()
|
|
|
+
|
|
|
+const { fetchDict: fetchDictGuardianship, dictNameMap: guardTypeNameMap } =
|
|
|
+ useDict('guardianship_type')
|
|
|
+
|
|
|
+const { fetchDict: fetchDictInstallPosition, dictNameMap: installPositionNameMap } =
|
|
|
+ useDict('install_position')
|
|
|
+
|
|
|
+Promise.all([fetchDictGuardianship(), fetchDictInstallPosition()])
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => todayScreenData.value,
|
|
|
+ (val) => {
|
|
|
+ console.log('🚀🚀🚀 todayScreenData更新了', val)
|
|
|
+ todayData.value.activeRate = val?.activeRate ?? 0
|
|
|
+ todayData.value.detectedCount = val?.detectedCount ?? 0
|
|
|
+ todayData.value.fallingCount = val?.fallingCount ?? 0
|
|
|
+ todayData.value.alarmCount = val?.alarmCount ?? 0
|
|
|
+ // todayData.value.systemGuardDay = val?.systemGuardDay ?? 0
|
|
|
+ todayData.value.onlineCount = val?.onlineCount ?? 0
|
|
|
+ todayData.value.deviceCount = val?.deviceCount ?? 0
|
|
|
+ // todayData.value.ageList = (val?.ageList && val?.ageList.filter((item) => item.count > 0)) ?? []
|
|
|
+ todayData.value.installPositionList =
|
|
|
+ (val?.installPositionList &&
|
|
|
+ val?.installPositionList.map((item) => ({
|
|
|
+ ...item,
|
|
|
+ name:
|
|
|
+ installPositionNameMap.value[
|
|
|
+ item.installPosition as keyof typeof installPositionNameMap.value
|
|
|
+ ] || '未知',
|
|
|
+ names: installPositionNameMap.value,
|
|
|
+ }))) ??
|
|
|
+ []
|
|
|
+ // todayData.value.guardList =
|
|
|
+ // (val?.guardList &&
|
|
|
+ // val?.guardList.map((item) => ({
|
|
|
+ // ...item,
|
|
|
+ // name:
|
|
|
+ // guardTypeNameMap.value[item.guardType as keyof typeof guardTypeNameMap.value] || '未知',
|
|
|
+ // names: guardTypeNameMap.value,
|
|
|
+ // }))) ??
|
|
|
+ // []
|
|
|
+ },
|
|
|
+ { immediate: true, deep: true }
|
|
|
+)
|
|
|
+
|
|
|
+type StatInfo = { lable: string; count: number }
|
|
|
+type HistoryData = { monthStatInfo: StatInfo[]; dayStatInfo: StatInfo[] }
|
|
|
+
|
|
|
+const historyData = ref<{
|
|
|
+ fallHistoryData: HistoryData
|
|
|
+ alarmHistoryData: HistoryData
|
|
|
+}>({
|
|
|
+ fallHistoryData: { monthStatInfo: [], dayStatInfo: [] },
|
|
|
+ alarmHistoryData: { monthStatInfo: [], dayStatInfo: [] },
|
|
|
+})
|
|
|
+
|
|
|
+// 通用转换函数
|
|
|
+const transformStatInfo = (
|
|
|
+ source: RawDayItem[] | RawMonthItem[],
|
|
|
+ labelKey: 'date' | 'month',
|
|
|
+ countKey: 'fallingCount' | 'alarmCount'
|
|
|
+): StatInfo[] => {
|
|
|
+ if (labelKey === 'date') {
|
|
|
+ return (source as RawDayItem[]).map((item) => ({
|
|
|
+ lable: item.date,
|
|
|
+ count: item[countKey] ?? 0,
|
|
|
+ }))
|
|
|
+ } else {
|
|
|
+ return (source as RawMonthItem[]).map((item) => ({
|
|
|
+ lable: item.month,
|
|
|
+ count: item[countKey] ?? 0,
|
|
|
+ }))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 监听跌倒数据
|
|
|
+watch(
|
|
|
+ () => fallHistoryData.value,
|
|
|
+ (val) => {
|
|
|
+ console.log('🚀🚀🚀 fallHistoryData 更新了', val)
|
|
|
+ historyData.value.fallHistoryData.dayStatInfo = transformStatInfo(
|
|
|
+ val?.dayStatInfo ?? [],
|
|
|
+ 'date',
|
|
|
+ 'fallingCount'
|
|
|
+ )
|
|
|
+ historyData.value.fallHistoryData.monthStatInfo = transformStatInfo(
|
|
|
+ val?.monthStatInfo ?? [],
|
|
|
+ 'month',
|
|
|
+ 'fallingCount'
|
|
|
+ )
|
|
|
+ },
|
|
|
+ { immediate: true, deep: true }
|
|
|
+)
|
|
|
+
|
|
|
+// 监听告警数据
|
|
|
+watch(
|
|
|
+ () => alarmHistoryData.value,
|
|
|
+ (val) => {
|
|
|
+ console.log('🚀🚀🚀 alarmHistoryData 更新了', val)
|
|
|
+ historyData.value.alarmHistoryData.dayStatInfo = transformStatInfo(
|
|
|
+ val?.dayStatInfo ?? [],
|
|
|
+ 'date',
|
|
|
+ 'alarmCount'
|
|
|
+ )
|
|
|
+ historyData.value.alarmHistoryData.monthStatInfo = transformStatInfo(
|
|
|
+ val?.monthStatInfo ?? [],
|
|
|
+ 'month',
|
|
|
+ 'alarmCount'
|
|
|
+ )
|
|
|
+ },
|
|
|
+ { immediate: true, deep: true }
|
|
|
+)
|
|
|
+
|
|
|
+const alarmMode = ref<'day' | 'month'>('day')
|
|
|
+const fallMode = ref<'day' | 'month'>('day')
|
|
|
+
|
|
|
+const alarmLoading = ref(false)
|
|
|
+const fallLoading = ref(false)
|
|
|
+
|
|
|
+const changeAlarmMode = async (mode: 'day' | 'month') => {
|
|
|
+ if (alarmMode.value !== mode) {
|
|
|
+ alarmMode.value = mode
|
|
|
+ alarmLoading.value = true
|
|
|
+ await updateAlarmQueryType?.(mode)
|
|
|
+ alarmLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const changeFallMode = async (mode: 'day' | 'month') => {
|
|
|
+ if (fallMode.value !== mode) {
|
|
|
+ fallMode.value = mode
|
|
|
+ fallLoading.value = true
|
|
|
+ await updateFallQueryType?.(mode)
|
|
|
+ fallLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const toDeviceList = () => {
|
|
|
+ window.open('/device/list', '_blank')
|
|
|
+}
|
|
|
+
|
|
|
+const scale = ref(0.8)
|
|
|
+
|
|
|
+const zoomIn = () => {
|
|
|
+ scale.value += 0.1
|
|
|
+ if (scale.value > 1) {
|
|
|
+ scale.value = 1
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const zoomReset = () => {
|
|
|
+ scale.value = 0.7
|
|
|
+}
|
|
|
+
|
|
|
+const zoomOut = () => {
|
|
|
+ scale.value = Math.max(0.5, scale.value - 0.1)
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ window.addEventListener('resize', handleResize)
|
|
|
+})
|
|
|
+onUnmounted(() => {
|
|
|
+ window.removeEventListener('resize', handleResize)
|
|
|
+})
|
|
|
+
|
|
|
+const handleResize = () => {
|
|
|
+ if (window.innerWidth < 1400) {
|
|
|
+ scale.value = 0.5
|
|
|
+ } else if (window.innerWidth < 1600) {
|
|
|
+ scale.value = 0.6
|
|
|
+ } else {
|
|
|
+ scale.value = 0.7
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const goToHome = () => {
|
|
|
+ window.open('/', '_blank')
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="less">
|
|
|
+@bg-color: #22284a;
|
|
|
+@text-color: #e0e0e0;
|
|
|
+@panel-bg: #0b173f;
|
|
|
+@border-color: #2a3b5a;
|
|
|
+@primary-color: #4dc9e6;
|
|
|
+@secondary-color: #6de4ff;
|
|
|
+@accent-color: #2572ed;
|
|
|
+@success-color: #2ecc71;
|
|
|
+@warning-color: #f39c12;
|
|
|
+@danger-color: #e74c3c;
|
|
|
+@gradient-start: #181c41;
|
|
|
+@gradient-end: #22284a;
|
|
|
+@glow-color: rgba(77, 201, 230, 0.2);
|
|
|
+@data-flow-color: rgba(77, 201, 230, 0.7);
|
|
|
+
|
|
|
+* {
|
|
|
+ margin: 0;
|
|
|
+ padding: 0;
|
|
|
+ box-sizing: border-box;
|
|
|
+ font-family: 'Microsoft YaHei', Arial, sans-serif;
|
|
|
+}
|
|
|
+
|
|
|
+.dashboard {
|
|
|
+ background-color: @bg-color;
|
|
|
+ color: @text-color;
|
|
|
+ height: 100vh;
|
|
|
+ width: 100vw;
|
|
|
+ overflow: hidden;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+
|
|
|
+ &-header {
|
|
|
+ height: 70px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0 20px;
|
|
|
+ background-color: @panel-bg;
|
|
|
+ border: 1px solid @border-color;
|
|
|
+ border-radius: 8px;
|
|
|
+ box-shadow: 0 0 15px rgba(0, 180, 255, 0.2);
|
|
|
+ position: relative;
|
|
|
+ flex-wrap: wrap;
|
|
|
+
|
|
|
+ .community-name {
|
|
|
+ font-size: 24px;
|
|
|
+ background: linear-gradient(90deg, @primary-color, @secondary-color);
|
|
|
+ -webkit-background-clip: text;
|
|
|
+ background-clip: text;
|
|
|
+ -webkit-text-fill-color: transparent;
|
|
|
+ text-shadow: 0 0 10px rgba(109, 228, 255, 0.5);
|
|
|
+ letter-spacing: 2px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ .fixedName {
|
|
|
+ margin-right: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tenantName {
|
|
|
+ max-width: 300px;
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .running-days {
|
|
|
+ position: absolute;
|
|
|
+ top: 70%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ flex-shrink: 0;
|
|
|
+
|
|
|
+ font-size: 24px;
|
|
|
+ font-family: 'Orbitron', 'Segoe UI', sans-serif;
|
|
|
+ color: #6de4ff;
|
|
|
+ text-shadow:
|
|
|
+ 0 0 8px rgba(109, 228, 255, 0.8),
|
|
|
+ 0 0 16px rgba(109, 228, 255, 0.4);
|
|
|
+
|
|
|
+ padding: 12px 24px;
|
|
|
+ border: 1px solid rgba(109, 228, 255, 0.4);
|
|
|
+ border-radius: 10px;
|
|
|
+ background: rgba(20, 30, 60, 0.3);
|
|
|
+ backdrop-filter: blur(6px);
|
|
|
+ box-shadow:
|
|
|
+ 0 0 12px rgba(109, 228, 255, 0.3),
|
|
|
+ inset 0 0 6px rgba(109, 228, 255, 0.2);
|
|
|
+
|
|
|
+ animation: pulse-glow 2s infinite ease-in-out;
|
|
|
+ z-index: 10;
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes pulse-glow {
|
|
|
+ 0%,
|
|
|
+ 100% {
|
|
|
+ box-shadow:
|
|
|
+ 0 0 12px rgba(109, 228, 255, 0.3),
|
|
|
+ inset 0 0 6px rgba(109, 228, 255, 0.2);
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ box-shadow:
|
|
|
+ 0 0 20px rgba(109, 228, 255, 0.6),
|
|
|
+ inset 0 0 10px rgba(109, 228, 255, 0.4);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .time-info {
|
|
|
+ font-size: 20px;
|
|
|
+ color: @primary-color;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .data-flow {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 3px;
|
|
|
+ background: linear-gradient(90deg, transparent, rgba(77, 201, 230, 0.7), transparent);
|
|
|
+ animation: dataFlow 2s linear infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes dataFlow {
|
|
|
+ 0% {
|
|
|
+ transform: translateX(-100%);
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ transform: translateX(100%);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &-content {
|
|
|
+ // padding: 24px;
|
|
|
+ padding: 10px 20px 5px;
|
|
|
+ flex: 1;
|
|
|
+ border-radius: 8px;
|
|
|
+ display: flex;
|
|
|
+ gap: 20px;
|
|
|
+ min-height: 0;
|
|
|
+ overflow-y: auto;
|
|
|
+
|
|
|
+ .block {
|
|
|
+ flex-grow: 1;
|
|
|
+ flex-shrink: 1;
|
|
|
+ flex-basis: 400px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ position: relative;
|
|
|
+ overflow-y: auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ .block-center {
|
|
|
+ flex-grow: 1;
|
|
|
+ flex-shrink: 1;
|
|
|
+ flex-basis: 300px;
|
|
|
+ display: flex;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ .map-container {
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ border-radius: 6px;
|
|
|
+ height: 100%;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: #0b173f;
|
|
|
+ overflow: hidden;
|
|
|
+ user-select: none;
|
|
|
+ &-wrapper {
|
|
|
+ position: relative;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .zoom-controls {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 10px;
|
|
|
+ right: 10px;
|
|
|
+ color: @text-color;
|
|
|
+ font-size: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .map-img {
|
|
|
+ object-fit: contain;
|
|
|
+ display: block;
|
|
|
+ border-radius: 6px;
|
|
|
+ pointer-events: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .map-label {
|
|
|
+ position: absolute;
|
|
|
+ background: rgba(32, 40, 65, 0.9);
|
|
|
+ border: 1px solid @border-color;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 4px 8px;
|
|
|
+ font-size: 20px;
|
|
|
+ color: @secondary-color;
|
|
|
+ font-weight: bold;
|
|
|
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
|
|
+ z-index: 10;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+
|
|
|
+ .building-1 {
|
|
|
+ top: 200px;
|
|
|
+ left: 200px;
|
|
|
+ animation: pulse 2s infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ .building-2 {
|
|
|
+ top: 250px;
|
|
|
+ left: 330px;
|
|
|
+ animation: pulse 2s infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ .building-3 {
|
|
|
+ top: 150px;
|
|
|
+ left: 450px;
|
|
|
+ animation: pulse 2s infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ .building-4 {
|
|
|
+ top: 350px;
|
|
|
+ left: 380px;
|
|
|
+ animation: pulse-red 2s infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes pulse {
|
|
|
+ 0% {
|
|
|
+ box-shadow: 0 0 0 0 rgba(77, 201, 230, 0.4);
|
|
|
+ }
|
|
|
+ 70% {
|
|
|
+ box-shadow: 0 0 0 12px rgba(77, 201, 230, 0);
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ box-shadow: 0 0 0 0 rgba(77, 201, 230, 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes pulse-red {
|
|
|
+ 0% {
|
|
|
+ box-shadow: 0 0 0 0 rgba(231, 76, 60, 0.4);
|
|
|
+ }
|
|
|
+ 70% {
|
|
|
+ box-shadow: 0 0 0 12px rgba(231, 76, 60, 0);
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ box-shadow: 0 0 0 0 rgba(231, 76, 60, 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .data-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1fr 1fr 1fr;
|
|
|
+ gap: 12px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .data-row {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1fr 1fr;
|
|
|
+ gap: 12px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 长者基础事件备忘录
|
|
|
+ .editor-note {
|
|
|
+ color: @text-color;
|
|
|
+ margin-bottom: 10px;
|
|
|
+
|
|
|
+ &-hd {
|
|
|
+ font-weight: bold;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ color: #6de4ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ &-bd {
|
|
|
+ height: 130px;
|
|
|
+ overflow: hidden;
|
|
|
+ position: relative;
|
|
|
+ line-height: 1.5;
|
|
|
+ }
|
|
|
+
|
|
|
+ &-bd::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ pointer-events: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ &-bd > div {
|
|
|
+ animation: scrollUp 12s linear infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes scrollUp {
|
|
|
+ 0% {
|
|
|
+ transform: translateY(100%);
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ transform: translateY(-100%);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .data-line {
|
|
|
+ display: grid;
|
|
|
+ // grid-template-columns: 1fr;
|
|
|
+ grid-template-columns: 1fr 1fr;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &-footer {
|
|
|
+ // height: 20px;
|
|
|
+ // color: @text-color;
|
|
|
+ // color: #6de4ff;
|
|
|
+ // text-align: center;
|
|
|
+ // line-height: 30px;
|
|
|
+ // font-size: 12px;
|
|
|
+ // margin-bottom: 10px;
|
|
|
+
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ div {
|
|
|
+ flex-grow: 1;
|
|
|
+ text-align: center;
|
|
|
+ padding: 10px;
|
|
|
+ border: 3px solid @border-color;
|
|
|
+ margin: 10px 24px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @media (max-width: 1600px) {
|
|
|
+ .dashboard-header .community-name {
|
|
|
+ font-size: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .dashboard-header .running-days {
|
|
|
+ font-size: 18px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .dashboard-header .time-info {
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @media (max-width: 1200px) {
|
|
|
+ .dashboard-content {
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 15px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .dashboard-content .block {
|
|
|
+ min-height: 400px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .dashboard-content .data-grid,
|
|
|
+ .dashboard-content .data-row {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @media (max-width: 768px) {
|
|
|
+ .dashboard-header {
|
|
|
+ height: auto;
|
|
|
+ padding: 15px;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 10px;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .dashboard-header .community-name,
|
|
|
+ .dashboard-header .running-days,
|
|
|
+ .dashboard-header .time-info {
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .dashboard-content {
|
|
|
+ padding: 15px;
|
|
|
+ min-height: calc(100vh - 150px - 50px);
|
|
|
+ }
|
|
|
+
|
|
|
+ .dashboard-content .block {
|
|
|
+ min-height: 300px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .map-label {
|
|
|
+ font-size: 12px !important;
|
|
|
+ padding: 2px 6px !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.toggle-group {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+ justify-content: flex-end;
|
|
|
+
|
|
|
+ button {
|
|
|
+ background: none;
|
|
|
+ border: 1px solid;
|
|
|
+ padding: 4px 10px;
|
|
|
+ border-radius: 4px;
|
|
|
+ font-size: 12px;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .alarm-button {
|
|
|
+ border-color: #f39c12;
|
|
|
+ color: #f39c12;
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ background-color: #f39c12;
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .fall-button {
|
|
|
+ border-color: #e74c3c;
|
|
|
+ color: #e74c3c;
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ background-color: #e74c3c;
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.tenant-switcher) {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ font-size: 14px;
|
|
|
+ .ant-select {
|
|
|
+ background-color: @panel-bg;
|
|
|
+
|
|
|
+ .ant-select-selector {
|
|
|
+ background-color: @panel-bg;
|
|
|
+ border-color: @border-color;
|
|
|
+ color: @text-color;
|
|
|
+ box-shadow: 0 0 5px @glow-color;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.ant-select-open .ant-select-selector {
|
|
|
+ border-color: @accent-color;
|
|
|
+ background-color: @panel-bg;
|
|
|
+
|
|
|
+ .ant-select-selection-item,
|
|
|
+ .ant-select-selection-placeholder {
|
|
|
+ color: @text-color !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .ant-select-arrow {
|
|
|
+ color: @text-color;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .ant-select-dropdown {
|
|
|
+ background-color: @bg-color;
|
|
|
+ color: @text-color;
|
|
|
+ border: 1px solid @border-color;
|
|
|
+ box-shadow: 0 0 10px @glow-color;
|
|
|
+ max-width: 180px;
|
|
|
+
|
|
|
+ .ant-select-item {
|
|
|
+ color: @text-color;
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ transition: background-color 0.2s ease;
|
|
|
+
|
|
|
+ &.ant-select-item-option-selected {
|
|
|
+ background-color: @accent-color;
|
|
|
+ color: @text-color;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background-color: @border-color;
|
|
|
+ color: @text-color;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|
|
|
+
|
|
|
+<style lang="less">
|
|
|
+.custom-scroll {
|
|
|
+ overflow-y: auto;
|
|
|
+
|
|
|
+ &::-webkit-scrollbar {
|
|
|
+ width: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::-webkit-scrollbar-track {
|
|
|
+ background: transparent;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::-webkit-scrollbar-thumb {
|
|
|
+ background-color: transparent;
|
|
|
+ border-radius: 2px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:hover::-webkit-scrollbar-thumb {
|
|
|
+ background-color: #00f0ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::-webkit-scrollbar-button {
|
|
|
+ display: none;
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::-webkit-scrollbar-button:single-button {
|
|
|
+ display: none;
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ scrollbar-width: thin;
|
|
|
+ scrollbar-color: transparent transparent;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ scrollbar-color: #00f0ff transparent;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|