list.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  1. <template>
  2. <view class="container">
  3. <!-- 头部区域 -->
  4. <view class="header">
  5. <view class="back-button" @click="goBack">
  6. <text class="iconfont icon-back">&#xe60e;</text>
  7. </view>
  8. <text class="page-title">选择地块</text>
  9. </view>
  10. <!-- 搜索栏 -->
  11. <view class="search-box">
  12. <view class="search-input">
  13. <text class="iconfont icon-search">&#xe62a;</text>
  14. <input
  15. type="text"
  16. v-model="searchKeyword"
  17. placeholder="搜索地块名称/编号/负责人/农场"
  18. confirm-type="search"
  19. @input="handleSearch"
  20. />
  21. <text v-if="searchKeyword" class="clear-icon" @click="clearSearch">×</text>
  22. </view>
  23. </view>
  24. <!-- 当前选中地块 -->
  25. <view class="current-block">
  26. <view class="block-header">
  27. <view class="left">
  28. <text class="icon-tag">当前</text>
  29. <text class="title">当前地块</text>
  30. </view>
  31. </view>
  32. <view class="current-block-card">
  33. <view class="current-tag">当前</view>
  34. <view class="block-content">
  35. <view class="block-icon">
  36. <image src="/static/icons/location.svg" class="location-icon" mode="aspectFit"></image>
  37. </view>
  38. <view class="block-info">
  39. <!-- 标题区域:显示地块名称 + 所属农场名称 -->
  40. <view class="block-name">{{ currentBlock.name }}</view>
  41. <view class="block-farm">{{ currentBlock.farmName }}</view>
  42. <!-- 次级信息区域:显示地块编号、负责人 -->
  43. <view class="block-meta">
  44. <text>编号:{{ currentBlock.code }}</text>
  45. <text class="separator">|</text>
  46. <text>负责人:{{ currentBlock.manager }}</text>
  47. </view>
  48. <!-- 已有信息:面积、类型、作物 -->
  49. <view class="block-details">
  50. <text>{{ currentBlock.area }}亩</text>
  51. <text class="separator">|</text>
  52. <text>{{ currentBlock.type }}</text>
  53. <text v-if="currentBlock.crop" class="separator">|</text>
  54. <text v-if="currentBlock.crop">{{ currentBlock.crop }}</text>
  55. </view>
  56. </view>
  57. </view>
  58. <view class="block-stats">
  59. <view class="stat-item">
  60. <image src="/static/icons/device.svg" class="stat-icon" mode="aspectFit"></image>
  61. <text>设备:{{ currentBlock.deviceCount }}</text>
  62. </view>
  63. <view class="stat-item">
  64. <image src="/static/icons/online.svg" class="stat-icon online-icon" mode="aspectFit"></image>
  65. <text>在线:{{ currentBlock.onlineDevices }}</text>
  66. </view>
  67. <view class="stat-item" v-if="currentBlock.alerts > 0">
  68. <image src="/static/icons/alert.svg" class="stat-icon alert-icon" mode="aspectFit"></image>
  69. <text class="alert-text">告警:{{ currentBlock.alerts }}</text>
  70. </view>
  71. </view>
  72. </view>
  73. </view>
  74. <!-- 地块列表 -->
  75. <view class="block-list">
  76. <view class="block-header">
  77. <view class="left">
  78. <text class="icon-tag">列表</text>
  79. <text class="title">可选地块列表</text>
  80. </view>
  81. <text class="count">共{{ filteredBlocks.length }}个地块</text>
  82. </view>
  83. <!-- 加载骨架屏 -->
  84. <template v-if="isLoading">
  85. <view class="skeleton-container">
  86. <view class="skeleton-box" v-for="i in 3" :key="i">
  87. <view class="skeleton-header">
  88. <view class="skeleton-icon"></view>
  89. <view class="skeleton-info">
  90. <view class="skeleton-title"></view>
  91. <view class="skeleton-detail"></view>
  92. </view>
  93. </view>
  94. <view class="skeleton-stats"></view>
  95. </view>
  96. </view>
  97. </template>
  98. <!-- 空状态 -->
  99. <template v-else-if="filteredBlocks.length === 0">
  100. <view class="empty-state">
  101. <image src="/static/icons/empty-plots.svg" mode="aspectFit" class="empty-image"></image>
  102. <text class="empty-text">暂无可选地块</text>
  103. <text class="empty-subtext">请添加或关联地块后再试</text>
  104. </view>
  105. </template>
  106. <!-- 地块列表 -->
  107. <template v-else>
  108. <view class="block-cards">
  109. <view
  110. class="block-card"
  111. v-for="block in filteredBlocks"
  112. :key="block.id"
  113. :class="{'active-block': currentBlock.id === block.id, 'clickable': currentBlock.id !== block.id}"
  114. @click="handleBlockClick(block)"
  115. >
  116. <view v-if="block.status === 'active'" class="status-badge status-active-badge">使用中</view>
  117. <view v-if="block.status === 'idle'" class="status-badge status-idle-badge">闲置</view>
  118. <view v-if="block.status === 'maintenance'" class="status-badge status-maintenance-badge">维护中</view>
  119. <view class="block-content">
  120. <view class="block-icon">
  121. <image src="/static/icons/location.svg" class="location-icon" mode="aspectFit"></image>
  122. </view>
  123. <view class="block-info">
  124. <!-- 标题区域:显示地块名称 + 所属农场名称 -->
  125. <view class="block-name">{{ block.name }}</view>
  126. <view class="block-farm">{{ block.farmName }}</view>
  127. <!-- 次级信息区域:显示地块编号、负责人 -->
  128. <view class="block-meta">
  129. <text>编号:{{ block.code }}</text>
  130. <text class="separator">|</text>
  131. <text>负责人:{{ block.manager }}</text>
  132. </view>
  133. <!-- 已有信息:面积、类型、作物 -->
  134. <view class="block-details">
  135. <text>{{ block.area }}亩</text>
  136. <text class="separator">|</text>
  137. <text>{{ block.type }}</text>
  138. <text v-if="block.crop" class="separator">|</text>
  139. <text v-if="block.crop">{{ block.crop }}</text>
  140. </view>
  141. </view>
  142. </view>
  143. <view class="block-stats">
  144. <view class="stat-item">
  145. <image src="/static/icons/device.svg" class="stat-icon" mode="aspectFit"></image>
  146. <text>设备:{{ block.deviceCount }}</text>
  147. </view>
  148. <view class="stat-item">
  149. <image src="/static/icons/online.svg" class="stat-icon online-icon" mode="aspectFit"></image>
  150. <text>在线:{{ block.onlineDevices }}</text>
  151. </view>
  152. <view class="stat-item" v-if="block.alerts > 0">
  153. <image src="/static/icons/alert.svg" class="stat-icon alert-icon" mode="aspectFit"></image>
  154. <text class="alert-text">告警:{{ block.alerts }}</text>
  155. </view>
  156. </view>
  157. </view>
  158. </view>
  159. </template>
  160. </view>
  161. </view>
  162. </template>
  163. <script>
  164. export default {
  165. data() {
  166. return {
  167. isLoading: true,
  168. searchKeyword: '',
  169. currentBlock: {
  170. id: 'plot001',
  171. code: 'FK20230001',
  172. name: '东区智慧农场',
  173. farmName: '绿色生态农业基地',
  174. manager: '张三',
  175. area: 15.5,
  176. type: '大棚',
  177. crop: '西红柿',
  178. status: 'active',
  179. deviceCount: 12,
  180. onlineDevices: 10,
  181. alerts: 1
  182. },
  183. blocks: [
  184. {
  185. id: 'plot001',
  186. code: 'FK20230001',
  187. name: '东区智慧农场',
  188. farmName: '绿色生态农业基地',
  189. manager: '张三',
  190. area: 15.5,
  191. type: '大棚',
  192. crop: '西红柿',
  193. status: 'active',
  194. deviceCount: 12,
  195. onlineDevices: 10,
  196. alerts: 1
  197. },
  198. {
  199. id: 'plot002',
  200. code: 'FK20230002',
  201. name: '南区试验田',
  202. farmName: '绿色生态农业基地',
  203. manager: '李四',
  204. area: 8.2,
  205. type: '露天',
  206. crop: '玉米',
  207. status: 'active',
  208. deviceCount: 8,
  209. onlineDevices: 6,
  210. alerts: 0
  211. },
  212. {
  213. id: 'plot003',
  214. code: 'FK20230003',
  215. name: '西区果园',
  216. farmName: '现代农业示范园',
  217. manager: '王五',
  218. area: 20.0,
  219. type: '果园',
  220. crop: '苹果',
  221. status: 'maintenance',
  222. deviceCount: 15,
  223. onlineDevices: 5,
  224. alerts: 3
  225. },
  226. {
  227. id: 'plot004',
  228. code: 'FK20230004',
  229. name: '北区水稻田',
  230. farmName: '水稻研究中心',
  231. manager: '赵六',
  232. area: 30.5,
  233. type: '水田',
  234. crop: '水稻',
  235. status: 'active',
  236. deviceCount: 10,
  237. onlineDevices: 9,
  238. alerts: 0
  239. },
  240. {
  241. id: 'plot005',
  242. code: 'FK20230005',
  243. name: '中央实验区',
  244. farmName: '农业科技研究所',
  245. manager: '钱七',
  246. area: 5.0,
  247. type: '育苗',
  248. crop: '',
  249. status: 'idle',
  250. deviceCount: 6,
  251. onlineDevices: 2,
  252. alerts: 0
  253. }
  254. ]
  255. }
  256. },
  257. computed: {
  258. filteredBlocks() {
  259. if (!this.searchKeyword) return this.blocks
  260. const keyword = this.searchKeyword.toLowerCase()
  261. return this.blocks.filter(block => {
  262. return block.name.toLowerCase().includes(keyword) ||
  263. block.id.toLowerCase().includes(keyword) ||
  264. block.code.toLowerCase().includes(keyword) ||
  265. block.manager.toLowerCase().includes(keyword) ||
  266. block.farmName.toLowerCase().includes(keyword)
  267. })
  268. }
  269. },
  270. mounted() {
  271. // 尝试从本地存储中读取上次选择的地块
  272. try {
  273. const savedPlot = uni.getStorageSync('current_plot')
  274. if (savedPlot) {
  275. const plotData = JSON.parse(savedPlot)
  276. // 如果存储的地块在当前地块列表中,则设置为当前选中地块
  277. const matchedBlock = this.blocks.find(block => block.id === plotData.id)
  278. if (matchedBlock) {
  279. this.currentBlock = matchedBlock
  280. }
  281. }
  282. } catch (e) {
  283. console.error('读取保存的地块失败', e)
  284. }
  285. // 模拟加载数据
  286. setTimeout(() => {
  287. this.isLoading = false
  288. }, 1000)
  289. },
  290. methods: {
  291. goBack() {
  292. uni.navigateBack()
  293. },
  294. handleSearch() {
  295. // 搜索功能已通过计算属性实现
  296. },
  297. clearSearch() {
  298. this.searchKeyword = ''
  299. },
  300. handleBlockClick(block) {
  301. // 如果点击当前已选中的地块,不做任何操作
  302. if (this.currentBlock.id === block.id) return
  303. // 弹出确认框
  304. uni.showModal({
  305. title: '切换地块',
  306. content: '是否切换到该地块?切换后将查看该地块的相关设备数据。',
  307. cancelText: '取消',
  308. confirmText: '确定',
  309. success: (res) => {
  310. if (res.confirm) {
  311. this.selectBlock(block)
  312. }
  313. }
  314. })
  315. },
  316. selectBlock(block) {
  317. this.currentBlock = block
  318. // 保存当前选择的地块到本地存储
  319. try {
  320. uni.setStorageSync('current_plot', JSON.stringify({
  321. id: block.id,
  322. code: block.code,
  323. name: block.name,
  324. timestamp: Date.now()
  325. }))
  326. } catch (e) {
  327. console.error('保存地块选择状态失败', e)
  328. }
  329. // 触发选择事件,传递地块ID
  330. this.$emit('selectBlock', block.id)
  331. // 获取页面参数,检查是否需要返回
  332. const pages = getCurrentPages()
  333. const currentPage = pages[pages.length - 1]
  334. let eventChannel
  335. // 尝试获取页面参数和事件通道
  336. let shouldReturn = true // 默认行为是返回
  337. try {
  338. const options = currentPage.options || {}
  339. // 获取事件通道(如果存在)
  340. eventChannel = currentPage.getOpenerEventChannel && currentPage.getOpenerEventChannel()
  341. // 如果有redirect参数,则跳转到指定页面而不是返回
  342. if (options.redirect) {
  343. shouldReturn = false
  344. uni.redirectTo({
  345. url: decodeURIComponent(options.redirect),
  346. success: () => {
  347. // 跳转成功后传递选中的地块数据
  348. if (eventChannel) {
  349. eventChannel.emit('selectBlockResult', {
  350. success: true,
  351. blockId: block.id,
  352. blockData: block
  353. })
  354. }
  355. }
  356. })
  357. } else if (options.noReturn === 'true') {
  358. // 如果设置了noReturn参数,则不执行返回操作
  359. shouldReturn = false
  360. }
  361. } catch (e) {
  362. console.error('获取页面参数失败', e)
  363. }
  364. // 如果需要返回上一页,则执行返回操作
  365. if (shouldReturn) {
  366. setTimeout(() => {
  367. uni.navigateBack({
  368. success: () => {
  369. // 返回成功后传递选中的地块数据
  370. if (eventChannel) {
  371. eventChannel.emit('selectBlockResult', {
  372. success: true,
  373. blockId: block.id,
  374. blockData: block
  375. })
  376. }
  377. }
  378. })
  379. }, 300)
  380. }
  381. },
  382. getStatusClass(status) {
  383. const statusMap = {
  384. 'active': 'status-active',
  385. 'idle': 'status-idle',
  386. 'maintenance': 'status-maintenance'
  387. }
  388. return statusMap[status] || ''
  389. },
  390. getStatusText(status) {
  391. const statusMap = {
  392. 'active': '使用中',
  393. 'idle': '闲置',
  394. 'maintenance': '维护中'
  395. }
  396. return statusMap[status] || '未知'
  397. },
  398. // 可以被父组件调用的方法
  399. onBack(callback) {
  400. if (typeof callback === 'function') {
  401. callback(this.currentBlock.id)
  402. }
  403. }
  404. }
  405. }
  406. </script>
  407. <style>
  408. .container {
  409. min-height: 100vh;
  410. background-color: #F5F7FA;
  411. padding-bottom: 30rpx;
  412. }
  413. /* 头部导航 */
  414. .header {
  415. display: none; /* 隐藏整个头部标题栏 */
  416. }
  417. .back-button {
  418. width: 60rpx;
  419. height: 60rpx;
  420. display: flex;
  421. align-items: center;
  422. justify-content: center;
  423. }
  424. .icon-back {
  425. font-size: 36rpx;
  426. color: #333;
  427. }
  428. .page-title {
  429. flex: 1;
  430. text-align: center;
  431. font-size: 32rpx;
  432. font-weight: bold;
  433. color: #333;
  434. margin-right: 60rpx; /* 平衡左侧返回按钮 */
  435. }
  436. /* 搜索框 */
  437. .search-box {
  438. padding: 20rpx 30rpx;
  439. background-color: #FFFFFF;
  440. border-bottom: 1px solid rgba(0,0,0,0.03);
  441. margin-bottom: 20rpx;
  442. }
  443. .search-header {
  444. font-size: 28rpx;
  445. color: #333;
  446. font-weight: 500;
  447. margin-bottom: 15rpx;
  448. }
  449. .search-input {
  450. display: flex;
  451. align-items: center;
  452. height: 70rpx;
  453. background-color: #F5F7FA;
  454. border-radius: 35rpx;
  455. padding: 0 30rpx;
  456. box-shadow: inset 0 1rpx 3rpx rgba(0, 0, 0, 0.05);
  457. border: 1rpx solid rgba(0, 0, 0, 0.03);
  458. }
  459. .icon-search {
  460. font-size: 28rpx;
  461. color: #999;
  462. margin-right: 10rpx;
  463. }
  464. .search-input input {
  465. flex: 1;
  466. height: 70rpx;
  467. font-size: 28rpx;
  468. color: #333;
  469. }
  470. .clear-icon {
  471. width: 40rpx;
  472. height: 40rpx;
  473. display: flex;
  474. align-items: center;
  475. justify-content: center;
  476. font-size: 32rpx;
  477. color: #999;
  478. }
  479. /* 块区域通用样式 */
  480. .current-block, .block-list {
  481. margin: 0 30rpx 20rpx;
  482. background-color: #FFFFFF;
  483. padding: 24rpx;
  484. border-radius: 24rpx;
  485. box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
  486. }
  487. .block-header {
  488. display: flex;
  489. justify-content: space-between;
  490. align-items: center;
  491. margin-bottom: 20rpx;
  492. }
  493. .block-header .left {
  494. display: flex;
  495. align-items: center;
  496. }
  497. .icon-tag {
  498. font-size: 24rpx;
  499. color: #FFFFFF;
  500. background: linear-gradient(135deg, #66CC6A 0%, #3BB44A 100%);
  501. padding: 4rpx 16rpx;
  502. border-radius: 6rpx;
  503. margin-right: 16rpx;
  504. box-shadow: 0 2rpx 5rpx rgba(59, 180, 74, 0.2);
  505. }
  506. .block-header .title {
  507. font-size: 30rpx;
  508. font-weight: bold;
  509. color: #333;
  510. }
  511. .block-header .count {
  512. font-size: 24rpx;
  513. color: #999;
  514. background-color: #F5F7FA;
  515. padding: 4rpx 16rpx;
  516. border-radius: 20rpx;
  517. }
  518. /* 当前选中地块卡片 */
  519. .current-block-card {
  520. background: linear-gradient(to right, #F0F8F0, #E7F5E8);
  521. border-radius: 16rpx;
  522. padding: 30rpx;
  523. position: relative;
  524. border: 2rpx solid rgba(59, 180, 74, 0.2);
  525. box-shadow: 0 4rpx 16rpx rgba(59, 180, 74, 0.1);
  526. }
  527. .current-tag {
  528. position: absolute;
  529. top: 0;
  530. right: 20rpx;
  531. background: linear-gradient(135deg, #66CC6A 0%, #3BB44A 100%);
  532. color: white;
  533. font-size: 22rpx;
  534. padding: 6rpx 16rpx;
  535. border-radius: 0 0 12rpx 12rpx;
  536. font-weight: bold;
  537. }
  538. /* 地块列表卡片 */
  539. .block-cards {
  540. display: flex;
  541. flex-direction: column;
  542. gap: 20rpx;
  543. }
  544. .block-card {
  545. background-color: #FFFFFF;
  546. border-radius: 16rpx;
  547. padding: 30rpx;
  548. box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
  549. position: relative;
  550. border: 1rpx solid rgba(0, 0, 0, 0.03);
  551. transition: all 0.3s ease;
  552. }
  553. .block-card:active {
  554. transform: scale(0.98);
  555. box-shadow: 0 1rpx 5rpx rgba(0, 0, 0, 0.03);
  556. }
  557. .block-card.active-block {
  558. border: 2rpx solid #3BB44A;
  559. background-color: #F0F8F0;
  560. box-shadow: 0 4rpx 16rpx rgba(59, 180, 74, 0.1);
  561. }
  562. .block-card.clickable {
  563. cursor: pointer;
  564. }
  565. .block-card.clickable:hover {
  566. box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
  567. }
  568. .block-content {
  569. display: flex;
  570. align-items: flex-start;
  571. position: relative;
  572. margin-bottom: 20rpx;
  573. }
  574. .block-icon {
  575. width: 40rpx;
  576. margin-right: 15rpx;
  577. display: flex;
  578. align-items: center;
  579. justify-content: center;
  580. }
  581. .location-icon {
  582. width: 36rpx;
  583. height: 36rpx;
  584. }
  585. .block-info {
  586. flex: 1;
  587. }
  588. .block-name {
  589. font-size: 32rpx;
  590. font-weight: bold;
  591. color: #333;
  592. margin-bottom: 6rpx;
  593. }
  594. .block-farm {
  595. font-size: 26rpx;
  596. color: #666;
  597. margin-bottom: 10rpx;
  598. }
  599. .block-meta {
  600. font-size: 26rpx;
  601. color: #999;
  602. margin-bottom: 10rpx;
  603. }
  604. .block-details {
  605. font-size: 26rpx;
  606. color: #666;
  607. }
  608. .separator {
  609. margin: 0 10rpx;
  610. color: #ccc;
  611. }
  612. .check-mark {
  613. position: absolute;
  614. right: 0;
  615. top: 0;
  616. }
  617. .check-icon {
  618. width: 48rpx;
  619. height: 48rpx;
  620. }
  621. /* 状态标签 */
  622. .status-badge {
  623. position: absolute;
  624. top: 0;
  625. right: 0;
  626. font-size: 22rpx;
  627. padding: 4rpx 12rpx;
  628. border-radius: 0 16rpx 0 12rpx;
  629. font-weight: 500;
  630. z-index: 1;
  631. }
  632. .status-active-badge {
  633. background-color: #E6F7E6;
  634. color: #3BB44A;
  635. border-left: 1rpx solid rgba(59, 180, 74, 0.2);
  636. border-bottom: 1rpx solid rgba(59, 180, 74, 0.2);
  637. }
  638. .status-idle-badge {
  639. background-color: #F1F2F3;
  640. color: #909399;
  641. border-left: 1rpx solid rgba(144, 147, 153, 0.2);
  642. border-bottom: 1rpx solid rgba(144, 147, 153, 0.2);
  643. }
  644. .status-maintenance-badge {
  645. background-color: #FEF0F0;
  646. color: #F56C6C;
  647. border-left: 1rpx solid rgba(245, 108, 108, 0.2);
  648. border-bottom: 1rpx solid rgba(245, 108, 108, 0.2);
  649. }
  650. /* 地块统计信息 */
  651. .block-stats {
  652. display: flex;
  653. align-items: center;
  654. flex-wrap: wrap;
  655. border-top: 1rpx solid #F0F0F0;
  656. padding-top: 20rpx;
  657. }
  658. .stat-item {
  659. display: flex;
  660. align-items: center;
  661. margin-right: 24rpx;
  662. font-size: 26rpx;
  663. color: #666;
  664. }
  665. .stat-icon {
  666. width: 36rpx;
  667. height: 36rpx;
  668. margin-right: 8rpx;
  669. flex-shrink: 0;
  670. }
  671. .online-icon {
  672. /* 在线图标使用绿色 */
  673. filter: hue-rotate(120deg) saturate(1.2);
  674. }
  675. .alert-icon {
  676. /* 告警图标使用红色 */
  677. filter: hue-rotate(0deg) saturate(1.5);
  678. }
  679. .alert-text {
  680. color: #F56C6C;
  681. }
  682. /* 骨架屏 */
  683. .skeleton-container {
  684. display: flex;
  685. flex-direction: column;
  686. gap: 20rpx;
  687. }
  688. .skeleton-box {
  689. background-color: #FFFFFF;
  690. border-radius: 16rpx;
  691. padding: 20rpx;
  692. margin-bottom: 10rpx;
  693. box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
  694. animation: skeleton-loading 1.5s infinite;
  695. }
  696. @keyframes skeleton-loading {
  697. 0% {
  698. opacity: 0.7;
  699. }
  700. 50% {
  701. opacity: 0.5;
  702. }
  703. 100% {
  704. opacity: 0.7;
  705. }
  706. }
  707. .skeleton-header {
  708. display: flex;
  709. align-items: flex-start;
  710. }
  711. .skeleton-icon {
  712. width: 40rpx;
  713. height: 40rpx;
  714. background-color: #EEEEEE;
  715. border-radius: 8rpx;
  716. margin-right: 15rpx;
  717. }
  718. .skeleton-info {
  719. flex: 1;
  720. }
  721. .skeleton-title {
  722. width: 200rpx;
  723. height: 32rpx;
  724. background-color: #EEEEEE;
  725. margin-bottom: 10rpx;
  726. border-radius: 4rpx;
  727. }
  728. .skeleton-detail {
  729. width: 300rpx;
  730. height: 24rpx;
  731. background-color: #EEEEEE;
  732. border-radius: 4rpx;
  733. }
  734. .skeleton-stats {
  735. margin-top: 16rpx;
  736. padding-top: 16rpx;
  737. border-top: 1rpx solid #F5F5F5;
  738. height: 30rpx;
  739. background-color: #EEEEEE;
  740. border-radius: 4rpx;
  741. }
  742. /* 空状态 */
  743. .empty-state {
  744. display: flex;
  745. flex-direction: column;
  746. align-items: center;
  747. justify-content: center;
  748. padding: 60rpx 0;
  749. }
  750. .empty-image {
  751. width: 240rpx;
  752. height: 240rpx;
  753. margin-bottom: 20rpx;
  754. opacity: 0.7;
  755. }
  756. .empty-text {
  757. font-size: 32rpx;
  758. color: #666;
  759. font-weight: 500;
  760. margin-bottom: 10rpx;
  761. }
  762. .empty-subtext {
  763. font-size: 26rpx;
  764. color: #999;
  765. }
  766. /* 基础图标样式 */
  767. .iconfont {
  768. font-family: "iconfont" !important;
  769. font-style: normal;
  770. -webkit-font-smoothing: antialiased;
  771. -moz-osx-font-smoothing: grayscale;
  772. }
  773. </style>