2026-07-03 04:10:46 +08:00
|
|
|
<template>
|
|
|
|
|
<view class="home-page">
|
2026-09-19 23:40:46 +08:00
|
|
|
<view class="top-bar" :style="{ paddingTop: statusBarHeight + 'px', height: (statusBarHeight + navBarHeight) + 'px', paddingRight: '110px' }">
|
|
|
|
|
<view class="org-switcher" @click="showOrgPicker = true">
|
|
|
|
|
<text class="org-label">{{ currentOrgName || '选择组织' }}</text>
|
|
|
|
|
<view class="org-arrow"><AppIcon name="arrow_down" :size="12" color="#111827" /></view>
|
2026-07-03 04:10:46 +08:00
|
|
|
</view>
|
2026-07-04 04:03:02 +08:00
|
|
|
<view class="scan-btn" @click="handleScan">
|
2026-09-19 23:40:46 +08:00
|
|
|
<image src="/static/scan-icon.png" style="width: 24px; height: 24px;" />
|
2026-07-03 04:10:46 +08:00
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
2026-09-19 23:40:46 +08:00
|
|
|
<view v-if="selectedOrgId !== null" class="project-filter">
|
|
|
|
|
<view class="filter-inner">
|
|
|
|
|
<view class="filter-tag" :class="{ active: selectedProjectId === null }" @click="selectProject(null)"><text>全部</text></view>
|
|
|
|
|
<view v-for="p in projects" :key="p.id" class="filter-tag" :class="{ active: selectedProjectId === p.id }" @click="selectProject(p.id)"><text>{{ p.name }}</text></view>
|
2026-07-03 04:10:46 +08:00
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
<view class="stats-row">
|
2026-09-19 23:40:46 +08:00
|
|
|
<view class="stat-item"><text class="stat-value primary">{{ Number(stats.online_cabinets) || 0 }}</text><text class="stat-label">在线设备</text></view>
|
|
|
|
|
<view class="stat-item"><text class="stat-value muted">{{ Number(stats.total_channels) || 0 }}</text><text class="stat-label">总通道</text></view>
|
|
|
|
|
<view class="stat-item"><text class="stat-value success">{{ Number(stats.idle_channels) || 0 }}</text><text class="stat-label">空闲</text></view>
|
|
|
|
|
<view class="stat-item"><text class="stat-value primary">{{ Number(stats.charging_count) || 0 }}</text><text class="stat-label">充电中</text></view>
|
|
|
|
|
<view class="stat-item"><text class="stat-value warning">{{ Number(stats.full_count) || 0 }}</text><text class="stat-label">充满</text></view>
|
|
|
|
|
<view class="stat-item"><text class="stat-value danger">{{ Number(stats.fault_count) || 0 }}</text><text class="stat-label">故障</text></view>
|
2026-07-04 04:03:02 +08:00
|
|
|
</view>
|
|
|
|
|
|
2026-09-19 23:40:46 +08:00
|
|
|
<view class="section-header"><text class="section-title">设备列表</text><text class="section-count">{{ cabinets.length }} 台</text></view>
|
|
|
|
|
|
|
|
|
|
<view v-if="cabinets.length === 0 && !loading" class="empty-state">
|
2026-07-04 04:03:02 +08:00
|
|
|
<text class="empty-text">暂无设备</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view v-else class="device-list">
|
2026-09-19 23:40:46 +08:00
|
|
|
<view v-for="cab in cabinets" :key="cab.id" class="device-card" @click="goDetail(cab.id)">
|
|
|
|
|
<view class="device-header">
|
|
|
|
|
<view class="device-id-row">
|
|
|
|
|
<text class="device-id">{{ cab.abstract_id }}</text>
|
|
|
|
|
<view class="status-badge" :class="cab.status === 1 ? 'online' : 'offline'"><text>{{ cab.status === 1 ? '在线' : '离线' }}</text></view>
|
2026-07-04 04:03:02 +08:00
|
|
|
</view>
|
2026-09-19 23:40:46 +08:00
|
|
|
<view class="header-right">
|
|
|
|
|
<text v-if="cab.temp != null" class="temp-badge">{{ cab.temp }}°C</text>
|
|
|
|
|
<view class="signal-inline">
|
|
|
|
|
<view class="signal" :class="signalClass(cab.rssi)">
|
|
|
|
|
<view class="signal-bar l1"></view><view class="signal-bar l2"></view><view class="signal-bar l3"></view><view class="signal-bar l4"></view><view class="signal-bar l5"></view>
|
|
|
|
|
</view>
|
|
|
|
|
<text class="rssi-value">{{ cab.rssi ?? '—' }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="pow-tags">
|
|
|
|
|
<view class="pow-tag" :class="isPowerFail(cab.pow_fail_dc) ? 'off' : 'on'"><text>{{ isPowerFail(cab.pow_fail_dc) ? '直流停电' : 'DC' }}</text></view>
|
|
|
|
|
<view class="pow-tag" :class="isPowerFail(cab.pow_fail_ac) ? 'off' : 'on'"><text>{{ isPowerFail(cab.pow_fail_ac) ? '交流停电' : 'AC' }}</text></view>
|
|
|
|
|
</view>
|
2026-07-04 04:03:02 +08:00
|
|
|
</view>
|
|
|
|
|
</view>
|
2026-09-19 23:40:46 +08:00
|
|
|
<text class="device-name">{{ cab.name || '未命名' }}</text>
|
|
|
|
|
<text v-if="cab.imei" class="device-imei">IMEI: {{ cab.imei }}</text>
|
|
|
|
|
<text v-if="cab.address" class="device-addr">{{ cab.address }}</text>
|
|
|
|
|
<view class="channel-stats">
|
|
|
|
|
<view class="channel-chip total"><AppIcon name="grid" :size="14" color="#6b7280" /><text class="chip-num">{{ Number(cab.channel_count) || 0 }}</text></view>
|
|
|
|
|
<view class="channel-chip idle"><AppIcon name="battery" :size="14" color="#16a34a" /><text class="chip-num">{{ Number(cab.idle_count) || 0 }}</text></view>
|
|
|
|
|
<view class="channel-chip charging"><AppIcon name="battery_charging" :size="14" color="#0f766e" /><text class="chip-num">{{ Number(cab.charging_count) || 0 }}</text></view>
|
|
|
|
|
<view class="channel-chip full"><AppIcon name="battery_full" :size="14" color="#d97706" /><text class="chip-num">{{ Number(cab.full_count) || 0 }}</text></view>
|
|
|
|
|
<view class="channel-chip fault"><AppIcon name="alert" :size="14" color="#dc2626" /><text class="chip-num">{{ Number(cab.fault_count) || 0 }}</text></view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<view v-if="showOrgPicker" class="org-overlay" @click="showOrgPicker = false">
|
|
|
|
|
<view class="org-sheet" @click.stop>
|
|
|
|
|
<view class="org-header"><text class="org-title">切换组织</text><view class="org-close" @click="showOrgPicker = false"><text>✕</text></view></view>
|
|
|
|
|
<scroll-view class="org-list" scroll-y>
|
|
|
|
|
<view class="org-option" :class="{ selected: selectedOrgId === null }" @click="switchOrgAll"><text>全部</text></view>
|
|
|
|
|
<view v-for="org in orgs" :key="org.id" class="org-option" :class="{ selected: selectedOrgId === org.id }" @click="switchOrg(org)"><text>{{ org.name }}</text></view>
|
|
|
|
|
</scroll-view>
|
2026-07-03 04:10:46 +08:00
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2026-07-04 04:03:02 +08:00
|
|
|
import { ref, computed } from 'vue'
|
|
|
|
|
import { onShow, onPullDownRefresh } from '@dcloudio/uni-app'
|
|
|
|
|
import { h5Api, type DashboardData, type CabinetItem, type OrgItem } from '../../api'
|
2026-07-03 04:10:46 +08:00
|
|
|
import { useAuthStore } from '../../store/auth'
|
2026-09-19 23:40:46 +08:00
|
|
|
import AppIcon from '../../components/AppIcon.vue'
|
|
|
|
|
import { isPowerFail } from '../../utils/deviceStatus'
|
|
|
|
|
|
|
|
|
|
const statusBarHeight = ref(47)
|
|
|
|
|
const navBarHeight = ref(44)
|
|
|
|
|
function calcNav() {
|
|
|
|
|
try {
|
|
|
|
|
const sys = wx.getSystemInfoSync()
|
|
|
|
|
statusBarHeight.value = sys.statusBarHeight || 47
|
|
|
|
|
const btn = wx.getMenuButtonBoundingClientRect()
|
|
|
|
|
navBarHeight.value = (btn && btn.height > 0) ? (btn.top - statusBarHeight.value) * 2 + btn.height : 44
|
|
|
|
|
} catch { statusBarHeight.value = 47; navBarHeight.value = 44 }
|
|
|
|
|
}
|
|
|
|
|
calcNav()
|
|
|
|
|
setTimeout(calcNav, 300)
|
|
|
|
|
setTimeout(calcNav, 600)
|
2026-07-03 04:10:46 +08:00
|
|
|
|
|
|
|
|
const authStore = useAuthStore()
|
|
|
|
|
|
2026-09-19 23:40:46 +08:00
|
|
|
function signalClass(rssi?: number): string {
|
|
|
|
|
if (!rssi || rssi < 30) return 'off'
|
|
|
|
|
if (rssi >= 55) return 'good'
|
|
|
|
|
if (rssi >= 40) return 'mid'
|
|
|
|
|
return 'low'
|
2026-07-04 04:03:02 +08:00
|
|
|
}
|
|
|
|
|
|
2026-09-19 23:40:46 +08:00
|
|
|
const stats = ref<DashboardData>({ online_cabinets: 0, total_channels: 0, charging_count: 0, idle_channels: 0, full_count: 0, fault_channels: 0, fault_count: 0, today_charge_count: 0, today_energy_kwh: 0, today_charge_hours: 0, alerts: [], projects: [] })
|
2026-07-04 04:03:02 +08:00
|
|
|
const projects = computed(() => stats.value.projects || [])
|
|
|
|
|
const cabinets = ref<CabinetItem[]>([])
|
|
|
|
|
const loading = ref(true)
|
|
|
|
|
const selectedProjectId = ref<number | null>(null)
|
|
|
|
|
const orgs = ref<OrgItem[]>([])
|
|
|
|
|
const showOrgPicker = ref(false)
|
|
|
|
|
const selectedOrgId = ref<number | null>(null)
|
|
|
|
|
const currentOrgName = ref('')
|
|
|
|
|
|
|
|
|
|
async function initOrg() {
|
2026-09-19 23:40:46 +08:00
|
|
|
try { const res = await h5Api.getOrganizations(); orgs.value = res || []; switchOrgAll() } catch { currentOrgName.value = '' }
|
|
|
|
|
}function switchOrgAll() { showOrgPicker.value = false; selectedOrgId.value = null; currentOrgName.value = '全部'; selectedProjectId.value = null; fetchDashboard(); fetchCabinets() }
|
|
|
|
|
function switchOrg(org: OrgItem) { showOrgPicker.value = false; selectedOrgId.value = org.id; currentOrgName.value = org.name; selectedProjectId.value = null; fetchDashboard(); fetchCabinets() }
|
2026-07-04 04:03:02 +08:00
|
|
|
|
|
|
|
|
onShow(async () => {
|
|
|
|
|
const scanId = authStore.scanAbstractId
|
|
|
|
|
if (scanId) {
|
|
|
|
|
authStore.clearScanId()
|
2026-09-19 23:40:46 +08:00
|
|
|
if (!authStore.isAuthenticated) { uni.navigateTo({ url: '/pages/login/index' }); return }
|
|
|
|
|
try { const res = await h5Api.getCabinetByAbstract(scanId); uni.navigateTo({ url: `/pages/device-detail/index?id=${res.id}` }); return } catch { uni.showToast({ title: '设备不存在', icon: 'none' }) }
|
2026-07-04 04:03:02 +08:00
|
|
|
}
|
2026-09-19 23:40:46 +08:00
|
|
|
if (!authStore.isAuthenticated) { uni.navigateTo({ url: '/pages/login/index' }); return }
|
|
|
|
|
initOrg(); fetchDashboard(); fetchCabinets()
|
2026-07-03 04:10:46 +08:00
|
|
|
})
|
2026-09-19 23:40:46 +08:00
|
|
|
onPullDownRefresh(async () => { await Promise.all([fetchDashboard(), fetchCabinets()]); uni.stopPullDownRefresh() })
|
2026-07-03 04:10:46 +08:00
|
|
|
|
|
|
|
|
async function fetchDashboard() {
|
2026-09-19 23:40:46 +08:00
|
|
|
try { const params: Record<string, string> = {}; if (selectedOrgId.value) params.org_id = String(selectedOrgId.value); const res = await h5Api.getDashboard(params); stats.value = res } catch {}
|
2026-07-04 04:03:02 +08:00
|
|
|
}
|
|
|
|
|
async function fetchCabinets() {
|
|
|
|
|
loading.value = true
|
2026-09-19 23:40:46 +08:00
|
|
|
try { const params: Record<string, any> = {}; if (selectedProjectId.value !== null) params.project_id = selectedProjectId.value; if (selectedOrgId.value) params.org_id = selectedOrgId.value; const res = await h5Api.getCabinets(Object.keys(params).length > 0 ? params : undefined); cabinets.value = res || [] } catch { cabinets.value = [] } finally { loading.value = false }
|
2026-07-04 04:03:02 +08:00
|
|
|
}
|
2026-09-19 23:40:46 +08:00
|
|
|
function selectProject(id: number | null) { selectedProjectId.value = id; fetchCabinets() }
|
|
|
|
|
function goDetail(id: number) { uni.navigateTo({ url: `/pages/device-detail/index?id=${id}` }) }
|
2026-07-04 04:03:02 +08:00
|
|
|
|
|
|
|
|
function handleScan() {
|
2026-09-19 23:40:46 +08:00
|
|
|
uni.scanCode({
|
|
|
|
|
scanType: ['qrCode'],
|
|
|
|
|
success: (res) => {
|
|
|
|
|
const code = res.result.trim()
|
|
|
|
|
const match = code.match(/\/(\d{2}-\d{8})$/) || code.match(/[?&]id=(\d{2}-\d{8})/)
|
|
|
|
|
const abstractId = match ? match[1] : code
|
|
|
|
|
if (/^\d{2}-\d{8}$/.test(abstractId)) { goToDeviceByScan(abstractId) }
|
|
|
|
|
else { uni.showToast({ title: '无效的二维码', icon: 'none' }) }
|
|
|
|
|
},
|
|
|
|
|
fail: () => { showScanInput() }
|
2026-07-04 04:03:02 +08:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
async function goToDeviceByScan(code: string) {
|
2026-09-19 23:40:46 +08:00
|
|
|
try { const result = await h5Api.scanCabinet(code); uni.navigateTo({ url: `/pages/device-detail/index?id=${result.id}` }) } catch (err: any) {
|
2026-07-04 04:03:02 +08:00
|
|
|
const msg = err?.message || ''
|
2026-09-19 23:40:46 +08:00
|
|
|
if (msg.includes('没有权限') || msg.includes('403') || msg.includes('Forbidden')) { uni.showToast({ title: '没有权限访问该设备', icon: 'none' }) } else if (msg.includes('不存在')) { uni.showToast({ title: '设备不存在', icon: 'none' }) } else { uni.showToast({ title: '扫码失败: ' + msg, icon: 'none' }) }
|
2026-07-04 04:03:02 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
function showScanInput() {
|
2026-09-19 23:40:46 +08:00
|
|
|
uni.showModal({ title: '扫码', content: '请输入或粘贴二维码中的设备编号', editable: true, placeholderText: '如: 10-00000000', success: async (res) => { if (!res.confirm || !res.content?.trim()) return; goToDeviceByScan(res.content.trim()) } })
|
2026-07-03 04:10:46 +08:00
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
2026-09-19 23:40:46 +08:00
|
|
|
.home-page { background: #f5f7f8; min-height: 100vh; }
|
|
|
|
|
.top-bar { display: flex; align-items: center; justify-content: space-between; padding: 0 20px; background: #fff; border-bottom: 1px solid #f1f5f9; position: sticky; top: 0; z-index: 100; box-sizing: border-box; }
|
|
|
|
|
.org-switcher { display: flex; align-items: center; gap: 6px; padding: 6px 12px; background: #f1f5f9; border-radius: 999px; }
|
|
|
|
|
.org-label { font-size: 14px; font-weight: 500; color: #111827; max-width: 160px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
|
|
|
.org-arrow { display: flex; align-items: center; }
|
|
|
|
|
.scan-btn { display: flex; align-items: center; justify-content: center; background: none; color: #111827; border: none; padding: 0; }
|
|
|
|
|
.project-filter { padding: 12px 20px; background: #fff; border-bottom: 1px solid #f1f5f9; overflow-x: auto; }
|
|
|
|
|
.project-filter::-webkit-scrollbar { display: none; }
|
|
|
|
|
.filter-inner { display: flex; gap: 8px; white-space: nowrap; }
|
|
|
|
|
.filter-tag { flex-shrink: 0; padding: 6px 14px; font-size: 13px; border-radius: 999px; border: 1px solid #e2e8f0; color: #475569; background: #fff; }
|
|
|
|
|
.filter-tag.active { background: #0d9488; color: #fff; border-color: #0d9488; }
|
|
|
|
|
.stats-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; padding: 16px 20px; }
|
|
|
|
|
.stat-item { background: #fff; border-radius: 12px; padding: 16px 12px; text-align: center; box-shadow: 0 1px 2px rgba(0,0,0,0.04); }
|
|
|
|
|
.stat-value { font-size: 22px; font-weight: 600; font-variant-numeric: tabular-nums; line-height: 1.1; display: block; }
|
|
|
|
|
.stat-value.primary { color: #0d9488; }
|
|
|
|
|
.stat-value.success { color: #22c55e; }
|
|
|
|
|
.stat-value.warning { color: #f59e0b; }
|
|
|
|
|
.stat-value.danger { color: #ef4444; }
|
|
|
|
|
.stat-value.muted { color: #94a3b8; }
|
|
|
|
|
.stat-label { font-size: 12px; color: #94a3b8; margin-top: 4px; }
|
|
|
|
|
.section-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 20px; }
|
|
|
|
|
.section-title { font-size: 16px; font-weight: 600; color: #111827; }
|
|
|
|
|
.section-count { font-size: 12px; color: #94a3b8; }
|
|
|
|
|
.device-list { padding: 0 20px; display: flex; flex-direction: column; gap: 12px; }
|
|
|
|
|
.device-card { background: #fff; border-radius: 12px; padding: 16px; box-shadow: 0 1px 2px rgba(0,0,0,0.04); }
|
|
|
|
|
.device-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
|
|
|
|
|
.device-id-row { display: flex; align-items: center; gap: 8px; }
|
|
|
|
|
.device-id { font-size: 14px; font-weight: 600; color: #111827; font-variant-numeric: tabular-nums; letter-spacing: 0.02em; }
|
|
|
|
|
.status-badge { display: inline-flex; align-items: center; gap: 4px; padding: 3px 8px; font-size: 11px; font-weight: 500; border-radius: 999px; }
|
|
|
|
|
.status-badge.online { background: #f0fdf4; color: #22c55e; }
|
|
|
|
|
.status-badge.offline { background: #f1f5f9; color: #94a3b8; }
|
|
|
|
|
.status-badge.online::before, .status-badge.offline::before { content: ''; width: 5px; height: 5px; border-radius: 50%; background: currentColor; }
|
|
|
|
|
.temp-badge { font-size: 12px; color: #475569; font-variant-numeric: tabular-nums; flex-shrink: 0; }
|
|
|
|
|
.header-right { display: flex; align-items: center; gap: 8px; }
|
|
|
|
|
.signal-inline { display: flex; align-items: center; gap: 3px; }
|
|
|
|
|
.signal { display: flex; align-items: flex-end; gap: 1.5px; }
|
|
|
|
|
.signal-bar { width: 3px; border-radius: 1px; }
|
|
|
|
|
.signal-bar.l1 { height: 4px; } .signal-bar.l2 { height: 7px; } .signal-bar.l3 { height: 10px; } .signal-bar.l4 { height: 13px; } .signal-bar.l5 { height: 16px; }
|
|
|
|
|
.signal.good .signal-bar { background: #16a34a; } .signal.mid .signal-bar { background: #f59e0b; } .signal.low .signal-bar { background: #ef4444; } .signal.off .signal-bar { background: #94a3b8; }
|
|
|
|
|
.rssi-value { font-size: 11px; color: #94a3b8; font-variant-numeric: tabular-nums; }
|
|
|
|
|
.pow-tags { display: flex; gap: 4px; }
|
|
|
|
|
.pow-tag { font-size: 10px; font-weight: 500; padding: 2px 6px; border-radius: 3px; letter-spacing: 0.04em; white-space: nowrap; }
|
|
|
|
|
.pow-tag.on { background: #dcfce7; color: #16a34a; }
|
|
|
|
|
.pow-tag.off { background: #fee2e2; color: #dc2626; }
|
|
|
|
|
.device-name { font-size: 14px; font-weight: 500; color: #111827; display: block; margin-bottom: 4px; }
|
|
|
|
|
.device-imei { font-size: 11px; color: #94a3b8; font-variant-numeric: tabular-nums; display: block; margin-bottom: 4px; opacity: 0.7; }
|
|
|
|
|
.device-addr { font-size: 12px; color: #475569; display: block; margin-bottom: 8px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
|
|
|
.channel-stats { display: flex; align-items: center; gap: 12px; padding-top: 12px; border-top: 1px solid #f1f5f9; }
|
|
|
|
|
.channel-chip { display: inline-flex; align-items: center; gap: 4px; font-size: 13px; color: #475569; }
|
|
|
|
|
.channel-chip.total { color: #6b7280; }
|
|
|
|
|
.channel-chip.idle { color: #16a34a; }
|
|
|
|
|
.channel-chip.charging { color: #0f766e; }
|
|
|
|
|
.channel-chip.full { color: #d97706; }
|
|
|
|
|
.channel-chip.fault { color: #dc2626; }
|
|
|
|
|
.chip-num { font-variant-numeric: tabular-nums; font-weight: 600; color: #111827; }
|
|
|
|
|
.empty-state { text-align: center; padding: 64px 20px; color: #94a3b8; }
|
|
|
|
|
.empty-text { font-size: 14px; }
|
|
|
|
|
.org-overlay { position: fixed; inset: 0; z-index: 200; background: rgba(0,0,0,0.45); display: flex; align-items: flex-end; justify-content: center; }
|
|
|
|
|
.org-sheet { background: #fff; width: 100%; max-width: 430px; max-height: 70vh; border-radius: 16px 16px 0 0; display: flex; flex-direction: column; overflow: hidden; }
|
|
|
|
|
.org-header { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px; border-bottom: 1px solid #f1f5f9; }
|
|
|
|
|
.org-title { font-size: 16px; font-weight: 600; }
|
|
|
|
|
.org-close { width: 28px; height: 28px; border-radius: 50%; background: #f1f5f9; display: flex; align-items: center; justify-content: center; font-size: 14px; color: #475569; }
|
|
|
|
|
.org-list { flex: 1; }
|
|
|
|
|
.org-option { display: flex; align-items: center; justify-content: space-between; padding: 14px 20px; font-size: 15px; color: #111827; border-bottom: 1px solid #f1f5f9; }
|
|
|
|
|
.org-option.selected { color: #0d9488; font-weight: 500; }
|
|
|
|
|
.org-option.selected::after { content: '✓'; font-size: 16px; font-weight: 600; }
|
2026-07-03 04:10:46 +08:00
|
|
|
</style>
|