index.vue 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637
  1. <template>
  2. <view class="dashboard-container">
  3. <!-- 顶部用户信息卡片 -->
  4. <view class="user-info-card">
  5. <view class="user-info">
  6. <text class="greeting">您好,{{ userData.nickname }}</text>
  7. <view class="plot-info">
  8. <text class="plot-label">当前地块:</text>
  9. <text class="plot-name">{{ userData.selectedPlot }}</text>
  10. <view class="switch-plot-btn" v-if="isLogin">
  11. <u-picker
  12. :show="show"
  13. :columns="columns"
  14. :defaultIndex="defaultIndex"
  15. @cancel="handleCancel"
  16. @confirm="onConfirm"
  17. keyName="text"
  18. ></u-picker>
  19. <text @click="handleSwitchPlot">切换</text>
  20. </view>
  21. </view>
  22. </view>
  23. <view class="avatar-container" @click="navigateToProfile">
  24. <view class="avatar">
  25. <image :src="userData.avatar" mode="aspectFill"></image>
  26. </view>
  27. </view>
  28. </view>
  29. <!-- 顶部统计概览 -->
  30. <view class="stats-overview">
  31. <view class="alert-card" v-for="(alert, index) in alertSummaries" :key="index"
  32. @click="navigateToAlertDetail(alert.type)">
  33. <view class="alert-header">
  34. <view class="alert-icon-container">
  35. <image class="custom-icon" :src="alert.iconSrc"></image>
  36. </view>
  37. <text class="alert-title">{{ alert.title }}</text>
  38. </view>
  39. <text class="alert-value">{{ alert.value }}</text>
  40. <text class="alert-description">{{ alert.description }}</text>
  41. </view>
  42. </view>
  43. <!-- 天气卡片 -->
  44. <view class="card weather-card">
  45. <view class="card-header">
  46. <view class="title-section">
  47. <view class="title-line"></view>
  48. <text class="card-title">天气与预报</text>
  49. </view>
  50. </view>
  51. <view class="weather-content">
  52. <view class="current-weather">
  53. <view class="weather-icon-container">
  54. <view class="weather-icon">
  55. <text v-if="weatherData.description === '晴朗'">☀️</text>
  56. <text v-else-if="weatherData.description.includes('雨')">🌧️</text>
  57. <text v-else-if="weatherData.description.includes('云')">⛅</text>
  58. <text v-else>🌤️</text>
  59. </view>
  60. </view>
  61. <view class="weather-details">
  62. <text class="weather-temp">{{ weatherData.temperature }}°C</text>
  63. <text class="weather-desc">{{ weatherData.description }}</text>
  64. </view>
  65. </view>
  66. <view class="weather-metrics">
  67. <view class="weather-metric">
  68. <text class="metric-label">湿度</text>
  69. <text class="metric-value">{{ weatherData.humidity }}%</text>
  70. </view>
  71. <view class="vertical-divider"></view>
  72. <view class="weather-metric">
  73. <text class="metric-label">风力</text>
  74. <text class="metric-value">{{ weatherData.windLevel }} 级</text>
  75. </view>
  76. <view class="vertical-divider"></view>
  77. <view class="weather-metric">
  78. <text class="metric-label">降水量</text>
  79. <text class="metric-value">{{ weatherData.rainfall }} mm</text>
  80. </view>
  81. </view>
  82. <view class="weather-advice">
  83. <view class="advice-header">
  84. <view class="icon-tile small">
  85. <u-icon name="info" color="#ffffff" size="14"></u-icon>
  86. </view>
  87. <text class="advice-title">今日建议:</text>
  88. </view>
  89. <text class="advice-content">{{ weatherData.advice }}</text>
  90. </view>
  91. </view>
  92. </view>
  93. <!-- 农场绩效卡片 -->
  94. <view class="card farm-performance">
  95. <view class="card-header">
  96. <view class="title-section">
  97. <view class="title-line"></view>
  98. <text class="card-title">产值分析</text>
  99. </view>
  100. <view class="period-selector">
  101. <text class="period" :class="{ active: currentPeriod === 'month' }"
  102. @click="changePeriod('month')">月</text>
  103. <text class="period" :class="{ active: currentPeriod === 'quarter' }"
  104. @click="changePeriod('quarter')">季</text>
  105. <text class="period" :class="{ active: currentPeriod === 'year' }"
  106. @click="changePeriod('year')">年</text>
  107. </view>
  108. </view>
  109. <view class="performance-stats">
  110. <view class="metric-column">
  111. <text class="metric-value">128<text class="metric-unit">亩</text></text>
  112. <text class="metric-label">管理面积总计</text>
  113. <view class="growth positive">
  114. <u-icon name="arrow-upward" color="#3BB44A" size="12"></u-icon>
  115. <text>比上月增长12%</text>
  116. </view>
  117. </view>
  118. <view class="metric-divider"></view>
  119. <view class="metric-column">
  120. <text class="metric-value">¥36,480</text>
  121. <text class="metric-label">预计产值</text>
  122. <view class="growth positive">
  123. <u-icon name="arrow-upward" color="#3BB44A" size="12"></u-icon>
  124. <text>比上月增长8.2%</text>
  125. </view>
  126. </view>
  127. </view>
  128. <view class="chart-container">
  129. <view class="chart-header">
  130. <text class="chart-title">产值趋势 (万元)</text>
  131. <view class="chart-legend">
  132. <view class="legend-item">
  133. <view class="legend-color" style="background: #3BB44A;"></view>
  134. <text>今年</text>
  135. </view>
  136. <view class="legend-item">
  137. <view class="legend-color" style="background: #E0E0E0;"></view>
  138. <text>去年</text>
  139. </view>
  140. </view>
  141. </view>
  142. <view class="chart-body">
  143. <view class="y-axis">
  144. <text v-for="(value, index) in [4, 3, 2, 1, 0]" :key="index">{{ value }}</text>
  145. </view>
  146. <view class="bars-container">
  147. <view class="month-group" v-for="(month, index) in farmPerformanceData.months" :key="index">
  148. <view class="bar-wrapper">
  149. <view class="bar last-year"
  150. :style="{ height: (farmPerformanceData.lastYearValues[index] / 4) * 100 + '%' }">
  151. </view>
  152. <view class="bar this-year"
  153. :style="{ height: (farmPerformanceData.thisYearValues[index] / 4) * 100 + '%' }">
  154. </view>
  155. </view>
  156. <text class="month-label">{{ month }}</text>
  157. </view>
  158. </view>
  159. </view>
  160. </view>
  161. </view>
  162. <!-- 监控设备概览 -->
  163. <view class="card device-overview">
  164. <view class="card-header">
  165. <view class="title-section">
  166. <view class="title-line"></view>
  167. <text class="card-title">监控设备概览</text>
  168. </view>
  169. </view>
  170. <view class="device-metrics-grid">
  171. <view class="device-metric-card" v-for="(metric, index) in deviceMetrics" :key="index">
  172. <view class="device-metric-header">
  173. <view class="icon-tile" :style="{ background: metric.gradient }">
  174. <u-icon :name="metric.icon" color="#ffffff" size="18"></u-icon>
  175. </view>
  176. <text class="metric-name">{{ metric.name }}</text>
  177. </view>
  178. <view class="device-metric-value">{{ metric.value }}</view>
  179. <view class="device-metric-trend" :class="metric.trend.type">
  180. <u-icon :name="metric.trend.type === 'up' ? 'arrow-upward' : 'arrow-downward'"
  181. :color="metric.trend.type === 'up' ? '#3BB44A' : '#FF5252'" size="14"></u-icon>
  182. <text>{{ metric.trend.value }}</text>
  183. </view>
  184. </view>
  185. </view>
  186. </view>
  187. <!-- 农业机械活动 -->
  188. <view class="card machinery-activity">
  189. <view class="card-header">
  190. <view class="title-section">
  191. <view class="title-line"></view>
  192. <text class="card-title">农机作业概览</text>
  193. </view>
  194. </view>
  195. <view class="machinery-metrics-grid">
  196. <view class="machinery-metric-card" v-for="(metric, index) in machineryMetrics" :key="index">
  197. <view class="machinery-metric-header">
  198. <view class="icon-tile" :style="{ background: metric.gradient }">
  199. <u-icon :name="metric.icon" color="#ffffff" size="18"></u-icon>
  200. </view>
  201. <text class="metric-name">{{ metric.name }}</text>
  202. </view>
  203. <view class="machinery-metric-value">{{ metric.value }}</view>
  204. <view v-if="metric.trend" class="machinery-metric-trend" :class="metric.trend.type">
  205. <u-icon :name="metric.trend.type === 'up' ? 'arrow-upward' : 'arrow-downward'"
  206. :color="metric.trend.type === 'up' ? '#3BB44A' : '#FF5252'" size="14"></u-icon>
  207. <text>{{ metric.trend.value }}</text>
  208. </view>
  209. <view v-else class="machinery-metric-unit">
  210. <text>{{ metric.unit }}</text>
  211. </view>
  212. </view>
  213. </view>
  214. </view>
  215. <!-- 农场活动卡片 -->
  216. <view class="card farm-activities">
  217. <view class="card-header">
  218. <view class="title-section">
  219. <view class="title-line"></view>
  220. <text class="card-title">农事活动</text>
  221. </view>
  222. <view class="action-button">
  223. <text>查看全部</text>
  224. <u-icon name="arrow-right" color="#ffffff" size="14"></u-icon>
  225. </view>
  226. </view>
  227. <view class="activities-list">
  228. <view class="activity-item" v-for="(activity, index) in farmData.recentActivities" :key="index">
  229. <view class="activity-dot" :class="activity.status"></view>
  230. <view class="activity-details">
  231. <view class="activity-title-row">
  232. <text class="activity-title">{{ activity.title }}</text>
  233. <text class="activity-date">{{ activity.date }}</text>
  234. </view>
  235. <view class="activity-meta-row">
  236. <text class="activity-executor">{{ activity.executor }}</text>
  237. <view class="activity-action" @click.stop="navigateToActivity(activity)">
  238. <text class="action-text">查看</text>
  239. <u-icon name="arrow-right" color="#3BB44A" size="14"></u-icon>
  240. </view>
  241. </view>
  242. </view>
  243. </view>
  244. </view>
  245. </view>
  246. </view>
  247. </template>
  248. <script setup>
  249. import { ref, reactive, computed, onMounted } from 'vue'
  250. import { onShow } from '@dcloudio/uni-app'
  251. import storage from "@/utils/storage.js"
  252. import { listFieldName } from "@/api/services/field.js"
  253. // 响应式数�?
  254. const isLogin = ref(false)
  255. const show = ref(false)
  256. const columns = ref([[]])
  257. const defaultIndex = ref([0])
  258. // 用户数据
  259. const userData = reactive({
  260. nickname: '游客',
  261. selectedPlot: '未选择地块',
  262. avatar: '/static/images/user-avatar.png',
  263. userId: null
  264. })
  265. // 农场数据
  266. const farmData = reactive({
  267. plotCount: 5,
  268. deviceCount: 12,
  269. deviceOnlineRate: 85,
  270. taskCompletionRate: 76,
  271. recentActivities: [
  272. {
  273. title: '稻田水稻施肥',
  274. date: '2023-05-15',
  275. status: 'pending',
  276. id: 1,
  277. executor: '李四'
  278. },
  279. {
  280. title: '南地块除草',
  281. date: '2023-05-12',
  282. status: 'completed',
  283. id: 2,
  284. executor: '王五'
  285. },
  286. {
  287. title: '西北区域杀虫',
  288. date: '2023-05-08',
  289. status: 'completed',
  290. id: 3,
  291. executor: '张三'
  292. }
  293. ]
  294. })
  295. // 天气数据
  296. const weatherData = reactive({
  297. temperature: 28,
  298. description: '晴朗',
  299. humidity: 65,
  300. windLevel: 3,
  301. rainfall: 0,
  302. advice: '今日适宜进行春玉米防虫作业,注意水分管理。'
  303. })
  304. // 作物数据
  305. const crops = ref([
  306. {
  307. name: '水稻',
  308. area: 48,
  309. progress: 75,
  310. icon: '🌾',
  311. bgColor: '#4CAF50'
  312. },
  313. {
  314. name: '小麦',
  315. area: 36,
  316. progress: 60,
  317. icon: '🌿',
  318. bgColor: '#66BB6A'
  319. },
  320. {
  321. name: '玉米',
  322. area: 32,
  323. progress: 85,
  324. icon: '🌽',
  325. bgColor: '#43A047'
  326. },
  327. {
  328. name: '大豆',
  329. area: 12,
  330. progress: 40,
  331. icon: '🫘',
  332. bgColor: '#388E3C'
  333. }
  334. ])
  335. // 监控设备指标
  336. const deviceMetrics = ref([
  337. {
  338. name: '在线设备',
  339. value: '28',
  340. icon: 'wifi',
  341. gradient: 'linear-gradient(135deg, #26A69A, #00796B)',
  342. trend: {
  343. type: 'up',
  344. value: '5.2%'
  345. }
  346. },
  347. {
  348. name: '告警设备',
  349. value: '3',
  350. icon: 'error-circle',
  351. gradient: 'linear-gradient(135deg, #FF7043, #E64A19)',
  352. trend: {
  353. type: 'down',
  354. value: '2.1%'
  355. }
  356. },
  357. {
  358. name: '离线设备',
  359. value: '5',
  360. icon: 'close-circle',
  361. gradient: 'linear-gradient(135deg, #78909C, #455A64)',
  362. trend: {
  363. type: 'down',
  364. value: '1.8%'
  365. }
  366. },
  367. {
  368. name: '数据稳定性',
  369. value: '96.3%',
  370. icon: 'checkmark-circle',
  371. gradient: 'linear-gradient(135deg, #66BB6A, #388E3C)',
  372. trend: {
  373. type: 'up',
  374. value: '0.5%'
  375. }
  376. }
  377. ])
  378. // 农业机械指标
  379. const machineryMetrics = ref([
  380. {
  381. name: '今日运行时长',
  382. value: '36.5',
  383. unit: '小时',
  384. icon: 'clock',
  385. gradient: 'linear-gradient(135deg, #42A5F5, #1976D2)'
  386. },
  387. {
  388. name: '今日作业地块',
  389. value: '8',
  390. unit: '块',
  391. icon: 'map',
  392. gradient: 'linear-gradient(135deg, #66BB6A, #388E3C)'
  393. },
  394. {
  395. name: '今日执行任务',
  396. value: '12',
  397. unit: '个',
  398. icon: 'calendar',
  399. gradient: 'linear-gradient(135deg, #FFA726, #F57C00)'
  400. },
  401. {
  402. name: '使用率',
  403. value: '78.2%',
  404. icon: 'star',
  405. gradient: 'linear-gradient(135deg, #5C6BC0, #3949AB)',
  406. trend: {
  407. type: 'up',
  408. value: '3.7%'
  409. }
  410. }
  411. ])
  412. // 当前选择的周�?
  413. const currentPeriod = ref('month')
  414. // 警报摘要数据
  415. const alertSummaries = ref([
  416. {
  417. title: '设备离线',
  418. value: '3 台',
  419. iconSrc: '/static/icons/offline.png',
  420. description: '最长离线时长:26 小时',
  421. type: 'device-offline'
  422. },
  423. {
  424. title: '虫害预警',
  425. value: '稻飞虱|小地老虎',
  426. iconSrc: '/static/icons/Pest_Alert.png',
  427. description: '预警等级:中',
  428. type: 'pest-warning'
  429. },
  430. {
  431. title: '气象预警',
  432. value: '强风(8级)',
  433. iconSrc: '/static/icons/weather_risk.png',
  434. description: '预计:未来 12 小时内',
  435. type: 'weather-risk'
  436. },
  437. {
  438. title: '作业延迟',
  439. value: '5 项',
  440. iconSrc: '/static/icons/task_delay.png',
  441. description: '最长延迟:3 天',
  442. type: 'task-delay'
  443. }
  444. ])
  445. // 农场绩效图表数据 - 月度数据
  446. const monthlyData = reactive({
  447. months: ['1月', '2月', '3月', '4月', '5月', '6月'],
  448. thisYearValues: [2.8, 3.4, 2.9, 3.6, 3.8, 3.2],
  449. lastYearValues: [2.5, 2.8, 2.4, 3.0, 3.2, 2.7]
  450. })
  451. // 季度数据
  452. const quarterlyData = reactive({
  453. months: ['Q1', 'Q2', 'Q3', 'Q4'],
  454. thisYearValues: [3.2, 3.7, 4.0, 3.5],
  455. lastYearValues: [2.7, 3.3, 3.6, 3.0]
  456. })
  457. // 年度数据
  458. const yearlyData = reactive({
  459. months: ['2019', '2020', '2021', '2022', '2023'],
  460. thisYearValues: [2.2, 2.5, 3.0, 3.5, 3.8],
  461. lastYearValues: [2.0, 2.3, 2.7, 3.2, 3.4]
  462. })
  463. // 计算属�?- 核心统计数据
  464. const coreStats = computed(() => {
  465. return [
  466. {
  467. label: '地块',
  468. value: farmData.plotCount,
  469. icon: 'map',
  470. },
  471. {
  472. label: '设备',
  473. value: farmData.deviceCount,
  474. icon: 'setting',
  475. },
  476. {
  477. label: '设备在线',
  478. value: farmData.deviceOnlineRate + '%',
  479. icon: 'wifi',
  480. },
  481. {
  482. label: '任务完成',
  483. value: farmData.taskCompletionRate + '%',
  484. icon: 'checkmark-circle',
  485. }
  486. ]
  487. })
  488. // 计算属�?- 根据当前周期计算要显示的数据
  489. const farmPerformanceData = computed(() => {
  490. if (currentPeriod.value === 'month') {
  491. return monthlyData
  492. } else if (currentPeriod.value === 'quarter') {
  493. return quarterlyData
  494. } else {
  495. return yearlyData
  496. }
  497. })
  498. // 方法定义
  499. const loadPhots = (userId) => {
  500. console.log("加载用户字段", userId)
  501. listFieldName(userId)
  502. .then(res => {
  503. const { data } = res || {}
  504. if (data?.code !== 200 || !Array.isArray(data.data)) {
  505. console.warn("接口返回异常或数据格式不正确", res)
  506. return
  507. }
  508. const fieldList = data.data
  509. console.log("fieldList:", fieldList)
  510. const fields = []
  511. fieldList.forEach(item => {
  512. const fieldName = item?.fieldName
  513. if (fieldName && !fields.includes(fieldName)) {
  514. fields.push({
  515. text: fieldName,
  516. value: item.id,
  517. growCrops: item.growCrops,
  518. managerName: item.managerName,
  519. size: item.size,
  520. farmId: item.farmId
  521. })
  522. }
  523. })
  524. columns.value = [fields] // 符合 u-picker 的二维数组格�?
  525. storage.setCurrentUserPlotsList(fields)
  526. console.log("字段加载完成:", columns.value)
  527. // 字段加载完成后立即设置默认索�?
  528. setDefaultSelectedPlot()
  529. })
  530. .catch(err => {
  531. console.error("加载字段失败", err)
  532. })
  533. }
  534. // 初始化地块列�?
  535. const initPlotColumns = () => {
  536. const cached = storage.getCurrentUserPlotsList()
  537. if (Array.isArray(cached) && cached.length > 0) {
  538. columns.value = [cached] // 结构上是二维数组
  539. console.log("从缓存加载字�?", columns.value)
  540. } else {
  541. loadPhots(userData.userid) // 异步方法内部要处理赋�?columns
  542. }
  543. }
  544. // 设置默认选中�?
  545. const setDefaultSelectedPlot = () => {
  546. try {
  547. const currentPlots = JSON.parse(storage.getPlots() || '{}')
  548. console.log("设置默认地块", currentPlots)
  549. if (currentPlots?.name) {
  550. userData.selectedPlot = currentPlots.name
  551. // 防止 columns 未定义或格式不正�?
  552. if (Array.isArray(columns.value) && columns.value.length > 0 && Array.isArray(columns.value[0])) {
  553. const plots = columns.value[0]
  554. const index = plots.findIndex(item => item.text === currentPlots.name)
  555. if (index !== -1) {
  556. defaultIndex.value = [index]
  557. console.log("设置默认选中地块索引:", index, "defaultIndex:", defaultIndex.value)
  558. } else {
  559. console.warn("未找到匹配的地块:", currentPlots.name)
  560. defaultIndex.value = [0] // 默认选中第一�?
  561. }
  562. } else {
  563. console.warn("columns数据格式不正�?", columns.value)
  564. defaultIndex.value = [0] // 默认选中第一�?
  565. }
  566. } else {
  567. console.warn("未设置默认地块或地块名为空");
  568. this.defaultIndex = [0]; // 默认选中第一项
  569. }
  570. } catch (error) {
  571. console.error("设置默认地块时出�?", error)
  572. defaultIndex.value = [0] // 出错时默认选中第一�?
  573. }
  574. }
  575. const onConfirm = (e) => {
  576. console.log('选择了:', e)
  577. userData.selectedPlot = e.value[0].text
  578. let obj = {
  579. id: e.value[0].value,
  580. name: e.value[0].text,
  581. growCrops: e.value[0].growCrops,
  582. managerName: e.value[0].managerName,
  583. size: e.value[0].size,
  584. farmId: e.value[0].farmId
  585. }
  586. storage.setPlots(JSON.stringify(obj))
  587. show.value = false
  588. }
  589. // 处理取消
  590. const handleCancel = () => {
  591. console.log('取消选择')
  592. show.value = false
  593. }
  594. // 处理切换地块
  595. const handleSwitchPlot = () => {
  596. // 在显示picker前重新设置defaultIndex
  597. setDefaultSelectedPlot()
  598. show.value = true
  599. }
  600. // 导航到个人资�?
  601. const navigateToProfile = () => {
  602. uni.navigateTo({
  603. url: '/pages/user/index'
  604. })
  605. }
  606. // 导航到活动详�?
  607. const navigateToActivity = (activity) => {
  608. uni.navigateTo({
  609. url: `/pages/activity/detail?id=${activity.id}`
  610. })
  611. }
  612. // 导航到警报详�?
  613. const navigateToAlertDetail = (type) => {
  614. // 实现导航到警报详情页的逻辑
  615. console.log(`Navigating to alert detail for type: ${type}`)
  616. }
  617. // 切换周期
  618. const changePeriod = (period) => {
  619. currentPeriod.value = period
  620. }
  621. // uni-app 生命周期 - onShow
  622. onShow(() => {
  623. const userInfo = storage.getUserInfo()
  624. console.log("userInfo", userInfo)
  625. userData.nickname = userInfo.username || '未登录'
  626. userData.avatar = userInfo.avatar || '/static/icons/user_icon.png'
  627. userData.userId = userInfo.userid
  628. if (storage.getHasLogin()) {
  629. isLogin.value = true
  630. // 加载当前登录用户所属所有地块信�?
  631. const cached = storage.getCurrentUserPlotsList()
  632. if (Array.isArray(cached) && cached.length > 0) {
  633. // 如果有缓存,直接用缓存构�?columns
  634. columns.value = [cached]
  635. console.log("从缓存加载字段:", columns.value)
  636. // 有缓存时,在columns设置后立即设置defaultIndex
  637. setDefaultSelectedPlot()
  638. } else {
  639. // 无缓存时请求接口,在接口完成后会调用setDefaultSelectedPlot
  640. loadPhots(userData.userId)
  641. }
  642. console.log("columns", columns.value)
  643. }
  644. })
  645. // onMounted 也保留,用于首次加载
  646. onMounted(() => {
  647. const userInfo = storage.getUserInfo()
  648. console.log("userInfo", userInfo)
  649. userData.nickname = userInfo.username || '未登录'
  650. userData.avatar = userInfo.avatar || '/static/icons/user_icon.png'
  651. userData.userId = userInfo.userid
  652. if (storage.getHasLogin()) {
  653. isLogin.value = true
  654. // 加载当前登录用户所属所有地块信�?
  655. const cached = storage.getCurrentUserPlotsList()
  656. if (Array.isArray(cached) && cached.length > 0) {
  657. // 如果有缓存,直接用缓存构�?columns
  658. columns.value = [cached]
  659. console.log("从缓存加载字�?", columns.value)
  660. // 有缓存时,在columns设置后立即设置defaultIndex
  661. setDefaultSelectedPlot()
  662. } else {
  663. // 无缓存时请求接口,在接口完成后会调用setDefaultSelectedPlot
  664. loadPhots(userData.userId)
  665. }
  666. console.log("columns", columns.value)
  667. }
  668. })
  669. </script>
  670. <style lang="scss" scoped>
  671. .dashboard-container {
  672. padding: 24rpx;
  673. background-color: #F6FDF9;
  674. min-height: 100vh;
  675. }
  676. // 顶部用户信息卡片
  677. .user-info-card {
  678. display: flex;
  679. justify-content: space-between;
  680. align-items: center;
  681. background: white;
  682. border-radius: 20rpx;
  683. padding: 24rpx 28rpx;
  684. margin-bottom: 24rpx;
  685. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04);
  686. .user-info {
  687. flex: 1;
  688. .greeting {
  689. font-size: 32rpx;
  690. font-weight: 600;
  691. color: #2C3E50;
  692. margin-bottom: 8rpx;
  693. }
  694. .plot-info {
  695. display: flex;
  696. align-items: center;
  697. .plot-label {
  698. font-size: 24rpx;
  699. color: #8C9396;
  700. }
  701. .plot-name {
  702. font-size: 24rpx;
  703. color: #2C3E50;
  704. margin: 0 8rpx;
  705. }
  706. .switch-plot-btn {
  707. display: flex;
  708. align-items: center;
  709. justify-content: center;
  710. background: linear-gradient(135deg, #3BB44A, #66CC6A);
  711. border-radius: 16rpx;
  712. padding: 4rpx 16rpx;
  713. box-shadow: 0 2rpx 8rpx rgba(59, 180, 74, 0.25);
  714. text {
  715. font-size: 22rpx;
  716. color: white;
  717. }
  718. }
  719. }
  720. }
  721. .avatar-container {
  722. .avatar {
  723. width: 80rpx;
  724. height: 80rpx;
  725. border-radius: 50%;
  726. background-color: rgba(59, 180, 74, 0.1);
  727. border: 2px solid rgba(255, 255, 255, 0.8);
  728. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
  729. overflow: hidden;
  730. image {
  731. width: 100%;
  732. height: 100%;
  733. object-fit: cover;
  734. }
  735. }
  736. }
  737. }
  738. // Top Stats Overview
  739. .stats-overview {
  740. display: grid;
  741. grid-template-columns: repeat(2, 1fr);
  742. gap: 16rpx;
  743. margin-bottom: 24rpx;
  744. .alert-card {
  745. background: white;
  746. border-radius: 16rpx;
  747. padding: 20rpx;
  748. display: flex;
  749. flex-direction: column;
  750. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
  751. position: relative;
  752. overflow: hidden;
  753. transition: transform 0.2s, box-shadow 0.2s;
  754. &:active {
  755. transform: translateY(2rpx);
  756. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
  757. }
  758. &::after {
  759. content: "";
  760. position: absolute;
  761. right: 12rpx;
  762. bottom: 12rpx;
  763. width: 16rpx;
  764. height: 16rpx;
  765. border-top: 2rpx solid #E0E0E0;
  766. border-right: 2rpx solid #E0E0E0;
  767. transform: rotate(45deg);
  768. opacity: 0.5;
  769. }
  770. .alert-header {
  771. display: flex;
  772. align-items: center;
  773. margin-bottom: 12rpx;
  774. .alert-icon-container {
  775. margin-right: 12rpx;
  776. width: 40rpx;
  777. height: 40rpx;
  778. display: flex;
  779. align-items: center;
  780. justify-content: center;
  781. .custom-icon {
  782. width: 40rpx;
  783. height: 40rpx;
  784. object-fit: contain;
  785. }
  786. }
  787. .alert-title {
  788. font-size: 26rpx;
  789. color: #333333;
  790. font-weight: 600;
  791. }
  792. }
  793. .alert-value {
  794. font-size: 30rpx;
  795. font-weight: 700;
  796. color: #3BB44A;
  797. margin-bottom: 6rpx;
  798. line-height: 1.2;
  799. padding-left: 52rpx;
  800. }
  801. .alert-description {
  802. font-size: 22rpx;
  803. color: #757575;
  804. padding-left: 52rpx;
  805. }
  806. }
  807. }
  808. // Card Base Styles
  809. .card {
  810. background: white;
  811. border-radius: 20rpx;
  812. box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.04), 0 1rpx 4rpx rgba(0, 0, 0, 0.02);
  813. margin-bottom: 24rpx;
  814. overflow: hidden;
  815. .card-header {
  816. display: flex;
  817. justify-content: space-between;
  818. align-items: center;
  819. padding: 24rpx 28rpx;
  820. border-bottom: 1px solid rgba(0, 0, 0, 0.03);
  821. .title-section {
  822. display: flex;
  823. align-items: center;
  824. .title-line {
  825. width: 4rpx;
  826. height: 28rpx;
  827. border-radius: 0;
  828. background: linear-gradient(180deg, #3BB44A, #66CC6A);
  829. margin-right: 16rpx;
  830. }
  831. .card-title {
  832. font-size: 28rpx;
  833. font-weight: 600;
  834. color: #2C3E50;
  835. }
  836. }
  837. .period-selector {
  838. display: flex;
  839. background: rgba(0, 0, 0, 0.03);
  840. border-radius: 20rpx;
  841. padding: 2rpx;
  842. .period {
  843. font-size: 22rpx;
  844. color: #8C9396;
  845. padding: 8rpx 16rpx;
  846. border-radius: 16rpx;
  847. &.active {
  848. background: white;
  849. color: #3BB44A;
  850. font-weight: 500;
  851. box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.05);
  852. }
  853. }
  854. }
  855. .action-button {
  856. display: flex;
  857. align-items: center;
  858. background: linear-gradient(135deg, #3BB44A, #66CC6A);
  859. border-radius: 24rpx;
  860. padding: 8rpx 16rpx;
  861. box-shadow: 0 2rpx 8rpx rgba(59, 180, 74, 0.25);
  862. text {
  863. font-size: 22rpx;
  864. color: white;
  865. margin-right: 6rpx;
  866. }
  867. }
  868. }
  869. }
  870. // Farm Performance Card
  871. .farm-performance {
  872. .performance-stats {
  873. display: flex;
  874. padding: 24rpx 28rpx;
  875. .metric-column {
  876. flex: 1;
  877. display: flex;
  878. flex-direction: column;
  879. .metric-value {
  880. font-size: 48rpx;
  881. font-weight: 700;
  882. color: #2C3E50;
  883. margin-bottom: 6rpx;
  884. line-height: 1.1;
  885. .metric-unit {
  886. font-size: 24rpx;
  887. font-weight: 500;
  888. color: #8C9396;
  889. margin-left: 4rpx;
  890. }
  891. }
  892. .metric-label {
  893. font-size: 24rpx;
  894. color: #8C9396;
  895. margin-bottom: 12rpx;
  896. }
  897. .growth {
  898. display: flex;
  899. align-items: center;
  900. font-size: 22rpx;
  901. &.positive {
  902. color: #3BB44A;
  903. }
  904. &.negative {
  905. color: #FF5252;
  906. }
  907. }
  908. }
  909. .metric-divider {
  910. width: 1px;
  911. background: rgba(0, 0, 0, 0.04);
  912. margin: 0 28rpx;
  913. }
  914. }
  915. .chart-container {
  916. padding: 24rpx 28rpx;
  917. .chart-header {
  918. display: flex;
  919. justify-content: space-between;
  920. align-items: center;
  921. margin-bottom: 16rpx;
  922. .chart-title {
  923. font-size: 28rpx;
  924. font-weight: 600;
  925. color: #2C3E50;
  926. }
  927. .chart-legend {
  928. display: flex;
  929. align-items: center;
  930. .legend-item {
  931. display: flex;
  932. align-items: center;
  933. margin-left: 16rpx;
  934. .legend-color {
  935. width: 16rpx;
  936. height: 16rpx;
  937. border-radius: 4rpx;
  938. margin-right: 8rpx;
  939. }
  940. text {
  941. font-size: 22rpx;
  942. color: #8C9396;
  943. }
  944. }
  945. }
  946. }
  947. .chart-body {
  948. display: flex;
  949. height: 240rpx;
  950. position: relative;
  951. margin-top: 12rpx;
  952. .y-axis {
  953. width: 40rpx;
  954. height: 200rpx;
  955. display: flex;
  956. flex-direction: column;
  957. justify-content: space-between;
  958. text-align: right;
  959. padding-right: 16rpx;
  960. margin-bottom: 30rpx;
  961. text {
  962. font-size: 22rpx;
  963. color: #8C9396;
  964. }
  965. }
  966. .bars-container {
  967. flex: 1;
  968. display: flex;
  969. justify-content: space-around;
  970. height: 200rpx;
  971. position: relative;
  972. &::before {
  973. content: "";
  974. position: absolute;
  975. left: 0;
  976. bottom: 0;
  977. width: 100%;
  978. height: 1px;
  979. background-color: #E0E0E0;
  980. }
  981. .month-group {
  982. display: flex;
  983. flex-direction: column;
  984. align-items: center;
  985. width: 14%;
  986. .bar-wrapper {
  987. height: 200rpx;
  988. width: 70%;
  989. display: flex;
  990. justify-content: center;
  991. position: relative;
  992. .bar {
  993. position: absolute;
  994. bottom: 0;
  995. width: 45%;
  996. border-radius: 4rpx 4rpx 0 0;
  997. transition: height 0.3s ease;
  998. }
  999. .last-year {
  1000. left: 0;
  1001. background: #E0E0E0;
  1002. }
  1003. .this-year {
  1004. right: 0;
  1005. background: #3BB44A;
  1006. }
  1007. }
  1008. .month-label {
  1009. font-size: 22rpx;
  1010. color: #8C9396;
  1011. margin-top: 8rpx;
  1012. }
  1013. }
  1014. }
  1015. }
  1016. }
  1017. }
  1018. // Crop Portfolio
  1019. .crop-portfolio {
  1020. .crop-grid {
  1021. padding: 16rpx;
  1022. display: grid;
  1023. grid-template-columns: repeat(2, 1fr);
  1024. gap: 16rpx;
  1025. }
  1026. .crop-item {
  1027. padding: 16rpx;
  1028. background: rgba(0, 0, 0, 0.01);
  1029. border-radius: 16rpx;
  1030. display: flex;
  1031. align-items: center;
  1032. .crop-icon {
  1033. width: 48rpx;
  1034. height: 48rpx;
  1035. border-radius: 12rpx;
  1036. display: flex;
  1037. align-items: center;
  1038. justify-content: center;
  1039. margin-right: 16rpx;
  1040. .crop-icon-text {
  1041. font-size: 24rpx;
  1042. }
  1043. }
  1044. .crop-details {
  1045. flex: 1;
  1046. .crop-info {
  1047. display: flex;
  1048. justify-content: space-between;
  1049. align-items: center;
  1050. margin-bottom: 10rpx;
  1051. .crop-name {
  1052. font-size: 26rpx;
  1053. font-weight: 600;
  1054. color: #2C3E50;
  1055. }
  1056. .crop-area {
  1057. font-size: 22rpx;
  1058. color: #8C9396;
  1059. }
  1060. }
  1061. .crop-stats {
  1062. display: flex;
  1063. align-items: center;
  1064. .crop-progress-container {
  1065. flex: 1;
  1066. height: 8rpx;
  1067. background: rgba(0, 0, 0, 0.03);
  1068. border-radius: 4rpx;
  1069. margin-right: 12rpx;
  1070. overflow: hidden;
  1071. .crop-progress {
  1072. height: 100%;
  1073. border-radius: 4rpx;
  1074. }
  1075. }
  1076. .crop-progress-text {
  1077. font-size: 22rpx;
  1078. color: #8C9396;
  1079. min-width: 36rpx;
  1080. text-align: right;
  1081. }
  1082. }
  1083. }
  1084. }
  1085. }
  1086. // Resource Efficiency
  1087. .resource-efficiency {
  1088. .resource-grid {
  1089. padding: 16rpx;
  1090. display: grid;
  1091. grid-template-columns: repeat(2, 1fr);
  1092. gap: 16rpx;
  1093. }
  1094. .resource-item {
  1095. padding: 16rpx;
  1096. background: rgba(0, 0, 0, 0.01);
  1097. border-radius: 16rpx;
  1098. .resource-header {
  1099. display: flex;
  1100. align-items: center;
  1101. margin-bottom: 12rpx;
  1102. .icon-tile {
  1103. width: 28rpx;
  1104. height: 28rpx;
  1105. border-radius: 6rpx;
  1106. display: flex;
  1107. align-items: center;
  1108. justify-content: center;
  1109. margin-right: 10rpx;
  1110. &.small {
  1111. width: 28rpx;
  1112. height: 28rpx;
  1113. }
  1114. }
  1115. .resource-name {
  1116. font-size: 24rpx;
  1117. color: #8C9396;
  1118. }
  1119. }
  1120. .resource-value {
  1121. margin-bottom: 12rpx;
  1122. .value {
  1123. font-size: 36rpx;
  1124. font-weight: 700;
  1125. color: #2C3E50;
  1126. }
  1127. .unit {
  1128. font-size: 22rpx;
  1129. color: #8C9396;
  1130. margin-left: 4rpx;
  1131. }
  1132. }
  1133. .resource-progress-container {
  1134. .resource-progress-bg {
  1135. height: 8rpx;
  1136. background: rgba(0, 0, 0, 0.03);
  1137. border-radius: 4rpx;
  1138. margin-bottom: 8rpx;
  1139. overflow: hidden;
  1140. .resource-progress {
  1141. height: 100%;
  1142. border-radius: 4rpx;
  1143. }
  1144. }
  1145. .resource-efficiency {
  1146. font-size: 22rpx;
  1147. color: #8C9396;
  1148. }
  1149. }
  1150. }
  1151. }
  1152. // Farm Activities
  1153. .farm-activities {
  1154. .activities-list {
  1155. padding: 8rpx 0;
  1156. }
  1157. .activity-item {
  1158. display: flex;
  1159. align-items: center;
  1160. padding: 20rpx 28rpx;
  1161. border-bottom: 1px solid rgba(0, 0, 0, 0.02);
  1162. &:last-child {
  1163. border-bottom: none;
  1164. }
  1165. .activity-dot {
  1166. width: 10rpx;
  1167. height: 10rpx;
  1168. border-radius: 50%;
  1169. margin-right: 16rpx;
  1170. flex-shrink: 0;
  1171. &.completed {
  1172. background: #4CAF50;
  1173. }
  1174. &.pending {
  1175. background: #FFC107;
  1176. }
  1177. &.failed {
  1178. background: #FF5252;
  1179. }
  1180. }
  1181. .activity-details {
  1182. flex: 1;
  1183. .activity-title-row {
  1184. display: flex;
  1185. justify-content: space-between;
  1186. align-items: center;
  1187. margin-bottom: 6rpx;
  1188. .activity-title {
  1189. font-size: 26rpx;
  1190. font-weight: 500;
  1191. color: #2C3E50;
  1192. }
  1193. .activity-date {
  1194. font-size: 22rpx;
  1195. color: #8C9396;
  1196. }
  1197. }
  1198. .activity-meta-row {
  1199. display: flex;
  1200. justify-content: space-between;
  1201. align-items: center;
  1202. .activity-executor {
  1203. font-size: 22rpx;
  1204. color: #8C9396;
  1205. }
  1206. .activity-action {
  1207. display: flex;
  1208. align-items: center;
  1209. .action-text {
  1210. font-size: 22rpx;
  1211. color: #3BB44A;
  1212. margin-right: 6rpx;
  1213. }
  1214. }
  1215. }
  1216. }
  1217. }
  1218. }
  1219. // Weather Card
  1220. .weather-card {
  1221. .weather-content {
  1222. padding: 20rpx 28rpx;
  1223. }
  1224. .current-weather {
  1225. display: flex;
  1226. align-items: center;
  1227. margin-bottom: 24rpx;
  1228. .weather-icon-container {
  1229. margin-right: 20rpx;
  1230. .weather-icon {
  1231. font-size: 60rpx;
  1232. line-height: 1;
  1233. }
  1234. }
  1235. .weather-details {
  1236. .weather-temp {
  1237. font-size: 48rpx;
  1238. font-weight: 700;
  1239. color: #2C3E50;
  1240. line-height: 1.1;
  1241. }
  1242. .weather-desc {
  1243. font-size: 26rpx;
  1244. color: #8C9396;
  1245. }
  1246. }
  1247. }
  1248. .weather-metrics {
  1249. display: flex;
  1250. justify-content: space-between;
  1251. align-items: center;
  1252. padding: 16rpx 24rpx;
  1253. margin-bottom: 24rpx;
  1254. border-radius: 12rpx;
  1255. background-color: rgba(247, 247, 247, 0.5);
  1256. border: 1px solid rgba(0, 0, 0, 0.03);
  1257. .weather-metric {
  1258. flex: 1;
  1259. display: flex;
  1260. flex-direction: column;
  1261. align-items: center;
  1262. .metric-label {
  1263. font-size: 22rpx;
  1264. color: #8C9396;
  1265. margin-bottom: 6rpx;
  1266. }
  1267. .metric-value {
  1268. font-size: 28rpx;
  1269. font-weight: 600;
  1270. color: #2C3E50;
  1271. }
  1272. }
  1273. .vertical-divider {
  1274. width: 1px;
  1275. height: 36rpx;
  1276. background-color: rgba(0, 0, 0, 0.05);
  1277. }
  1278. }
  1279. .weather-advice {
  1280. background: rgba(59, 180, 74, 0.05);
  1281. border-radius: 16rpx;
  1282. padding: 16rpx;
  1283. .advice-header {
  1284. display: flex;
  1285. align-items: center;
  1286. margin-bottom: 10rpx;
  1287. .icon-tile {
  1288. width: 24rpx;
  1289. height: 24rpx;
  1290. border-radius: 50%;
  1291. background: linear-gradient(135deg, #3BB44A, #66CC6A);
  1292. display: flex;
  1293. align-items: center;
  1294. justify-content: center;
  1295. margin-right: 10rpx;
  1296. box-shadow: 0 2rpx 8rpx rgba(59, 180, 74, 0.25);
  1297. &.small {
  1298. width: 24rpx;
  1299. height: 24rpx;
  1300. }
  1301. }
  1302. .advice-title {
  1303. font-size: 24rpx;
  1304. font-weight: 600;
  1305. color: #2C3E50;
  1306. }
  1307. }
  1308. .advice-content {
  1309. font-size: 24rpx;
  1310. color: #2C3E50;
  1311. line-height: 1.4;
  1312. }
  1313. }
  1314. }
  1315. // For small screens - mobile responsiveness
  1316. @media screen and (max-width: 768px) {
  1317. .stats-overview {
  1318. grid-template-columns: repeat(2, 1fr);
  1319. }
  1320. .crop-grid {
  1321. grid-template-columns: 1fr;
  1322. }
  1323. }
  1324. // 监控设备概览
  1325. .device-overview {
  1326. .device-metrics-grid {
  1327. display: grid;
  1328. grid-template-columns: repeat(2, 1fr);
  1329. gap: 16rpx;
  1330. padding: 16rpx;
  1331. }
  1332. .device-metric-card {
  1333. background: white;
  1334. border-radius: 16rpx;
  1335. padding: 20rpx;
  1336. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
  1337. border: 1px solid rgba(0, 0, 0, 0.02);
  1338. .device-metric-header {
  1339. display: flex;
  1340. align-items: center;
  1341. margin-bottom: 16rpx;
  1342. .icon-tile {
  1343. width: 24rpx;
  1344. height: 24rpx;
  1345. border-radius: 50%;
  1346. display: flex;
  1347. align-items: center;
  1348. justify-content: center;
  1349. margin-right: 10rpx;
  1350. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
  1351. }
  1352. .metric-name {
  1353. font-size: 24rpx;
  1354. color: #8C9396;
  1355. }
  1356. }
  1357. .device-metric-value {
  1358. font-size: 44rpx;
  1359. font-weight: 700;
  1360. color: #2C3E50;
  1361. margin-bottom: 12rpx;
  1362. }
  1363. .device-metric-trend {
  1364. display: flex;
  1365. align-items: center;
  1366. font-size: 22rpx;
  1367. &.up {
  1368. color: #3BB44A;
  1369. }
  1370. &.down {
  1371. color: #FF5252;
  1372. }
  1373. text {
  1374. margin-left: 4rpx;
  1375. }
  1376. }
  1377. }
  1378. }
  1379. // 农业机械活动
  1380. .machinery-activity {
  1381. .machinery-metrics-grid {
  1382. display: grid;
  1383. grid-template-columns: repeat(2, 1fr);
  1384. gap: 16rpx;
  1385. padding: 16rpx;
  1386. }
  1387. .machinery-metric-card {
  1388. background: white;
  1389. border-radius: 16rpx;
  1390. padding: 20rpx;
  1391. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
  1392. border: 1px solid rgba(0, 0, 0, 0.02);
  1393. .machinery-metric-header {
  1394. display: flex;
  1395. align-items: center;
  1396. margin-bottom: 16rpx;
  1397. .icon-tile {
  1398. width: 24rpx;
  1399. height: 24rpx;
  1400. border-radius: 50%;
  1401. display: flex;
  1402. align-items: center;
  1403. justify-content: center;
  1404. margin-right: 10rpx;
  1405. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
  1406. }
  1407. .metric-name {
  1408. font-size: 24rpx;
  1409. color: #8C9396;
  1410. }
  1411. }
  1412. .machinery-metric-value {
  1413. font-size: 44rpx;
  1414. font-weight: 700;
  1415. color: #2C3E50;
  1416. margin-bottom: 12rpx;
  1417. }
  1418. .machinery-metric-trend {
  1419. display: flex;
  1420. align-items: center;
  1421. font-size: 22rpx;
  1422. &.up {
  1423. color: #3BB44A;
  1424. }
  1425. &.down {
  1426. color: #FF5252;
  1427. }
  1428. text {
  1429. margin-left: 4rpx;
  1430. }
  1431. }
  1432. .machinery-metric-unit {
  1433. font-size: 22rpx;
  1434. color: #8C9396;
  1435. }
  1436. }
  1437. }
  1438. // 添加测试样式
  1439. .test-block {
  1440. background-color: #4CAF50;
  1441. padding: 30rpx;
  1442. margin: 20rpx;
  1443. border-radius: 12rpx;
  1444. display: flex;
  1445. justify-content: center;
  1446. align-items: center;
  1447. }
  1448. .test-text {
  1449. color: white;
  1450. font-size: 36rpx;
  1451. font-weight: bold;
  1452. }
  1453. </style>