WorkspaceView.vue 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198
  1. <!-- 首页工作台:轻量化蓝白科技风 B端数据中台 - Element Plus -->
  2. <template>
  3. <div class="workspace-container">
  4. <!-- 顶部标题栏 -->
  5. <header class="oa-header">
  6. <div class="header-left">
  7. <h1 class="system-title">乌拉海沟水库综合应用管理系统</h1>
  8. <el-tag type="primary" effect="dark" round size="small">工作台</el-tag>
  9. </div>
  10. <div class="header-right">
  11. <el-tooltip content="返回大屏" placement="bottom">
  12. <div class="back-bigscreen-btn" @click="$emit('backToDashboard')">
  13. <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  14. <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
  15. <polyline points="9 22 9 12 15 12 15 22"></polyline>
  16. </svg>
  17. </div>
  18. </el-tooltip>
  19. <el-input v-model="searchText" placeholder="搜索功能、工单、设备..." clearable class="header-search">
  20. <template #prefix>
  21. <el-icon><Search /></el-icon>
  22. </template>
  23. </el-input>
  24. <span class="header-date">{{ currentDate }}</span>
  25. <span class="header-time">{{ currentTime }}</span>
  26. </div>
  27. </header>
  28. <!-- 主体区域 -->
  29. <div class="oa-body">
  30. <!-- 左侧树状菜单 -->
  31. <aside class="sidebar">
  32. <el-menu :default-active="activeMenu" class="sidebar-menu" @select="handleMenuSelect">
  33. <template v-for="(group, index) in menuTree" :key="index">
  34. <el-menu-item v-if="!group.children || group.children.length === 0" :index="group.name">
  35. <span class="menu-top-item">{{ group.name }}</span>
  36. </el-menu-item>
  37. <el-sub-menu v-else :index="group.name">
  38. <template #title>
  39. <span>{{ group.name }}</span>
  40. </template>
  41. <el-menu-item v-for="(item, idx) in group.children" :key="idx" :index="item.id">
  42. <span>{{ item.name }}</span>
  43. </el-menu-item>
  44. </el-sub-menu>
  45. </template>
  46. </el-menu>
  47. </aside>
  48. <!-- 右侧内容区 -->
  49. <main class="oa-main">
  50. <!-- 设备运维页面 -->
  51. <DeviceMaintainView v-if="activeMenu === '设备运维'" />
  52. <!-- 闸门控制页面 -->
  53. <GateControlAdminView v-if="activeMenu === '闸门控制'" />
  54. <!-- 巡检计划页面 -->
  55. <PatrolPlanView v-if="activeMenu === 'patrol-plan'" />
  56. <!-- 巡检记录页面 -->
  57. <PatrolRecordView v-if="activeMenu === 'patrol-record'" />
  58. <!-- 隐患台账页面 -->
  59. <PatrolHiddenView v-if="activeMenu === 'patrol-hidden'" />
  60. <!-- 水雨情监测页面 -->
  61. <WaterRainView v-if="activeMenu === 'hydro-realtime'" />
  62. <HydroHistoryView v-if="activeMenu === 'hydro-history'" />
  63. <!-- 水资源调度模块 -->
  64. <WaterResourceAllocationView v-if="isWaterActive" ref="waterView" :default-tab="waterTabMap[activeMenu]" :key="activeMenu" />
  65. <!-- 工程效益 -->
  66. <BenefitSummaryView v-if="isBenefitActive" :key="activeMenu" />
  67. <!-- 资料档案模块 -->
  68. <ArchiveFilesView v-if="activeMenu === 'archive-files'" />
  69. <ArchiveOrdersView v-if="activeMenu === 'archive-orders'" />
  70. <ArchiveLifecycleView v-if="activeMenu === 'archive-lifecycle'" />
  71. <!-- 系统权限模块 -->
  72. <SysRoleView v-if="activeMenu === 'sys-role'" />
  73. <SysUserView v-if="activeMenu === 'sys-user'" />
  74. <SysLogView v-if="activeMenu === 'sys-log'" />
  75. <!-- 首页内容 -->
  76. <template v-if="isHomePage">
  77. <!-- 核心水情数据卡片 -->
  78. <section class="glass-card stats-section">
  79. <div class="card-header">
  80. <span class="card-title">实时水情</span>
  81. <div class="card-actions">
  82. <el-tag :type="waterLevelStatus.type" effect="dark" size="small">
  83. {{ waterLevelStatus.text }}
  84. </el-tag>
  85. </div>
  86. </div>
  87. <div class="stats-grid">
  88. <div class="stat-card" v-for="(card, index) in waterDataCards" :key="index">
  89. <div class="stat-card-left">
  90. <div class="stat-icon-wrap" :style="{ background: card.iconBg }">
  91. <span class="stat-icon">{{ card.icon }}</span>
  92. </div>
  93. <span class="stat-label">{{ card.title }}</span>
  94. </div>
  95. <div class="stat-card-right">
  96. <div class="stat-card-value">
  97. <span class="stat-value">{{ card.value }}</span>
  98. <span class="stat-unit">{{ card.unit }}</span>
  99. </div>
  100. <span class="stat-time">{{ card.updateTime }}</span>
  101. </div>
  102. </div>
  103. </div>
  104. </section>
  105. <!-- 中间区域:预警 + 工单 + 设备状态 -->
  106. <section class="middle-section">
  107. <!-- 左侧:预警信息 + 待办工单 -->
  108. <div class="middle-left">
  109. <!-- 预警信息 -->
  110. <div class="glass-card warning-panel">
  111. <div class="card-header">
  112. <span class="card-title">预警信息</span>
  113. <el-badge :value="warningList.length" :max="99" class="warning-badge">
  114. <el-button type="danger" size="small" plain>查看全部</el-button>
  115. </el-badge>
  116. </div>
  117. <div class="card-body">
  118. <div class="warning-list">
  119. <div v-for="(item, index) in warningList" :key="index"
  120. :class="['warning-item', `warning-${item.level}`]">
  121. <div class="warning-icon">
  122. <el-icon><Warning /></el-icon>
  123. </div>
  124. <div class="warning-content">
  125. <div class="warning-title">{{ item.title }}</div>
  126. <div class="warning-meta">
  127. <span>{{ item.location }}</span>
  128. <span>{{ item.time }}</span>
  129. </div>
  130. </div>
  131. <el-tag :type="getWarningType(item.level)" size="small" effect="dark">
  132. {{ item.levelText }}
  133. </el-tag>
  134. </div>
  135. </div>
  136. </div>
  137. </div>
  138. <!-- 月度指标统计 -->
  139. <div class="glass-card stats-table-panel">
  140. <div class="card-header">
  141. <span class="card-title">月度指标统计</span>
  142. <div class="card-actions">
  143. <el-button type="primary" size="small" plain>导出Excel</el-button>
  144. <el-button type="primary" size="small" plain>导出PDF</el-button>
  145. </div>
  146. </div>
  147. <div class="card-body">
  148. <el-table :data="monthlyStats" stripe style="width: 100%" size="small">
  149. <el-table-column prop="name" label="统计项" width="120" />
  150. <el-table-column label="本月数值">
  151. <template #default="{ row }">
  152. <span class="value-highlight">{{ row.value }}</span>
  153. </template>
  154. </el-table-column>
  155. <el-table-column prop="unit" label="单位" width="80" />
  156. <el-table-column label="环比变化" width="100">
  157. <template #default="{ row }">
  158. <span :class="['trend', row.trend > 0 ? 'trend-up' : 'trend-down']">
  159. {{ row.trend > 0 ? '+' : '' }}{{ row.trend }}%
  160. </span>
  161. </template>
  162. </el-table-column>
  163. <el-table-column label="趋势" width="80">
  164. <template #default="{ row }">
  165. <div class="mini-chart">
  166. <div class="chart-bar" :style="{ height: Math.abs(row.trend) + '%', background: row.trend > 0 ? '#52c41a' : '#ff4d4f' }"></div>
  167. </div>
  168. </template>
  169. </el-table-column>
  170. </el-table>
  171. </div>
  172. </div>
  173. <!-- 待办工单 -->
  174. <div class="glass-card todo-panel">
  175. <div class="card-header">
  176. <span class="card-title">待办工单</span>
  177. <el-button type="primary" size="small" plain>查看全部</el-button>
  178. </div>
  179. <div class="card-body">
  180. <el-table :data="todoList" stripe style="width: 100%" size="small" header-row-class-name="table-header" height="150">
  181. <el-table-column prop="type" label="工单类型" width="100" />
  182. <el-table-column prop="location" label="关联点位" />
  183. <el-table-column prop="reportTime" label="上报时间" width="140" />
  184. <el-table-column prop="responsible" label="责任人" width="80" />
  185. <el-table-column label="状态" width="80">
  186. <template #default="{ row }">
  187. <el-tag :type="getStatusType(row.status)" size="small" effect="light" round>
  188. {{ row.status }}
  189. </el-tag>
  190. </template>
  191. </el-table-column>
  192. <el-table-column label="操作" width="70">
  193. <template #default>
  194. <el-button type="primary" link size="small">处理</el-button>
  195. </template>
  196. </el-table-column>
  197. </el-table>
  198. </div>
  199. </div>
  200. </div>
  201. <!-- 右侧:设备状态 + 今日调度 + 月度统计 -->
  202. <div class="middle-right">
  203. <!-- 设备运行状态 -->
  204. <div class="glass-card device-panel">
  205. <div class="card-header">
  206. <span class="card-title">设备状态</span>
  207. <el-button type="primary" size="small" link>设备管理</el-button>
  208. </div>
  209. <div class="device-stats">
  210. <div class="device-stat-item" v-for="(item, index) in deviceStats" :key="index">
  211. <div class="device-stat-icon" :style="{ background: item.color }">
  212. <span>{{ item.icon }}</span>
  213. </div>
  214. <div class="device-stat-info">
  215. <span class="device-stat-value">{{ item.value }}</span>
  216. <span class="device-stat-label">{{ item.label }}</span>
  217. </div>
  218. </div>
  219. </div>
  220. <div class="device-list">
  221. <div v-for="(device, index) in deviceList" :key="index" class="device-item">
  222. <div class="device-info">
  223. <span class="device-name">{{ device.name }}</span>
  224. <span class="device-location">{{ device.location }}</span>
  225. </div>
  226. <el-tag :type="getDeviceStatusType(device.status)" size="small" effect="light">
  227. {{ device.statusText }}
  228. </el-tag>
  229. </div>
  230. </div>
  231. </div>
  232. <!-- 今日调度计划 -->
  233. <div class="glass-card schedule-panel">
  234. <div class="card-header">
  235. <span class="card-title">今日调度</span>
  236. <el-button type="primary" size="small" plain>调度管理</el-button>
  237. </div>
  238. <div class="card-body">
  239. <div class="schedule-list">
  240. <div v-for="(item, index) in todaySchedule" :key="index" class="schedule-item">
  241. <div class="schedule-time">{{ item.time }}</div>
  242. <div class="schedule-content">
  243. <div class="schedule-title">{{ item.title }}</div>
  244. <div class="schedule-desc">{{ item.description }}</div>
  245. </div>
  246. <el-tag :type="getScheduleType(item.status)" size="small" effect="light">
  247. {{ item.statusText }}
  248. </el-tag>
  249. </div>
  250. </div>
  251. </div>
  252. </div>
  253. </div>
  254. </section>
  255. <!-- 底部:快捷功能 + 气象信息 -->
  256. <section class="bottom-section">
  257. <!-- 快捷功能 -->
  258. <div class="glass-card quick-panel">
  259. <div class="card-header">
  260. <span class="card-title">快捷功能</span>
  261. </div>
  262. <div class="quick-grid">
  263. <div class="quick-item" v-for="(item, index) in quickAccess" :key="index" @click="handleQuickAccess(item.name)">
  264. <div class="quick-icon" :style="{ background: item.gradient }">
  265. <span>{{ item.icon }}</span>
  266. </div>
  267. <span class="quick-label">{{ item.name }}</span>
  268. </div>
  269. </div>
  270. </div>
  271. <!-- 气象信息 -->
  272. <div class="glass-card weather-panel">
  273. <div class="card-header">
  274. <span class="card-title">气象信息</span>
  275. <span class="weather-update">更新于 {{ weatherData.updateTime }}</span>
  276. </div>
  277. <div class="weather-content">
  278. <div class="weather-main">
  279. <div class="weather-icon">{{ weatherData.icon }}</div>
  280. <div class="weather-info">
  281. <span class="weather-temp">{{ weatherData.temperature }}°C</span>
  282. <span class="weather-desc">{{ weatherData.description }}</span>
  283. </div>
  284. </div>
  285. <div class="weather-details">
  286. <div class="weather-detail-item">
  287. <span class="detail-label">湿度</span>
  288. <span class="detail-value">{{ weatherData.humidity }}%</span>
  289. </div>
  290. <div class="weather-detail-item">
  291. <span class="detail-label">风速</span>
  292. <span class="detail-value">{{ weatherData.windSpeed }}m/s</span>
  293. </div>
  294. <div class="weather-detail-item">
  295. <span class="detail-label">降雨概率</span>
  296. <span class="detail-value">{{ weatherData.rainProbability }}%</span>
  297. </div>
  298. </div>
  299. <div class="weather-forecast">
  300. <div v-for="(day, index) in weatherForecast" :key="index" class="forecast-item">
  301. <span class="forecast-day">{{ day.day }}</span>
  302. <span class="forecast-icon">{{ day.icon }}</span>
  303. <span class="forecast-temp">{{ day.high }}°/{{ day.low }}°</span>
  304. </div>
  305. </div>
  306. </div>
  307. </div>
  308. </section>
  309. </template>
  310. </main>
  311. </div>
  312. </div>
  313. </template>
  314. <script>
  315. import { Search, Warning } from '@element-plus/icons-vue'
  316. import DeviceMaintainView from '../admin/DeviceMaintainView.vue'
  317. import PatrolPlanView from '../admin/PatrolPlanView.vue'
  318. import PatrolRecordView from '../admin/PatrolRecordView.vue'
  319. import PatrolHiddenView from '../admin/PatrolHiddenView.vue'
  320. import WaterRainView from '../admin/WaterRainView.vue'
  321. import HydroHistoryView from '../admin/HydroHistoryView.vue'
  322. import WaterResourceAllocationView from '../admin/WaterResourceAllocationView.vue'
  323. import GateControlAdminView from '../admin/GateControlAdminView.vue'
  324. import BenefitSummaryView from '../admin/BenefitSummaryView.vue'
  325. import ArchiveFilesView from '../admin/ArchiveFilesView.vue'
  326. import ArchiveOrdersView from '../admin/ArchiveOrdersView.vue'
  327. import ArchiveLifecycleView from '../admin/ArchiveLifecycleView.vue'
  328. import SysRoleView from '../admin/SysRoleView.vue'
  329. import SysUserView from '../admin/SysUserView.vue'
  330. import SysLogView from '../admin/SysLogView.vue'
  331. export default {
  332. name: 'WorkspaceView',
  333. components: { Search, Warning, DeviceMaintainView, PatrolPlanView, PatrolRecordView, PatrolHiddenView, WaterRainView, HydroHistoryView, WaterResourceAllocationView, GateControlAdminView, BenefitSummaryView, ArchiveFilesView, ArchiveOrdersView, ArchiveLifecycleView, SysRoleView, SysUserView, SysLogView },
  334. data() {
  335. return {
  336. currentDate: '',
  337. currentTime: '',
  338. searchText: '',
  339. timer: null,
  340. activeMenu: 'dashboard',
  341. waterTabMap: {
  342. 'water-supply': 'supply',
  343. 'water-eco': 'eco'
  344. },
  345. menuTree: [
  346. { name: '首页', children: [] },
  347. { name: '日常巡检', children: [{ id: 'patrol-plan', name: '巡检计划' }, { id: 'patrol-record', name: '巡检记录' }, { id: 'patrol-hidden', name: '隐患台账' }] },
  348. { name: '设备运维', children: [] },
  349. { name: '闸门控制', children: [] },
  350. { name: '水雨情监测', children: [{ id: 'hydro-realtime', name: '实时监测' }, { id: 'hydro-history', name: '历史数据' }] },
  351. { name: '水资源调度', children: [{ id: 'water-supply', name: '供水水量' }, { id: 'water-eco', name: '生态补水' }] },
  352. { name: '工程效益', children: [] },
  353. { name: '资料档案', children: [{ id: 'archive-files', name: '工程档案' }, { id: 'archive-orders', name: '调令文件' }, { id: 'archive-lifecycle', name: '全生命周期' }] },
  354. { name: '系统权限', children: [{ id: 'sys-role', name: '角色管理' }, { id: 'sys-user', name: '用户账号' }, { id: 'sys-log', name: '操作日志' }] }
  355. ],
  356. // 水位状态
  357. waterLevelStatus: {
  358. type: 'success',
  359. text: '正常蓄水位'
  360. },
  361. // 实时水情数据
  362. waterDataCards: [
  363. { title: '库水位', value: '18.52', unit: 'm', icon: '🌊', iconBg: 'linear-gradient(135deg, #dbeafe, #93c5fd)', updateTime: '14:30 更新' },
  364. { title: '汛限水位', value: '22.00', unit: 'm', icon: '📏', iconBg: 'linear-gradient(135deg, #fef3c7, #fcd34d)', updateTime: '设计值' },
  365. { title: '库容', value: '2350.8', unit: '万m³', icon: '💧', iconBg: 'linear-gradient(135deg, #cffafe, #67e8f9)', updateTime: '14:30 更新' },
  366. { title: '入库流量', value: '8.5', unit: 'm³/s', icon: '📥', iconBg: 'linear-gradient(135deg, #dcfce7, #86efac)', updateTime: '14:30 更新' },
  367. { title: '出库流量', value: '12.8', unit: 'm³/s', icon: '📤', iconBg: 'linear-gradient(135deg, #fce7f3, #f9a8d4)', updateTime: '14:30 更新' },
  368. { title: '实时雨量', value: '2.5', unit: 'mm', icon: '🌧️', iconBg: 'linear-gradient(135deg, #e0e7ff, #a5b4fc)', updateTime: '14:30 更新' }
  369. ],
  370. // 预警信息
  371. warningList: [
  372. { level: 'danger', levelText: '一级', title: '库水位接近汛限水位', location: '主坝监测点', time: '14:25' },
  373. { level: 'warning', levelText: '二级', title: '3号渗压计数据异常', location: '副坝渗压监测', time: '13:40' },
  374. { level: 'info', levelText: '三级', title: '视频监控离线', location: '溢洪道摄像头', time: '12:15' }
  375. ],
  376. // 设备统计
  377. deviceStats: [
  378. { icon: '📡', label: '传感器总数', value: '156', color: 'linear-gradient(135deg, #dbeafe, #93c5fd)' },
  379. { icon: '✅', label: '在线设备', value: '148', color: 'linear-gradient(135deg, #dcfce7, #86efac)' },
  380. { icon: '⚠️', label: '故障设备', value: '5', color: 'linear-gradient(135deg, #fef3c7, #fcd34d)' },
  381. { icon: '❌', label: '离线设备', value: '3', color: 'linear-gradient(135deg, #fee2e2, #fca5a5)' }
  382. ],
  383. // 设备列表
  384. deviceList: [
  385. { name: '主坝水位计', location: '大坝坝顶', status: 'online', statusText: '在线' },
  386. { name: '副坝渗压计', location: '副坝坝体', status: 'warning', statusText: '异常' },
  387. { name: '溢洪道闸门', location: '溢洪道', status: 'online', statusText: '运行' },
  388. { name: '入库流量计', location: '进水口', status: 'offline', statusText: '离线' }
  389. ],
  390. // 气象信息
  391. weatherData: {
  392. icon: '⛅',
  393. temperature: 28,
  394. description: '多云',
  395. humidity: 65,
  396. windSpeed: 3.2,
  397. rainProbability: 30,
  398. updateTime: '14:00'
  399. },
  400. // 天气预报
  401. weatherForecast: [
  402. { day: '今天', icon: '⛅', high: 32, low: 22 },
  403. { day: '明天', icon: '🌧️', high: 28, low: 20 },
  404. { day: '后天', icon: '☀️', high: 35, low: 24 }
  405. ],
  406. // 待办工单
  407. todoList: [
  408. { type: '巡检待完成', location: '大坝坝顶', reportTime: '2024-01-15 09:00', responsible: '张三', status: '待处理' },
  409. { type: '隐患待整改', location: '溢洪道闸门', reportTime: '2024-01-14 16:30', responsible: '李四', status: '处理中' },
  410. { type: '设备维保到期', location: '输水闸启闭机', reportTime: '2024-01-14 10:15', responsible: '王五', status: '已完成' },
  411. { type: '用水审批待处理', location: '灌溉渠道A段', reportTime: '2024-01-15 08:45', responsible: '赵六', status: '待处理' },
  412. { type: '预警待处置', location: '库区水位监测点', reportTime: '2024-01-15 11:20', responsible: '张三', status: '已逾期' }
  413. ],
  414. // 今日调度
  415. todaySchedule: [
  416. { time: '08:00', title: '灌溉供水调度', description: '灌溉渠道A段开闸放水,流量5m³/s', status: 'completed', statusText: '已完成' },
  417. { time: '10:00', title: '人饮水量调节', description: '水厂取水量调整至3m³/s', status: 'completed', statusText: '已完成' },
  418. { time: '14:00', title: '生态补水', description: '下游河道生态补水,流量2m³/s', status: 'inProgress', statusText: '执行中' },
  419. { time: '16:00', title: '闸门检修', description: '3号闸门例行检修,预计2小时', status: 'pending', statusText: '待执行' }
  420. ],
  421. // 快捷功能
  422. quickAccess: [
  423. { name: '视频监控', icon: '📹', gradient: 'linear-gradient(135deg, #e0f2fe, #bae6fd)' },
  424. { name: '巡检上报', icon: '🔍', gradient: 'linear-gradient(135deg, #dcfce7, #bbf7d0)' },
  425. { name: '设备报修', icon: '🔧', gradient: 'linear-gradient(135deg, #fef9c3, #fef08a)' },
  426. { name: '用水调度', icon: '💧', gradient: 'linear-gradient(135deg, #cffafe, #a5f3fc)' },
  427. { name: '隐患台账', icon: '📋', gradient: 'linear-gradient(135deg, #f3e8ff, #e9d5ff)' },
  428. { name: '报表导出', icon: '📊', gradient: 'linear-gradient(135deg, #fce7f3, #fbcfe8)' }
  429. ],
  430. // 月度统计
  431. monthlyStats: [
  432. { name: '月度灌溉水量', value: '125.6', unit: '万m³', trend: 5.2 },
  433. { name: '人饮水量', value: '18.3', unit: '万m³', trend: -2.1 },
  434. { name: '工业水量', value: '8.7', unit: '万m³', trend: 3.8 },
  435. { name: '生态补水量', value: '15.2', unit: '万m³', trend: 12.5 },
  436. { name: '防汛处置次数', value: '3', unit: '次', trend: -25.0 }
  437. ]
  438. }
  439. },
  440. computed: {
  441. isHomePage() {
  442. const subPages = ['设备运维', '闸门控制', '工程效益', 'patrol-plan', 'patrol-record', 'patrol-hidden', 'hydro-realtime', 'hydro-history', 'archive-files', 'archive-orders', 'archive-lifecycle', 'sys-role', 'sys-user', 'sys-log']
  443. const waterPages = ['water-supply', 'water-eco']
  444. return ![...subPages, ...waterPages].includes(this.activeMenu)
  445. },
  446. isWaterActive() {
  447. return ['water-supply', 'water-eco'].includes(this.activeMenu)
  448. },
  449. isBenefitActive() {
  450. return this.activeMenu === '工程效益'
  451. }
  452. },
  453. mounted() {
  454. this.updateDateTime()
  455. this.timer = setInterval(this.updateDateTime, 1000)
  456. },
  457. beforeUnmount() {
  458. if (this.timer) clearInterval(this.timer)
  459. },
  460. methods: {
  461. updateDateTime() {
  462. const now = new Date()
  463. this.currentDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
  464. this.currentTime = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`
  465. },
  466. handleMenuSelect(index) {
  467. this.activeMenu = index
  468. },
  469. getStatusType(status) {
  470. return { '待处理': 'primary', '处理中': 'warning', '已完成': 'success', '已逾期': 'danger' }[status] || 'info'
  471. },
  472. getWarningType(level) {
  473. return { 'danger': 'danger', 'warning': 'warning', 'info': 'info' }[level] || 'info'
  474. },
  475. getDeviceStatusType(status) {
  476. return { 'online': 'success', 'warning': 'warning', 'offline': 'danger' }[status] || 'info'
  477. },
  478. getScheduleType(status) {
  479. return { 'completed': 'success', 'inProgress': 'primary', 'pending': 'info' }[status] || 'info'
  480. },
  481. handleQuickAccess(name) {
  482. console.log('快捷功能:', name)
  483. }
  484. }
  485. }
  486. </script>
  487. <style scoped>
  488. /* ==================== 全局重置 ==================== */
  489. * { box-sizing: border-box; margin: 0; padding: 0; }
  490. /* ==================== 整体容器 ==================== */
  491. .workspace-container {
  492. width: 100%;
  493. height: 100%;
  494. background: linear-gradient(180deg, #f0f5fa 0%, #e8eef5 100%);
  495. display: flex;
  496. flex-direction: column;
  497. overflow: hidden;
  498. font-family: 'Alibaba PuHuiTi', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
  499. }
  500. /* ==================== Header 头部栏 ==================== */
  501. .oa-header {
  502. height: 70px;
  503. background: linear-gradient(135deg, rgba(59, 130, 246, 0.9) 0%, rgba(37, 99, 235, 0.95) 100%);
  504. backdrop-filter: blur(20px);
  505. color: white;
  506. padding: 0 32px;
  507. display: flex;
  508. justify-content: space-between;
  509. align-items: center;
  510. box-shadow: 0 4px 24px rgba(59, 130, 246, 0.15);
  511. flex-shrink: 0;
  512. position: relative;
  513. z-index: 10;
  514. }
  515. .oa-header::after {
  516. content: '';
  517. position: absolute;
  518. bottom: 0;
  519. left: 0;
  520. right: 0;
  521. height: 1px;
  522. background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
  523. }
  524. .header-left {
  525. display: flex;
  526. align-items: center;
  527. gap: 16px;
  528. }
  529. .system-title {
  530. font-family: 'Alibaba PuHuiTi', sans-serif;
  531. font-size: 28px;
  532. font-weight: 700;
  533. margin: 0;
  534. line-height: 70px;
  535. letter-spacing: 1px;
  536. text-shadow: 0 2px 4px rgba(0,0,0,0.1);
  537. }
  538. .header-search {
  539. width: 280px;
  540. }
  541. .header-search :deep(.el-input__wrapper) {
  542. background: rgba(255, 255, 255, 0.2);
  543. border: 1px solid rgba(255, 255, 255, 0.3);
  544. border-radius: 20px;
  545. box-shadow: none;
  546. }
  547. .header-search :deep(.el-input__wrapper:hover),
  548. .header-search :deep(.el-input__wrapper.is-focus) {
  549. background: rgba(255, 255, 255, 0.3);
  550. border-color: rgba(255, 255, 255, 0.5);
  551. box-shadow: none;
  552. }
  553. .header-search :deep(.el-input__inner) {
  554. color: white;
  555. }
  556. .header-search :deep(.el-input__inner::placeholder) {
  557. color: rgba(255, 255, 255, 0.6);
  558. }
  559. .header-search :deep(.el-input__prefix .el-icon) {
  560. color: rgba(255, 255, 255, 0.7);
  561. }
  562. .header-right {
  563. display: flex;
  564. align-items: center;
  565. gap: 12px;
  566. font-size: 14px;
  567. opacity: 0.95;
  568. }
  569. .back-bigscreen-btn {
  570. width: 36px;
  571. height: 36px;
  572. border-radius: 50%;
  573. background: rgba(255,255,255,0.15);
  574. border: 1px solid rgba(255,255,255,0.3);
  575. display: flex;
  576. align-items: center;
  577. justify-content: center;
  578. cursor: pointer;
  579. transition: all 0.25s;
  580. color: rgba(255,255,255,0.85);
  581. flex-shrink: 0;
  582. }
  583. .back-bigscreen-btn:hover {
  584. background: rgba(255,255,255,0.3);
  585. color: #fff;
  586. transform: scale(1.05);
  587. }
  588. .header-time {
  589. font-size: 20px;
  590. font-weight: 600;
  591. letter-spacing: 1px;
  592. }
  593. /* ==================== 主体区域 ==================== */
  594. .oa-body {
  595. flex: 1;
  596. display: flex;
  597. overflow: hidden;
  598. }
  599. /* ==================== 左侧菜单 ==================== */
  600. .sidebar {
  601. width: 220px;
  602. background: rgba(255, 255, 255, 0.85);
  603. backdrop-filter: blur(20px);
  604. border-right: 1px solid rgba(226, 232, 240, 0.8);
  605. display: flex;
  606. flex-direction: column;
  607. flex-shrink: 0;
  608. }
  609. .sidebar-menu {
  610. flex: 1;
  611. overflow-y: auto;
  612. border-right: none;
  613. background: transparent;
  614. }
  615. .sidebar-menu :deep(.el-sub-menu__title),
  616. .sidebar-menu :deep(.el-menu-item) {
  617. height: 48px;
  618. line-height: 48px;
  619. font-family: 'Alibaba PuHuiTi', sans-serif;
  620. font-size: 15px;
  621. font-weight: 400;
  622. letter-spacing: 1px;
  623. color: #475569;
  624. transition: all 0.25s ease;
  625. }
  626. .sidebar-menu :deep(.el-sub-menu__title) {
  627. font-weight: 400;
  628. color: #334155;
  629. }
  630. .sidebar-menu :deep(.el-menu-item) {
  631. padding-left: 48px !important;
  632. color: #64748b;
  633. }
  634. .sidebar-menu :deep(.el-menu-item.is-active) {
  635. background: linear-gradient(135deg, #dbeafe, #bfdbfe);
  636. color: #1d4ed8;
  637. font-weight: 600;
  638. border-right: 3px solid #3b82f6;
  639. }
  640. .sidebar-menu :deep(.el-menu-item:hover) {
  641. background: rgba(219, 234, 254, 0.5);
  642. color: #2563eb;
  643. }
  644. .sidebar-menu :deep(.el-sub-menu__title:hover) {
  645. background: rgba(219, 234, 254, 0.3);
  646. color: #2563eb;
  647. }
  648. .menu-top-item {
  649. font-size: 15px;
  650. font-weight: 400;
  651. color: #334155;
  652. }
  653. .sidebar-menu :deep(.el-menu-item):has(.menu-top-item) {
  654. padding-left: 20px !important;
  655. }
  656. /* ==================== 右侧内容区 ==================== */
  657. .oa-main {
  658. flex: 1;
  659. display: flex;
  660. flex-direction: column;
  661. padding: 20px;
  662. gap: 20px;
  663. overflow-y: auto;
  664. overflow-x: hidden;
  665. }
  666. /* ==================== 玻璃拟态白色卡片 ==================== */
  667. .glass-card {
  668. background: rgba(255, 255, 255, 0.75);
  669. backdrop-filter: blur(20px);
  670. border-radius: 16px;
  671. border: 1px solid rgba(255, 255, 255, 0.8);
  672. box-shadow:
  673. 0 4px 6px -1px rgba(0, 0, 0, 0.03),
  674. 0 2px 4px -2px rgba(0, 0, 0, 0.03),
  675. inset 0 1px 0 rgba(255, 255, 255, 0.6);
  676. display: flex;
  677. flex-direction: column;
  678. overflow: hidden;
  679. transition: box-shadow 0.3s ease;
  680. }
  681. .glass-card:hover {
  682. box-shadow:
  683. 0 10px 15px -3px rgba(0, 0, 0, 0.05),
  684. 0 4px 6px -4px rgba(0, 0, 0, 0.05),
  685. inset 0 1px 0 rgba(255, 255, 255, 0.6);
  686. }
  687. .card-header {
  688. height: 52px;
  689. padding: 0 20px;
  690. border-bottom: 1px solid rgba(226, 232, 240, 0.6);
  691. display: flex;
  692. justify-content: space-between;
  693. align-items: center;
  694. flex-shrink: 0;
  695. }
  696. .card-title {
  697. font-family: 'Alibaba PuHuiTi', sans-serif;
  698. font-size: 16px;
  699. font-weight: 600;
  700. color: #1e293b;
  701. position: relative;
  702. padding-left: 12px;
  703. }
  704. .card-title::before {
  705. content: '';
  706. position: absolute;
  707. left: 0;
  708. top: 50%;
  709. transform: translateY(-50%);
  710. width: 4px;
  711. height: 18px;
  712. background: linear-gradient(180deg, #3b82f6, #60a5fa);
  713. border-radius: 2px;
  714. }
  715. .card-actions {
  716. display: flex;
  717. gap: 8px;
  718. }
  719. .card-body {
  720. flex: 1;
  721. overflow: auto;
  722. padding: 12px 16px;
  723. }
  724. /* ==================== 核心数据卡片 ==================== */
  725. .stats-section {
  726. min-height: 180px;
  727. flex-shrink: 0;
  728. padding: 16px 20px;
  729. }
  730. .stats-section .card-header {
  731. height: 36px;
  732. padding: 0;
  733. margin-bottom: 12px;
  734. border-bottom: none;
  735. }
  736. .stats-grid {
  737. flex: 1;
  738. display: flex;
  739. gap: 16px;
  740. }
  741. .stat-card {
  742. flex: 1;
  743. background: linear-gradient(135deg, rgba(255,255,255,0.9) 0%, rgba(239,246,255,0.9) 100%);
  744. backdrop-filter: blur(10px);
  745. border-radius: 12px;
  746. border: 1px solid rgba(186, 230, 253, 0.5);
  747. display: flex;
  748. justify-content: space-between;
  749. align-items: center;
  750. padding: 16px;
  751. transition: all 0.3s ease;
  752. }
  753. .stat-card:hover {
  754. transform: translateY(-4px);
  755. box-shadow: 0 8px 25px rgba(59, 130, 246, 0.12);
  756. border-color: rgba(96, 165, 250, 0.5);
  757. }
  758. .stat-card-left {
  759. display: flex;
  760. align-items: center;
  761. gap: 12px;
  762. }
  763. .stat-icon-wrap {
  764. width: 44px;
  765. height: 44px;
  766. background: linear-gradient(135deg, #dbeafe, #bfdbfe);
  767. border-radius: 12px;
  768. display: flex;
  769. align-items: center;
  770. justify-content: center;
  771. box-shadow: 0 2px 8px rgba(59, 130, 246, 0.1);
  772. }
  773. .stat-icon { font-size: 22px; }
  774. .stat-label { font-size: 14px; color: #64748b; font-weight: 500; }
  775. .stat-card-right { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; }
  776. .stat-card-value { display: flex; align-items: baseline; gap: 4px; }
  777. .stat-value { font-size: 28px; font-weight: 700; color: #1d4ed8; line-height: 1; }
  778. .stat-unit { font-size: 14px; color: #94a3b8; }
  779. .stat-time { font-size: 12px; color: #94a3b8; }
  780. /* ==================== Element Plus 表格样式覆盖 ==================== */
  781. .card-body :deep(.el-table) {
  782. background: transparent;
  783. --el-table-bg-color: transparent;
  784. --el-table-tr-bg-color: transparent;
  785. --el-table-header-bg-color: rgba(241, 245, 249, 0.8);
  786. --el-table-row-hover-bg-color: rgba(219, 234, 254, 0.3);
  787. --el-table-border-color: rgba(226, 232, 240, 0.6);
  788. --el-table-text-color: #64748b;
  789. --el-table-header-text-color: #475569;
  790. font-size: 14px;
  791. }
  792. .card-body :deep(.el-table__header th) {
  793. font-weight: 600;
  794. }
  795. .value-highlight { font-weight: 600; color: #1d4ed8; }
  796. /* ==================== 快捷功能网格 ==================== */
  797. .quick-grid {
  798. flex: 1;
  799. display: grid;
  800. grid-template-columns: repeat(3, 1fr);
  801. grid-template-rows: repeat(2, 1fr);
  802. gap: 14px;
  803. padding: 16px 20px;
  804. }
  805. .quick-item {
  806. display: flex;
  807. flex-direction: column;
  808. align-items: center;
  809. justify-content: center;
  810. gap: 10px;
  811. background: rgba(248, 250, 252, 0.8);
  812. border: 1px solid rgba(226, 232, 240, 0.6);
  813. border-radius: 12px;
  814. cursor: pointer;
  815. transition: all 0.3s ease;
  816. }
  817. .quick-item:hover {
  818. background: rgba(219, 234, 254, 0.5);
  819. border-color: rgba(96, 165, 250, 0.4);
  820. transform: translateY(-3px);
  821. box-shadow: 0 6px 20px rgba(59, 130, 246, 0.1);
  822. }
  823. .quick-icon {
  824. width: 48px;
  825. height: 48px;
  826. border-radius: 14px;
  827. display: flex;
  828. align-items: center;
  829. justify-content: center;
  830. font-size: 24px;
  831. box-shadow: 0 2px 8px rgba(0,0,0,0.05);
  832. }
  833. .quick-label { font-size: 13px; font-weight: 500; color: #475569; }
  834. /* ==================== 中间区域布局 ==================== */
  835. .middle-section { display: flex; gap: 20px; min-height: 600px; flex-shrink: 0; }
  836. .middle-left { flex: 55; display: flex; flex-direction: column; gap: 20px; }
  837. .middle-right { flex: 45; display: flex; flex-direction: column; gap: 20px; }
  838. .warning-panel { min-height: 220px; }
  839. .todo-panel { flex: 1; min-height: 0; }
  840. .todo-panel .card-body { overflow: hidden; }
  841. .device-panel { min-height: 280px; }
  842. .weather-panel { flex-shrink: 0; }
  843. .quick-panel { flex-shrink: 0; }
  844. /* ==================== 预警信息样式 ==================== */
  845. .warning-list {
  846. display: flex;
  847. flex-direction: column;
  848. gap: 12px;
  849. }
  850. .warning-item {
  851. display: flex;
  852. align-items: center;
  853. gap: 12px;
  854. padding: 12px;
  855. border-radius: 8px;
  856. background: rgba(248, 250, 252, 0.8);
  857. border: 1px solid rgba(226, 232, 240, 0.6);
  858. transition: all 0.2s ease;
  859. }
  860. .warning-item:hover {
  861. background: rgba(241, 245, 249, 0.9);
  862. }
  863. .warning-danger {
  864. border-left: 4px solid #ef4444;
  865. }
  866. .warning-warning {
  867. border-left: 4px solid #f59e0b;
  868. }
  869. .warning-info {
  870. border-left: 4px solid #3b82f6;
  871. }
  872. .warning-icon {
  873. width: 36px;
  874. height: 36px;
  875. border-radius: 8px;
  876. display: flex;
  877. align-items: center;
  878. justify-content: center;
  879. font-size: 18px;
  880. }
  881. .warning-danger .warning-icon {
  882. background: linear-gradient(135deg, #fee2e2, #fca5a5);
  883. color: #dc2626;
  884. }
  885. .warning-warning .warning-icon {
  886. background: linear-gradient(135deg, #fef3c7, #fcd34d);
  887. color: #d97706;
  888. }
  889. .warning-info .warning-icon {
  890. background: linear-gradient(135deg, #dbeafe, #93c5fd);
  891. color: #2563eb;
  892. }
  893. .warning-content {
  894. flex: 1;
  895. min-width: 0;
  896. }
  897. .warning-title {
  898. font-size: 14px;
  899. font-weight: 500;
  900. color: #1e293b;
  901. margin-bottom: 4px;
  902. }
  903. .warning-meta {
  904. display: flex;
  905. gap: 12px;
  906. font-size: 12px;
  907. color: #94a3b8;
  908. }
  909. .warning-badge :deep(.el-badge__content) {
  910. top: -2px;
  911. right: 8px;
  912. }
  913. /* ==================== 设备状态样式 ==================== */
  914. .device-stats {
  915. display: grid;
  916. grid-template-columns: repeat(2, 1fr);
  917. gap: 10px;
  918. padding: 14px 16px;
  919. border-bottom: 1px solid rgba(226, 232, 240, 0.6);
  920. }
  921. .device-stat-item {
  922. display: flex;
  923. align-items: center;
  924. gap: 10px;
  925. }
  926. .device-stat-icon {
  927. width: 40px;
  928. height: 40px;
  929. border-radius: 10px;
  930. display: flex;
  931. align-items: center;
  932. justify-content: center;
  933. font-size: 20px;
  934. }
  935. .device-stat-info {
  936. display: flex;
  937. flex-direction: column;
  938. }
  939. .device-stat-value {
  940. font-size: 18px;
  941. font-weight: 600;
  942. color: #1e293b;
  943. line-height: 1.2;
  944. }
  945. .device-stat-label {
  946. font-size: 12px;
  947. color: #94a3b8;
  948. }
  949. .device-list {
  950. flex: 1;
  951. overflow-y: auto;
  952. padding: 12px 16px;
  953. }
  954. .device-item {
  955. display: flex;
  956. justify-content: space-between;
  957. align-items: center;
  958. padding: 10px 12px;
  959. border-radius: 6px;
  960. background: rgba(248, 250, 252, 0.5);
  961. margin-bottom: 8px;
  962. transition: background 0.2s ease;
  963. }
  964. .device-item:hover {
  965. background: rgba(241, 245, 249, 0.8);
  966. }
  967. .device-info {
  968. display: flex;
  969. flex-direction: column;
  970. gap: 2px;
  971. }
  972. .device-name {
  973. font-size: 14px;
  974. font-weight: 500;
  975. color: #334155;
  976. }
  977. .device-location {
  978. font-size: 12px;
  979. color: #94a3b8;
  980. }
  981. /* ==================== 气象信息样式 ==================== */
  982. .weather-content {
  983. padding: 16px 20px;
  984. }
  985. .weather-main {
  986. display: flex;
  987. align-items: center;
  988. gap: 16px;
  989. margin-bottom: 16px;
  990. }
  991. .weather-icon {
  992. font-size: 48px;
  993. line-height: 1;
  994. }
  995. .weather-info {
  996. display: flex;
  997. flex-direction: column;
  998. }
  999. .weather-temp {
  1000. font-size: 32px;
  1001. font-weight: 700;
  1002. color: #1e293b;
  1003. line-height: 1.2;
  1004. }
  1005. .weather-desc {
  1006. font-size: 14px;
  1007. color: #64748b;
  1008. }
  1009. .weather-update {
  1010. font-size: 12px;
  1011. color: #94a3b8;
  1012. }
  1013. .weather-details {
  1014. display: grid;
  1015. grid-template-columns: repeat(3, 1fr);
  1016. gap: 12px;
  1017. padding-bottom: 16px;
  1018. border-bottom: 1px solid rgba(226, 232, 240, 0.6);
  1019. margin-bottom: 16px;
  1020. }
  1021. .weather-detail-item {
  1022. display: flex;
  1023. flex-direction: column;
  1024. align-items: center;
  1025. gap: 4px;
  1026. }
  1027. .detail-label {
  1028. font-size: 12px;
  1029. color: #94a3b8;
  1030. }
  1031. .detail-value {
  1032. font-size: 14px;
  1033. font-weight: 600;
  1034. color: #334155;
  1035. }
  1036. .weather-forecast {
  1037. display: grid;
  1038. grid-template-columns: repeat(3, 1fr);
  1039. gap: 12px;
  1040. }
  1041. .forecast-item {
  1042. display: flex;
  1043. flex-direction: column;
  1044. align-items: center;
  1045. gap: 6px;
  1046. padding: 10px;
  1047. border-radius: 8px;
  1048. background: rgba(248, 250, 252, 0.8);
  1049. }
  1050. .forecast-day {
  1051. font-size: 12px;
  1052. color: #64748b;
  1053. font-weight: 500;
  1054. }
  1055. .forecast-icon {
  1056. font-size: 24px;
  1057. }
  1058. .forecast-temp {
  1059. font-size: 12px;
  1060. color: #475569;
  1061. }
  1062. /* ==================== 今日调度样式 ==================== */
  1063. .schedule-panel {
  1064. min-height: 200px;
  1065. }
  1066. .stats-table-panel {
  1067. min-height: 250px;
  1068. }
  1069. /* ==================== 底部区域(快捷功能 + 气象) ==================== */
  1070. .bottom-section {
  1071. display: flex;
  1072. gap: 20px;
  1073. min-height: 200px;
  1074. flex-shrink: 0;
  1075. }
  1076. .bottom-section .quick-panel {
  1077. flex: 40;
  1078. }
  1079. .bottom-section .weather-panel {
  1080. flex: 60;
  1081. }
  1082. .schedule-list {
  1083. display: flex;
  1084. flex-direction: column;
  1085. gap: 12px;
  1086. }
  1087. .schedule-item {
  1088. display: flex;
  1089. align-items: center;
  1090. gap: 16px;
  1091. padding: 12px;
  1092. border-radius: 8px;
  1093. background: rgba(248, 250, 252, 0.8);
  1094. border: 1px solid rgba(226, 232, 240, 0.6);
  1095. }
  1096. .schedule-time {
  1097. font-size: 14px;
  1098. font-weight: 600;
  1099. color: #3b82f6;
  1100. min-width: 50px;
  1101. }
  1102. .schedule-content {
  1103. flex: 1;
  1104. min-width: 0;
  1105. }
  1106. .schedule-title {
  1107. font-size: 14px;
  1108. font-weight: 500;
  1109. color: #1e293b;
  1110. margin-bottom: 4px;
  1111. }
  1112. .schedule-desc {
  1113. font-size: 12px;
  1114. color: #94a3b8;
  1115. overflow: hidden;
  1116. text-overflow: ellipsis;
  1117. white-space: nowrap;
  1118. }
  1119. /* ==================== 趋势样式 ==================== */
  1120. .trend-up {
  1121. color: #52c41a;
  1122. }
  1123. .trend-down {
  1124. color: #ff4d4f;
  1125. }
  1126. /* ==================== 迷你图表 ==================== */
  1127. .mini-chart {
  1128. height: 30px;
  1129. display: flex;
  1130. align-items: flex-end;
  1131. }
  1132. .chart-bar {
  1133. width: 16px;
  1134. min-height: 4px;
  1135. border-radius: 2px 2px 0 0;
  1136. }
  1137. /* ==================== 底部区域 ==================== */
  1138. .bottom-section .card-header {
  1139. height: 48px;
  1140. padding: 0 20px;
  1141. border-bottom: 1px solid rgba(226, 232, 240, 0.6);
  1142. display: flex;
  1143. justify-content: space-between;
  1144. align-items: center;
  1145. }
  1146. .bottom-section .card-body {
  1147. flex: 1;
  1148. overflow: auto;
  1149. padding: 12px 16px;
  1150. }
  1151. .trend { font-weight: 600; font-size: 13px; }
  1152. /* ==================== 滚动条美化 ==================== */
  1153. ::-webkit-scrollbar { width: 6px; height: 6px; }
  1154. ::-webkit-scrollbar-track { background: transparent; }
  1155. ::-webkit-scrollbar-thumb { background: rgba(148, 163, 184, 0.3); border-radius: 3px; }
  1156. ::-webkit-scrollbar-thumb:hover { background: rgba(148, 163, 184, 0.5); }
  1157. </style>