index.vue 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658
  1. <template>
  2. <!-- 设备列表 -->
  3. <div class="device-management">
  4. <!-- 头部 -->
  5. <s-header
  6. :name="sys?.title || $t('device.managementCenter')"
  7. :noback="true"
  8. :isFixed="false"
  9. class="management-header"
  10. />
  11. <!-- 主体内容 -->
  12. <div
  13. class="device-list-container"
  14. ref="scrollContainer"
  15. @scroll="handleScroll"
  16. >
  17. <van-list
  18. v-model:loading="loading"
  19. v-model:error="error"
  20. :error-text="$t('public.requestFailed')"
  21. :finished-text="$t('public.noMore')"
  22. offset="100"
  23. :immediate-check="false"
  24. :finished="finished"
  25. @load="onLoad"
  26. >
  27. <!-- 数据概览 -->
  28. <div class="data-overview">
  29. <div class="overview-header">
  30. <h3 class="section-title">
  31. <van-icon name="setting" color="#2c87c8" />
  32. {{ $t("device.dataOverview") }}
  33. </h3>
  34. <div class="action-icons">
  35. <van-popover
  36. v-model:show="showPopover"
  37. placement="left-start"
  38. theme="dark"
  39. :actions="actions"
  40. @select="selectLabel"
  41. >
  42. <template #reference>
  43. <van-icon name="bars" class="icon-filter" />
  44. </template>
  45. </van-popover>
  46. <van-icon
  47. name="search"
  48. class="icon-search"
  49. @click="searchClick"
  50. />
  51. </div>
  52. </div>
  53. <!-- 统计卡片 -->
  54. <div class="stats-cards">
  55. <div class="stat-card">
  56. <div class="card-value">{{ equipStatus.machineUseNum }}</div>
  57. <div class="card-label">{{ $t("device.totalNumberOfRuns") }}</div>
  58. </div>
  59. <div class="stat-card">
  60. <div class="card-value">{{ equipStatus.machineTotalNum }}</div>
  61. <div class="card-label">
  62. {{ $t("device.totalNumberOfEquipment") }}
  63. </div>
  64. </div>
  65. </div>
  66. </div>
  67. <!-- 设备列表 -->
  68. <div class="device-list-section">
  69. <van-tabs
  70. v-model:active="active"
  71. @click-tab="clickLabel"
  72. animated
  73. class="device-tabs"
  74. color="#2c87c8"
  75. >
  76. <van-tab :title="$t('device.whole')" name="" />
  77. <van-tab :title="$t('device.powerOn')" name="ON" />
  78. <van-tab :title="$t('device.abnormal')" name="ABNORMAL" />
  79. <van-tab
  80. v-for="item in labelList"
  81. :key="item.id"
  82. :title="item.name"
  83. :name="item.id"
  84. />
  85. </van-tabs>
  86. <!-- 设备项 -->
  87. <div class="device-items">
  88. <div v-for="item in list" :key="item.id" class="device-item">
  89. <!-- 设备状态标识 -->
  90. <div
  91. class="status-indicator"
  92. :class="{
  93. active: item.eqeStatus === 1,
  94. alert: item.hasTodayAlarm,
  95. }"
  96. ></div>
  97. <!-- 设备主体信息 -->
  98. <div class="device-main">
  99. <!-- 基础信息 -->
  100. <div class="device-info">
  101. <div class="name-row">
  102. <h4 class="device-name">
  103. {{ item.name || item.clientId.slice(-6) }}
  104. </h4>
  105. <div
  106. class="alarm-indicator"
  107. v-if="item.hasTodayAlarm"
  108. :class="{ blink: showAlert }"
  109. />
  110. <div
  111. class="status-dot"
  112. v-else
  113. :class="{ active: item.eqeStatus === 1 }"
  114. />
  115. </div>
  116. <div class="device-id">
  117. {{ $t("device.machineUniqueCode") }}:{{ item.clientId }}
  118. </div>
  119. <!-- 状态信息 -->
  120. <div class="status-info">
  121. <span v-if="user.type < 1" class="lock-status">
  122. {{ $t("device.lockCondition") }}:{{
  123. item.isBlocked
  124. ? $t("device.lockState")
  125. : $t("device.unLockState")
  126. }}
  127. </span>
  128. <template
  129. v-if="item.machineType === '0' || !item.machineType"
  130. >
  131. <span
  132. class="temperature"
  133. :class="{
  134. warning: item.furnaceTm <= 100 && item.eqeStatus == 1,
  135. }"
  136. >
  137. {{ $t("device.furnaceHeadTemperature") }}:{{
  138. item.furnaceTm || 0
  139. }}{{ $t("device.degree") }}
  140. </span>
  141. <span class="temperature"
  142. >{{ $t("device.temperatureInCabinet") }}:{{
  143. item.cabinetTm || 0
  144. }}{{ $t("device.degree") }}</span
  145. >
  146. <span class="humidity"
  147. >{{ $t("device.humidityInCabinet") }}:{{
  148. item.cabinetHd || 0
  149. }}{{ $t("device.humidity") }}</span
  150. >
  151. </template>
  152. <template v-if="item.machineType === '1'">
  153. <span class="temperature">
  154. {{
  155. $t("device.cornGeneratorTemperature") +
  156. ":" +
  157. (item.cabinetTm ? item.cabinetTm : "0") +
  158. $t("device.degree")
  159. }}
  160. </span>
  161. <span
  162. v-if="item.equimentType === 'P30'"
  163. class="temperature"
  164. >
  165. {{
  166. $t("device.stirringTemperature") +
  167. ":" +
  168. (item.cabinetHd ? item.cabinetHd : "0") +
  169. $t("device.degree")
  170. }}</span
  171. >
  172. <span
  173. v-if="item.furnaceTm && item.furnaceTm != '-1'"
  174. class="humidity"
  175. >
  176. {{
  177. $t("device.cupQuantity") + ":" + item.furnaceTm
  178. }}</span
  179. >
  180. <span v-if="item.equimentType === 'P30'" class="humidity">
  181. {{
  182. $t("device.bucketWeight") +
  183. ":" +
  184. item.furnaceSp +
  185. $t("device.weight")
  186. }}</span
  187. >
  188. </template>
  189. </div>
  190. </div>
  191. <!-- 扩展内容 -->
  192. <div v-if="item.checkType" class="device-detail">
  193. <!-- 设备详细信息 -->
  194. <div class="detail-section" v-if="user.type < 2">
  195. <label
  196. >{{ $t("device.affiliatedMerchants") }}:{{
  197. item.adminUserName
  198. }}</label
  199. >
  200. </div>
  201. <!-- 睡眠控制 -->
  202. <div class="detail-section machine-control">
  203. <div class="control-container">
  204. <!-- 文本说明 -->
  205. <span class="status-text">
  206. {{ $t("device.sleepState") }}:{{
  207. item.isSleep
  208. ? $t("device.sleeping")
  209. : $t("device.notSleeping")
  210. }}
  211. </span>
  212. <!-- 切换按钮 -->
  213. <van-switch
  214. :model-value="item.isSleep"
  215. size="18px"
  216. active-color="#2c87c8"
  217. class="switch-button"
  218. @click="changeSleep(item)"
  219. />
  220. </div>
  221. </div>
  222. <!-- 睡眠控制下方添加 -->
  223. <div
  224. v-if="item.isSleep"
  225. class="detail-section sleep-description"
  226. >
  227. <div class="desc-view" v-if="!sleepDescBoxShow">
  228. <label class="desc-label"
  229. >{{ $t("device.sleepDesc") }}:</label
  230. >
  231. <span class="desc-text">{{
  232. item.sleepDesc || $t("device.SuspendBusiness")
  233. }}</span>
  234. <van-button
  235. size="small"
  236. color="#2c87c8"
  237. class="edit-btn"
  238. @click="editSleepDesc(item)"
  239. >
  240. {{ $t("device.modify") }}
  241. </van-button>
  242. </div>
  243. <div class="desc-edit" v-else>
  244. <van-field
  245. v-model="item.sleepDesc"
  246. :placeholder="$t('device.sleepDescPlace')"
  247. class="edit-field"
  248. label-width="4em"
  249. clearable
  250. >
  251. <template #button>
  252. <van-button
  253. size="small"
  254. type="primary"
  255. class="confirm-btn"
  256. @click="sleepDescChg(item.sleepDesc, item.id)"
  257. >
  258. {{ $t("device.confirm") }}
  259. </van-button>
  260. <van-button
  261. size="small"
  262. class="cancel-btn"
  263. @click="sleepDescBoxShow = false"
  264. >
  265. {{ $t("device.cancel") }}
  266. </van-button>
  267. </template>
  268. </van-field>
  269. </div>
  270. </div>
  271. <!-- 炉头状态 -->
  272. <div
  273. class="detail-section machine-control"
  274. v-if="item.machineType === '0' || !item.machineType"
  275. >
  276. <div class="control-container">
  277. <span class="status-text">
  278. {{
  279. item.machineType == "0" || item.machineType == null
  280. ? $t("device.furnHeadStatus")
  281. : $t("device.deviceStatus")
  282. }}:
  283. {{
  284. item.eqeStatus === 1
  285. ? $t("device.opened")
  286. : $t("device.closed")
  287. }}
  288. </span>
  289. <!-- 操作按钮组 -->
  290. <div class="button-group">
  291. <van-button
  292. size="small"
  293. color="#2c87c8"
  294. class="action-btn"
  295. @click="openCloseHead(item.id, 1)"
  296. >
  297. {{ $t("device.open") }}
  298. </van-button>
  299. <van-button
  300. size="small"
  301. type="danger"
  302. class="action-btn"
  303. @click="openCloseHead(item.id, 0)"
  304. >
  305. {{ $t("device.close") }}
  306. </van-button>
  307. <van-button
  308. size="small"
  309. type="warning"
  310. class="action-btn"
  311. @click="restartHead(item.id)"
  312. >
  313. {{ $t("device.restartHead") }}
  314. </van-button>
  315. </div>
  316. </div>
  317. </div>
  318. <!-- 定位: -->
  319. <div class="detail-section" v-if="item.latitude">
  320. <label @click="viewPosiClk(item)"
  321. >{{ $t("device.position") }}:{{ item.fullName }}</label
  322. >
  323. </div>
  324. <!-- 物料监控 -->
  325. <template v-if="item.isMaterialUse === '1'">
  326. <div
  327. class="detail-section material-usage"
  328. v-if="item.machineType === '0' || !item.machineType"
  329. >
  330. <!-- 糖类原料 -->
  331. <div class="material-group">
  332. <div class="material-grid">
  333. <div
  334. v-for="(sugar, index) in sugarTypes"
  335. :key="index"
  336. class="material-item"
  337. >
  338. <van-icon
  339. :name="sugar.icon"
  340. class="material-icon"
  341. size="25px"
  342. :style="{ color: sugar.color }"
  343. />
  344. <div class="material-info">
  345. <span class="material-label">{{
  346. $t(`device.${sugar.type}`)
  347. }}</span>
  348. <span class="material-value"
  349. >{{
  350. Format_calcuDecial(item[sugar.type])
  351. }}
  352. %</span
  353. >
  354. </div>
  355. </div>
  356. </div>
  357. </div>
  358. <!-- 一键补料 -->
  359. <div class="supply-section">
  360. <van-button
  361. block
  362. type="primary"
  363. icon="replay"
  364. class="supply-button"
  365. @click="replenishmentClk(item)"
  366. >
  367. {{ $t("device.oneKeyFeed") }}
  368. </van-button>
  369. </div>
  370. </div>
  371. <!-- 爆米花物料 -->
  372. <div
  373. class="detail-section material-usage"
  374. v-if="item.machineType === '1'"
  375. >
  376. <!-- 爆米花P30物料-->
  377. <div
  378. class="material-group"
  379. v-if="item.equimentType === 'P30'"
  380. >
  381. <div class="material-grid">
  382. <div
  383. v-for="(poporn, index) in popornTypes"
  384. :key="index"
  385. class="material-item"
  386. >
  387. <van-icon
  388. :name="poporn.icon"
  389. class="material-icon"
  390. size="25px"
  391. :style="{ color: poporn.color }"
  392. />
  393. <div class="material-info">
  394. <span class="material-label">{{
  395. $t(`device.${poporn.type}`)
  396. }}</span>
  397. <span class="material-value"
  398. >{{
  399. Format_calcuDecial(item[poporn.code])
  400. }}
  401. g</span
  402. >
  403. </div>
  404. </div>
  405. </div>
  406. </div>
  407. <!-- 爆米花SBM10物料-->
  408. <div
  409. class="material-group"
  410. v-if="item.equimentType === 'SBM10'"
  411. >
  412. <div class="material-grid">
  413. <div
  414. v-for="(poporn, index) in poporn10Types"
  415. :key="index"
  416. class="material-item"
  417. >
  418. <van-icon
  419. :name="poporn.icon"
  420. class="material-icon"
  421. size="25px"
  422. :style="{ color: poporn.color }"
  423. />
  424. <div class="material-info">
  425. <span class="material-label">{{
  426. $t(`device.${poporn.type}`)
  427. }}</span>
  428. <span class="material-value"
  429. >{{
  430. Format_calcuDecial(item[poporn.code])
  431. }}
  432. g</span
  433. >
  434. </div>
  435. </div>
  436. </div>
  437. </div>
  438. </div>
  439. <!-- 冰淇淋物料 -->
  440. <div
  441. class="detail-section material-usage"
  442. v-if="item.machineType === '2'"
  443. >
  444. <div class="material-group">
  445. <div class="material-grid">
  446. <div
  447. v-for="(ice, index) in iceTypes"
  448. :key="index"
  449. class="material-item"
  450. >
  451. <van-icon
  452. :name="ice.icon"
  453. class="material-icon"
  454. size="25px"
  455. :style="{ color: ice.color }"
  456. />
  457. <div class="material-info">
  458. <span class="material-label">{{
  459. $t(`device.${ice.type}`)
  460. }}</span>
  461. <span class="material-value"
  462. >{{
  463. Format_calcuDecial(item[ice.code])
  464. }}
  465. %</span
  466. >
  467. </div>
  468. </div>
  469. </div>
  470. </div>
  471. </div>
  472. </template>
  473. <!-- 最近刷新时间 -->
  474. <div class="detail-section">
  475. <label
  476. >{{ $t("device.lastRefreshTime") }}:{{
  477. showDateTime(item.lastUpdateTime)
  478. }}</label
  479. >
  480. </div>
  481. <!-- 音量 -->
  482. <div class="detail-section">
  483. <label
  484. >{{ $t("device.volume") }}:{{ item.volume || 0 }}</label
  485. >
  486. </div>
  487. <!-- 报警信息区域 -->
  488. <div
  489. class="detail-section alert-section"
  490. v-if="item.alarmList?.length"
  491. >
  492. <!-- 报警列表 -->
  493. <div class="alert-list">
  494. <div
  495. v-for="itemAlarm in item.alarmList"
  496. :key="itemAlarm"
  497. class="alert-item"
  498. >
  499. <div class="alert-content">
  500. <div class="alert-time">
  501. <van-icon name="clock" class="alert-icon" />
  502. {{ showDateTime(itemAlarm.occurrenceTime) }}
  503. </div>
  504. <div class="alert-message">
  505. <van-icon name="warning" class="alert-icon" />
  506. <span class="alert-text">{{
  507. itemAlarm.alarmContent
  508. }}</span>
  509. </div>
  510. </div>
  511. </div>
  512. </div>
  513. <!-- 一键清除 -->
  514. <div class="alert-actions">
  515. <van-button
  516. block
  517. type="primary"
  518. color="#07c160"
  519. icon="delete"
  520. @click="clearAllAlarm(item.alarmList, item)"
  521. >
  522. {{ $t("device.oneClickClear") }}
  523. </van-button>
  524. </div>
  525. </div>
  526. <!-- 操作按钮 -->
  527. <div class="action-buttons">
  528. <van-button
  529. color="#2c87c8"
  530. icon="bars"
  531. size="small"
  532. @click="deviceSet(item)"
  533. >{{ $t("device.editDevice") }}</van-button
  534. >
  535. <van-button
  536. color="#2c87c8"
  537. icon="setting"
  538. v-if="controlList.length"
  539. size="small"
  540. @click="deviceOprShow(item, controlList)"
  541. >
  542. {{ $t("device.commonOperations") }}
  543. </van-button>
  544. </div>
  545. </div>
  546. <!-- 展开/收起 -->
  547. <div
  548. class="toggle-detail"
  549. @click="item.checkType = !item.checkType"
  550. >
  551. <span>{{
  552. item.checkType ? $t("device.stow") : $t("device.seeMore")
  553. }}</span>
  554. <van-icon
  555. :name="item.checkType ? 'arrow-up' : 'arrow-down'"
  556. />
  557. </div>
  558. </div>
  559. </div>
  560. </div>
  561. </div>
  562. <van-back-top
  563. right="20"
  564. bottom="80"
  565. style="background-color: #2c87c8"
  566. />
  567. </van-list>
  568. </div>
  569. <!-- 其他组件 -->
  570. <device-oper ref="oprRef" @operfinish="operFinish" />
  571. <device-search ref="searchRef" @search="search" />
  572. </div>
  573. </template>
  574. <script>
  575. import { Api_postMachineNum } from "../../service/home";
  576. import {
  577. onMounted,
  578. reactive,
  579. toRefs,
  580. ref,
  581. onActivated,
  582. onBeforeUnmount,
  583. } from "vue";
  584. import {
  585. showFailToast,
  586. showSuccessToast,
  587. showToast,
  588. showConfirmDialog,
  589. } from "vant";
  590. import sHeader from "../../components/SimpleHeader";
  591. import { getLoginUser, Format_calcuDecial } from "../../common/js/utils";
  592. import {
  593. eliminate,
  594. Api_getReplenishment,
  595. changeSleepDesc,
  596. setFurnace,
  597. sleepEquipment,
  598. getMachineList,
  599. } from "../../service/device/index";
  600. import deviceSearch from "./deviceSearch";
  601. import deviceOper from "./deviceOper";
  602. import { onBeforeRouteLeave, useRouter } from "vue-router";
  603. import dateUtil from "../../utils/dateUtil";
  604. import { useI18n } from "vue-i18n";
  605. import { Api_getLabelList } from "../../service/labelMan";
  606. import { getAdminRole } from "@/service/user";
  607. export default {
  608. name: "device",
  609. components: { sHeader, deviceSearch, deviceOper },
  610. setup() {
  611. const { t } = useI18n();
  612. const searchRef = ref(null);
  613. const oprRef = ref(null);
  614. const list = ref([]);
  615. const loading = ref(true);
  616. const error = ref(false);
  617. const finished = ref(false);
  618. const router = useRouter();
  619. const sys = ref(null);
  620. const user = getLoginUser();
  621. const labelList = ref([]);
  622. // 棉花糖物料
  623. const sugarTypes = ref([
  624. { type: "whiteSugar", icon: "stop", color: "#ffffff" },
  625. { type: "redSugar", icon: "stop", color: "#ff4d4f" },
  626. { type: "yellowSugar", icon: "stop", color: "#faad14" },
  627. { type: "blueSugar", icon: "stop", color: "#1890ff" },
  628. { type: "stick", icon: "minus", color: "#9254de" },
  629. { type: "water", icon: "weapp-nav", color: "#ADD8E6" },
  630. { type: "wasteWater", icon: "weapp-nav", color: "#666" },
  631. ]);
  632. // 爆米花P30物料
  633. const popornTypes = ref([
  634. { code: "whiteSugar", type: "chocolate", icon: "stop", color: "#5C3317" },
  635. { code: "redSugar", type: "cheese", icon: "stop", color: "#fcdd8d" },
  636. { code: "yellowSugar", type: "peach", icon: "stop", color: "#FFC0CB" },
  637. { code: "blueSugar", type: "caramel", icon: "stop", color: "#D2691E" },
  638. ]);
  639. // 爆米花P10物料
  640. const poporn10Types = ref([
  641. { code: "redSugar", type: "sweet", icon: "stop", color: "#FFC0CB" },
  642. { code: "whiteSugar", type: "salty", icon: "stop", color: "#fcdd8d" },
  643. ]);
  644. // 冰淇淋物料
  645. const iceTypes = ref([
  646. { code: "whiteSugar", type: "C01", icon: "stop", color: "#5C3317" },
  647. { code: "redSugar", type: "C02", icon: "stop", color: "#faad14" },
  648. { code: "yellowSugar", type: "J01", icon: "stop", color: "#ff4d4f" },
  649. { code: "blueSugar", type: "J02", icon: "stop", color: "#1890ff" },
  650. { code: "stick", type: "J03", icon: "stop", color: "#f8ff3b" },
  651. ]);
  652. // 创建容器引用
  653. const scrollContainer = ref(null);
  654. // 创建响应式滚动位置
  655. const scrollTop = ref(0);
  656. // 返回顶部
  657. const backTop = () => {
  658. document.documentElement.scrollTop = 0;
  659. };
  660. // 滚动处理函数
  661. const handleScroll = () => {
  662. // console.log("scrollContainer", scrollContainer.value);
  663. if (scrollContainer.value) {
  664. scrollTop.value = scrollContainer.value.scrollTop;
  665. // 如果需要,可以在这里触发其他逻辑
  666. // console.log("当前滚动位置:", scrollTop.value);
  667. }
  668. };
  669. onActivated(() => {
  670. scrollContainer.value.scrollTop = scrollTop.value;
  671. });
  672. onBeforeRouteLeave(() => {
  673. // console.log("离开时的位置", scrollTop.value);
  674. });
  675. // 在组件卸载前清除定时器
  676. onBeforeUnmount(() => {
  677. clearInterval(updateDataInterval);
  678. });
  679. const updateDataInterval = () => {
  680. // 每隔5分钟更新数据
  681. setInterval(() => {
  682. init();
  683. if (oprRef.value) {
  684. oprRef.value.closeOper();
  685. }
  686. // verticalScrollPosition.value = 0;
  687. }, 5 * 60 * 1000); // 5分钟的毫秒数
  688. };
  689. //控制睡眠描述的显示隐藏
  690. const sleepDescBoxShow = ref(false);
  691. // 页面列表查询参数
  692. let searchParams = reactive({
  693. id: "", // 用户账户id
  694. // adminName: '', // 用户登录名
  695. current: 1, // 页数
  696. size: 10, // 页大小
  697. todayDate: dateUtil.formateDate(new Date(), "yyyy-MM-dd"), // 当天时间
  698. labelId: "", // 分组标签
  699. });
  700. // 初始化页面获取列表
  701. const showAlert = ref(false);
  702. // 远程操作权限
  703. const controlList = ref([]);
  704. // 获取账号权限
  705. const getAccountPer = async () => {
  706. const { data } = await getAdminRole({ adminId: user.id });
  707. if (data.code === "00000") {
  708. if (data.data.controlCodesJson !== null) {
  709. controlList.value = JSON.parse(data.data.controlCodesJson);
  710. }
  711. }
  712. };
  713. onMounted(() => {
  714. init();
  715. updateDataInterval();
  716. getAccountPer();
  717. // window.addEventListener('scroll', handleScroll);
  718. // 加载样式
  719. // styleUrl('device');
  720. setInterval(() => {
  721. showAlert.value = !showAlert.value;
  722. }, 500); // 1000毫秒即1秒
  723. });
  724. // 初始化
  725. const init = () => {
  726. // 获取设备情况
  727. getMachineNum();
  728. getLabelList();
  729. if (localStorage.getItem("loginSys")) {
  730. const loginSysString = localStorage.getItem("loginSys");
  731. sys.value = JSON.parse(loginSysString);
  732. }
  733. list.value = [];
  734. searchParams.current = 1;
  735. if (user) {
  736. searchParams.id = user.id;
  737. getList();
  738. }
  739. };
  740. // 获取设备标签
  741. const getLabelList = async () => {
  742. Api_getLabelList({
  743. adminId: user.id,
  744. type: "1",
  745. }).then((res) => {
  746. const { data } = res.data;
  747. labelList.value = data;
  748. });
  749. };
  750. // 获取设备列表数据
  751. const getList = async () => {
  752. finished.value = false;
  753. const { data } = await getMachineList(Object.assign({}, searchParams));
  754. if (data.code === "00000") {
  755. if (searchParams.current === 0) {
  756. list.value = [];
  757. }
  758. // 列表值叠加
  759. list.value = list.value.concat(
  760. data.data.records.map((item) => {
  761. if (item.sleepDesc == null) {
  762. item.sleepDesc = t("device.SuspendBusiness");
  763. }
  764. return {
  765. ...item,
  766. checkType: false,
  767. };
  768. })
  769. );
  770. if (list.value.length === data.data.total) {
  771. finished.value = true;
  772. }
  773. loading.value = false;
  774. } else {
  775. showFailToast(data.message);
  776. }
  777. };
  778. // 滚动加载
  779. const onLoad = () => {
  780. if (!finished.value) {
  781. // console.log("滚动加载")
  782. searchParams.current = searchParams.current + 1;
  783. getList();
  784. }
  785. };
  786. // 搜索点击
  787. const searchClick = () => {
  788. searchRef.value.showSearch();
  789. };
  790. // 搜索条件触发查询
  791. const search = (e) => {
  792. list.value = [];
  793. loading.value = true;
  794. searchParams.current = 1;
  795. searchParams = Object.assign(searchParams, e);
  796. getList();
  797. getMachineNum();
  798. };
  799. // 跳转设备编辑
  800. const deviceSet = (e) => {
  801. router.push({ path: "deviceSet", query: { deviceId: e.id } });
  802. };
  803. // 常用操作弹窗展示触发
  804. const deviceOprShow = (e, value) => {
  805. oprRef.value.showOper(e, value);
  806. };
  807. // 消除报警
  808. const clearAlarm = async (e, e1, e2) => {
  809. const params = {
  810. id: e.id,
  811. name: e.name,
  812. selfName: e.selfName,
  813. areaId: e.areaId,
  814. channel: e.channel,
  815. contactName: e.contactName,
  816. contactPhone: e.contactPhone,
  817. flowers: e.flowers,
  818. operationalName: e.operationalName,
  819. operationalPhone: e.operationalPhone,
  820. timeRuleId: e.timeRuleId,
  821. };
  822. const { data } = await eliminate(Object.assign({}, params));
  823. if (data.code) {
  824. showSuccessToast(t("device.successfullyEliminatedTheAlarm"));
  825. setTimeout(() => {
  826. e2 = e2.filter((item) => item.id !== e.id);
  827. list.value[
  828. list.value.findIndex((item) => item.id === e1.id)
  829. ].alarmList = e2;
  830. if (e2.length === 0) {
  831. e1.hasTodayAlarm = false;
  832. }
  833. }, 1000);
  834. } else {
  835. showFailToast(data.message);
  836. }
  837. };
  838. // 一键消除报警
  839. const clearAllAlarm = (e, e1) => {
  840. showConfirmDialog({
  841. title: t("device.openRemind"),
  842. message: t("device.isClear"),
  843. })
  844. .then(async () => {
  845. const { data } = await eliminate(Object.assign({}, { id: e[0].id }));
  846. if (data.code) {
  847. showSuccessToast(t("device.successfullyEliminatedTheAlarm"));
  848. setTimeout(() => {
  849. list.value[
  850. list.value.findIndex((item) => item.id === e1.id)
  851. ].alarmList = null;
  852. e1.hasTodayAlarm = false;
  853. if (e1.machineType == "0" || e1.machineType == null) {
  854. restartHead(e1.id, t("device.clearAfter"));
  855. }
  856. }, 800);
  857. } else {
  858. showFailToast(data.message);
  859. }
  860. })
  861. .catch((error) => {
  862. console.log(error);
  863. });
  864. };
  865. const showDateTime = (date) => {
  866. if (!date) {
  867. return "";
  868. }
  869. const currentDate = new Date(
  870. dateUtil.formateDate(new Date(date), "yyyy/MM/dd hh:mm:ss")
  871. );
  872. return dateUtil.timeZoneDate(currentDate);
  873. };
  874. // 点击查看定位
  875. const viewPosiClk = (row) => {
  876. if (row.latitude) {
  877. router.push({
  878. path: "viewPosition",
  879. query: {
  880. latitude: row.latitude,
  881. longitude: row.longitude,
  882. fullName: row.fullName,
  883. name: row.name ? row.name : row.clientId.slice(-6),
  884. status: row.eqeStatus,
  885. },
  886. });
  887. } else {
  888. showToast(`${t("device.noPosition")}!!!`);
  889. }
  890. };
  891. // 点击补料
  892. const replenishmentClk = (row) => {
  893. showConfirmDialog({
  894. title: t("user.tips"),
  895. message: t("device.isReplenishment"),
  896. })
  897. .then(async () => {
  898. const { data } = await Api_getReplenishment({
  899. equipmentId: row.id,
  900. });
  901. if (data.code) {
  902. showSuccessToast(t("device.sentSuccessfully"));
  903. setTimeout(() => {
  904. // router.go(0);
  905. init();
  906. }, 1500);
  907. } else {
  908. showFailToast(data.message);
  909. }
  910. })
  911. .catch(() => {
  912. return;
  913. });
  914. };
  915. // 操作弹窗完成的回调
  916. const operFinish = () => {
  917. init();
  918. };
  919. // 设备状况
  920. const equipStatus = ref({});
  921. // 获取设备情况
  922. const getMachineNum = () => {
  923. Api_postMachineNum({
  924. adminId: user.id,
  925. companyType: searchParams.companyType,
  926. }).then((res) => {
  927. equipStatus.value = res.data.data || {};
  928. });
  929. };
  930. // 点击运行总数和总设备数
  931. const eqeStatusClk = (val) => {
  932. searchParams.eqeStatus = val;
  933. // 初始化
  934. searchParams.current = 1;
  935. list.value = [];
  936. getList();
  937. };
  938. // 点击修改图标
  939. const editSleepDesc = () => {
  940. sleepDescBoxShow.value = !sleepDescBoxShow.value;
  941. };
  942. // 点击睡眠描述的确定按钮
  943. const sleepDescChg = async (sleepDesc, id) => {
  944. if (!sleepDesc) {
  945. showToast(t("device.sleepDescPlace"));
  946. } else {
  947. const { data } = await changeSleepDesc({
  948. equipmentId: id,
  949. sleepDesc,
  950. });
  951. if (data.code === "00000") {
  952. showToast(t("device.modificationSucceeded"));
  953. setTimeout(() => {
  954. sleepDescBoxShow.value = false;
  955. }, 500);
  956. }
  957. }
  958. };
  959. // 点击标签
  960. const active = ref("");
  961. const clickLabel = (item) => {
  962. console.log(item);
  963. list.value = [];
  964. searchParams.current = 1;
  965. searchParams.labelId = item.name;
  966. getList();
  967. };
  968. // 分组管理
  969. const showPopover = ref(false);
  970. const actions = [
  971. { text: t("device.group"), value: "0" },
  972. { text: t("device.addGroup"), value: "1" },
  973. ];
  974. const selectLabel = (action) => {
  975. // showToast(action.value);
  976. if (action.value == "0") {
  977. router.push("/labelMan");
  978. }
  979. if (action.value == "1") {
  980. router.push("/labelManAdd");
  981. }
  982. };
  983. // 睡眠切换
  984. const changeSleep = (item) => {
  985. showConfirmDialog({
  986. title: t("user.tips"),
  987. message: t("device.changeSleep"),
  988. })
  989. .then(async () => {
  990. const { data } = await sleepEquipment({
  991. id: item.id,
  992. isSleep: item.isSleep ? "0" : "1",
  993. });
  994. if (data.code) {
  995. showSuccessToast(t("device.changeSleepSuccess"));
  996. setTimeout(() => {
  997. init();
  998. }, 1500);
  999. } else {
  1000. showFailToast(data.message);
  1001. }
  1002. })
  1003. .catch(() => {
  1004. return;
  1005. });
  1006. };
  1007. // 重启炉头
  1008. const restartHead = (id, msg) => {
  1009. showConfirmDialog({
  1010. title: t("user.tips"),
  1011. message: msg ? msg : t("device.restartFurnaceHeadTips"),
  1012. })
  1013. .then(async () => {
  1014. const { data } = await setFurnace({
  1015. id: id,
  1016. eqeStatus: 1,
  1017. });
  1018. if (data.code) {
  1019. showSuccessToast(t("device.restartSucceeded"));
  1020. setTimeout(() => {
  1021. init();
  1022. }, 1500);
  1023. } else {
  1024. showFailToast(data.message);
  1025. }
  1026. })
  1027. .catch(() => {
  1028. return;
  1029. });
  1030. };
  1031. // 开启/关闭炉头
  1032. const openCloseHead = (id, status) => {
  1033. showConfirmDialog({
  1034. title: t("user.tips"),
  1035. message:
  1036. status == 1
  1037. ? t("device.openFurnaceHeadTips")
  1038. : t("device.closeFurnaceHeadTips"),
  1039. })
  1040. .then(async () => {
  1041. const { data } = await setFurnace({
  1042. id: id,
  1043. eqeStatus: status,
  1044. });
  1045. if (data.code) {
  1046. showSuccessToast(
  1047. (status == 1 ? t("device.open") : t("device.close")) +
  1048. t("device.success")
  1049. );
  1050. setTimeout(() => {
  1051. init();
  1052. }, 1500);
  1053. } else {
  1054. showFailToast(data.message);
  1055. }
  1056. })
  1057. .catch(() => {
  1058. return;
  1059. });
  1060. };
  1061. return {
  1062. showAlert,
  1063. ...toRefs(searchParams),
  1064. list,
  1065. loading,
  1066. error,
  1067. finished,
  1068. onLoad,
  1069. searchRef,
  1070. searchClick,
  1071. search,
  1072. deviceSet,
  1073. clearAlarm,
  1074. clearAllAlarm,
  1075. oprRef,
  1076. deviceOprShow,
  1077. showDateTime,
  1078. sys,
  1079. viewPosiClk,
  1080. replenishmentClk,
  1081. Format_calcuDecial,
  1082. operFinish,
  1083. equipStatus,
  1084. eqeStatusClk,
  1085. editSleepDesc,
  1086. sleepDescBoxShow,
  1087. sleepDescChg,
  1088. changeSleep,
  1089. backTop,
  1090. user,
  1091. labelList,
  1092. clickLabel,
  1093. active,
  1094. actions,
  1095. showPopover,
  1096. selectLabel,
  1097. restartHead,
  1098. openCloseHead,
  1099. controlList,
  1100. sugarTypes,
  1101. popornTypes,
  1102. iceTypes,
  1103. poporn10Types,
  1104. scrollContainer,
  1105. scrollTop,
  1106. handleScroll,
  1107. };
  1108. },
  1109. };
  1110. </script>
  1111. <style lang="less" scoped>
  1112. .device-management {
  1113. --primary-color: #2c87c8;
  1114. --success-color: #07c160;
  1115. --warning-color: #ff976a;
  1116. --danger-color: #ff463c;
  1117. --text-primary: #404d74;
  1118. --text-secondary: #666;
  1119. --border-color: #eee;
  1120. --card-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  1121. --tabbar-height: 40px;
  1122. --tabbar-padding: calc(var(--tabbar-height) + 20px);
  1123. background: #f8f9fa;
  1124. min-height: 100vh;
  1125. .device-list-container {
  1126. height: calc(100% - 95px);
  1127. overflow: auto;
  1128. overflow-x: hidden;
  1129. }
  1130. .management-header {
  1131. box-shadow: var(--card-shadow);
  1132. }
  1133. .data-overview {
  1134. background: white;
  1135. margin: 10px;
  1136. border-radius: 8px;
  1137. box-shadow: var(--card-shadow);
  1138. .overview-header {
  1139. display: flex;
  1140. justify-content: space-between;
  1141. align-items: center;
  1142. padding: 16px;
  1143. border-bottom: 1px solid var(--border-color);
  1144. .section-title {
  1145. font-size: 15px;
  1146. color: var(--text-primary);
  1147. margin: 0;
  1148. }
  1149. .action-icons {
  1150. .icon-filter,
  1151. .icon-search {
  1152. font-size: 20px;
  1153. color: var(--primary-color);
  1154. margin-left: 12px;
  1155. }
  1156. }
  1157. }
  1158. .stats-cards {
  1159. display: grid;
  1160. grid-template-columns: repeat(2, 1fr);
  1161. gap: 16px;
  1162. padding: 16px;
  1163. .stat-card {
  1164. background: linear-gradient(145deg, #f3f4ff, #ffffff);
  1165. border-radius: 8px;
  1166. padding: 20px;
  1167. text-align: center;
  1168. transition: transform 1s;
  1169. &:active {
  1170. transform: scale(0.98);
  1171. }
  1172. .card-value {
  1173. font-size: 24px;
  1174. font-weight: 600;
  1175. color: var(--primary-color);
  1176. margin-bottom: 8px;
  1177. }
  1178. .card-label {
  1179. font-size: 14px;
  1180. color: var(--text-secondary);
  1181. }
  1182. }
  1183. }
  1184. }
  1185. .device-list-section {
  1186. margin: 10px;
  1187. background: white;
  1188. border-radius: 8px;
  1189. box-shadow: var(--card-shadow);
  1190. :deep(.van-tabs__wrap) {
  1191. border-radius: 8px;
  1192. }
  1193. .device-tabs {
  1194. :deep(.van-tab) {
  1195. font-size: 14px;
  1196. &--active {
  1197. color: var(--primary-color);
  1198. font-weight: 500;
  1199. }
  1200. }
  1201. }
  1202. .device-items {
  1203. .device-item {
  1204. display: flex;
  1205. margin: 12px;
  1206. background: white;
  1207. border-radius: 8px;
  1208. box-shadow: var(--card-shadow);
  1209. .status-indicator {
  1210. width: 4px;
  1211. border-radius: 2px 0 0 2px;
  1212. background: #ddd;
  1213. &.active {
  1214. background: var(--success-color);
  1215. }
  1216. &.alert {
  1217. background: var(--danger-color);
  1218. animation: pulse 1.5s infinite;
  1219. }
  1220. }
  1221. .device-main {
  1222. flex: 1;
  1223. padding: 16px;
  1224. .name-row {
  1225. display: flex;
  1226. align-items: center;
  1227. margin-bottom: 8px;
  1228. .device-name {
  1229. font-size: 16px;
  1230. color: var(--text-primary);
  1231. margin: 0;
  1232. flex: 1;
  1233. }
  1234. .status-dot {
  1235. width: 12px;
  1236. height: 12px;
  1237. border-radius: 50%;
  1238. background: #ddd;
  1239. &.active {
  1240. background: var(--success-color);
  1241. }
  1242. }
  1243. .alarm-indicator {
  1244. width: 12px;
  1245. height: 12px;
  1246. border-radius: 50%;
  1247. background: var(--danger-color);
  1248. &.blink {
  1249. animation: pulse 2s infinite;
  1250. }
  1251. }
  1252. }
  1253. .device-id {
  1254. font-size: 12px;
  1255. color: var(--text-secondary);
  1256. margin-bottom: 12px;
  1257. }
  1258. .status-info {
  1259. > span {
  1260. display: block;
  1261. font-size: 14px;
  1262. margin: 8px 0;
  1263. color: var(--text-primary);
  1264. &.warning {
  1265. color: var(--danger-color);
  1266. font-weight: 500;
  1267. }
  1268. }
  1269. }
  1270. .device-detail {
  1271. border-top: 1px solid var(--border-color);
  1272. margin-top: 12px;
  1273. .detail-section {
  1274. padding: 12px 0;
  1275. border-bottom: 1px solid var(--border-color);
  1276. // 新增睡眠控制样式
  1277. &.machine-control {
  1278. .control-container {
  1279. display: flex;
  1280. flex-wrap: wrap; // 允许换行
  1281. align-items: center;
  1282. gap: 12px; // 控制文本和按钮间距
  1283. // 文本部分
  1284. .status-text {
  1285. color: var(--text-primary);
  1286. white-space: nowrap; // 防止文本换行
  1287. }
  1288. // 切换按钮
  1289. .van-switch {
  1290. flex-shrink: 0; // 防止按钮被压缩
  1291. position: relative;
  1292. top: 1px; // 微调垂直对齐
  1293. }
  1294. // 控制按钮
  1295. .button-group {
  1296. display: flex;
  1297. gap: 8px; // 按钮间距
  1298. flex-shrink: 0; // 防止按钮压缩
  1299. .action-btn {
  1300. // 保持与全局按钮样式一致
  1301. border-radius: 4px;
  1302. padding: 0 10px;
  1303. max-width: 120px;
  1304. overflow: hidden;
  1305. text-overflow: ellipsis;
  1306. white-space: nowrap;
  1307. height: 28px;
  1308. line-height: 28px;
  1309. // 移动端适配
  1310. @media (max-width: 480px) {
  1311. max-width: 80px;
  1312. padding: 0 8px;
  1313. }
  1314. }
  1315. }
  1316. }
  1317. }
  1318. // 睡眠描述样式(保持与报警模块一致)
  1319. &.sleep-description {
  1320. // 查看状态
  1321. .desc-view {
  1322. display: flex;
  1323. align-items: center;
  1324. gap: 2px;
  1325. .desc-label {
  1326. color: var(--text-primary);
  1327. flex-shrink: 0;
  1328. }
  1329. .desc-text {
  1330. color: var(--text-primary);
  1331. word-break: break-word;
  1332. padding-right: 10px;
  1333. }
  1334. .edit-btn {
  1335. flex-shrink: 0;
  1336. border-radius: 4px;
  1337. padding: 0 10px;
  1338. overflow: hidden;
  1339. text-overflow: ellipsis;
  1340. white-space: nowrap;
  1341. height: 28px;
  1342. line-height: 28px;
  1343. // 移动端适配
  1344. @media (max-width: 480px) {
  1345. max-width: 80px;
  1346. padding: 0 8px;
  1347. }
  1348. }
  1349. }
  1350. // 编辑状态
  1351. .desc-edit {
  1352. .edit-field {
  1353. padding: 8px 12px;
  1354. background: #f8f9fa;
  1355. border-radius: 6px;
  1356. :deep(.van-field__control) {
  1357. min-height: 36px;
  1358. padding: 4px 8px;
  1359. background: white;
  1360. border-radius: 4px;
  1361. }
  1362. :deep(.van-button) {
  1363. margin-left: 8px;
  1364. height: 28px;
  1365. line-height: 26px;
  1366. &.confirm-btn {
  1367. background: var(--primary-color);
  1368. border-color: var(--primary-color);
  1369. padding: 0 5px;
  1370. }
  1371. &.cancel-btn {
  1372. color: var(--text-secondary);
  1373. border-color: #ddd;
  1374. padding: 0 5px;
  1375. }
  1376. }
  1377. }
  1378. }
  1379. // 保持与报警信息相同的交互细节
  1380. &:last-child {
  1381. border-bottom: none;
  1382. padding-bottom: 0;
  1383. }
  1384. }
  1385. // 物料监控样式
  1386. &.material-usage {
  1387. --material-icon-size: 20px;
  1388. --material-grid-gap: 12px;
  1389. .material-group {
  1390. // margin-bottom: 20px;
  1391. .material-title {
  1392. color: var(--text-primary);
  1393. font-size: 12px;
  1394. margin: 0 0 12px 0;
  1395. font-weight: 500;
  1396. }
  1397. .material-grid {
  1398. display: grid;
  1399. grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  1400. gap: var(--material-grid-gap);
  1401. }
  1402. .material-item {
  1403. display: flex;
  1404. align-items: center;
  1405. padding: 8px;
  1406. background: #e7e7e7;
  1407. border-radius: 6px;
  1408. .material-icon {
  1409. font-size: var(--material-icon-size);
  1410. margin-right: 8px;
  1411. flex-shrink: 0;
  1412. }
  1413. .material-info {
  1414. flex: 1;
  1415. min-width: 0;
  1416. }
  1417. .material-label {
  1418. display: block;
  1419. font-size: 13px;
  1420. color: var(--text-secondary);
  1421. white-space: nowrap;
  1422. overflow: hidden;
  1423. text-overflow: ellipsis;
  1424. }
  1425. .material-value {
  1426. display: block;
  1427. font-size: 15px;
  1428. color: var(--text-primary);
  1429. font-weight: 500;
  1430. }
  1431. }
  1432. }
  1433. .supply-section {
  1434. margin-top: 16px;
  1435. .supply-button {
  1436. border-radius: 6px;
  1437. --van-button-default-height: 40px;
  1438. .van-icon {
  1439. font-size: 18px;
  1440. vertical-align: -2px;
  1441. }
  1442. }
  1443. }
  1444. }
  1445. // 报警样式
  1446. &.alert-section {
  1447. --alert-icon-size: 16px;
  1448. --alert-spacing: 8px;
  1449. .alert-list {
  1450. margin-bottom: 16px;
  1451. .alert-item {
  1452. &:not(:last-child) {
  1453. margin-bottom: 12px;
  1454. }
  1455. }
  1456. }
  1457. .alert-content {
  1458. background: #fff5f5;
  1459. border-radius: 6px;
  1460. padding: 12px;
  1461. }
  1462. .alert-time {
  1463. display: flex;
  1464. align-items: center;
  1465. color: var(--text-secondary);
  1466. font-size: 13px;
  1467. margin-bottom: var(--alert-spacing);
  1468. .alert-icon {
  1469. font-size: var(--alert-icon-size);
  1470. margin-right: 6px;
  1471. color: var(--text-secondary);
  1472. }
  1473. }
  1474. .alert-message {
  1475. display: flex;
  1476. align-items: flex-start;
  1477. color: var(--danger-color);
  1478. .alert-icon {
  1479. font-size: var(--alert-icon-size);
  1480. margin-right: 6px;
  1481. flex-shrink: 0;
  1482. }
  1483. .alert-text {
  1484. flex: 1;
  1485. word-break: break-word;
  1486. line-height: 1.4;
  1487. }
  1488. }
  1489. .alert-actions {
  1490. margin-top: 16px;
  1491. .van-button {
  1492. border-radius: 6px;
  1493. &::before {
  1494. border-radius: 5px !important;
  1495. }
  1496. }
  1497. }
  1498. }
  1499. // 保留原有label样式
  1500. label {
  1501. color: var(--text-primary);
  1502. }
  1503. }
  1504. .action-buttons {
  1505. display: flex;
  1506. gap: 8px;
  1507. padding: 12px 0;
  1508. .van-button {
  1509. flex: 1;
  1510. }
  1511. }
  1512. }
  1513. .toggle-detail {
  1514. display: flex;
  1515. align-items: center;
  1516. justify-content: center;
  1517. color: var(--primary-color);
  1518. padding-top: 12px;
  1519. cursor: pointer;
  1520. .van-icon {
  1521. margin-left: 4px;
  1522. }
  1523. }
  1524. }
  1525. }
  1526. }
  1527. }
  1528. }
  1529. @keyframes pulse {
  1530. 0%,
  1531. 100% {
  1532. opacity: 0.6;
  1533. }
  1534. 50% {
  1535. opacity: 1;
  1536. }
  1537. }
  1538. </style>