SprayControl.vue 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. <template>
  2. <div class="fire-safety-panel">
  3. <!-- 数据列表容器 -->
  4. <div class="sensor-list">
  5. <!-- 按钮 -->
  6. <div class="control-bar">
  7. <a-button class="control-btn" @click="handleSpary(true)">启动喷淋</a-button>
  8. <a-button class="control-btn" @click="handleSpary(false)">停止喷淋</a-button>
  9. <a-button class="control-btn">手动控制</a-button>
  10. <a-button class="control-btn">自动控制</a-button>
  11. </div>
  12. <!-- 循环渲染分组 -->
  13. <div v-for="(beltData, index) in data.beltData" :key="index" class="block-item">
  14. <!-- {{ beltData }} -->
  15. <!-- 这里应该由data渲染,因为data数量是动态不固定的,config.config是静态的 -->
  16. <div v-for="(group, index) in config.config" :key="index" class="sensor-group">
  17. <!-- 组标题 -->
  18. <div class="group-title">
  19. <a-checkbox
  20. class="check-btn"
  21. :checked="selectedSids.includes(beltData.sid)"
  22. @change="(e) => handleCheckChange(e.target.checked, beltData.sid)"
  23. >
  24. 控制勾选
  25. </a-checkbox>
  26. </div>
  27. <!-- 循环渲染组内项 -->
  28. <div v-for="(item, itemIndex) in group.contentTop" :key="itemIndex" class="sensor-item" :class="getBgClass(itemIndex)">
  29. <div class="item-icon" :class="getBgClass(itemIndex)"></div>
  30. <div class="item-status">
  31. <div class="item-label">{{ item.label }}</div>
  32. <div class="item-code">{{ getItemText(beltData, item) }}</div>
  33. </div>
  34. </div>
  35. <div class="container">
  36. <div v-for="(item, itemIndex) in group.contents" :key="itemIndex" class="dataInfo-item" :class="getBgClass(itemIndex)">
  37. <div class="item-status">
  38. <div class="item-label">{{ item.label }}</div>
  39. <div class="item-code">
  40. <span class="status-dot" v-if="item.trans" :class="getStatusClass(beltData, item)"> </span>
  41. {{ getItemText(beltData, item) }}
  42. </div>
  43. </div>
  44. </div>
  45. </div>
  46. </div>
  47. </div>
  48. </div>
  49. </div>
  50. </template>
  51. <script setup lang="ts">
  52. import { computed, onMounted, ref } from 'vue';
  53. import { getFormattedText } from '../../hooks/helper';
  54. const props = defineProps<{
  55. config: Array<{
  56. readFrom: string;
  57. contentTop: Array<{
  58. label: string;
  59. code: string;
  60. status?: string;
  61. info?: string;
  62. }>;
  63. contents: Array<{
  64. label: string;
  65. code: string;
  66. trans?: any;
  67. }>;
  68. }>;
  69. data: {
  70. [key: string]: any;
  71. };
  72. }>();
  73. // --- 存储选中的 sid ---
  74. const selectedSids = ref<string[]>([]);
  75. // --- 处理勾选变化 ---
  76. const handleCheckChange = (checked: boolean, sid: string) => {
  77. if (checked) {
  78. // 勾选:如果不存在则加入数组
  79. if (!selectedSids.value.includes(sid)) {
  80. selectedSids.value.push(sid);
  81. }
  82. } else {
  83. // 取消勾选:从数组中移除
  84. const index = selectedSids.value.indexOf(sid);
  85. if (index > -1) {
  86. selectedSids.value.splice(index, 1);
  87. }
  88. }
  89. console.log('当前选中的 SID 列表:', selectedSids.value);
  90. };
  91. const getBgClass = (index) => {
  92. return index % 2 === 0 ? 'bg-1' : 'bg-2';
  93. };
  94. /**
  95. * 根据配置项从当前皮带数据中获取对应的值
  96. * @param currentItem 当前循环到的单条皮带数据 (beltData)
  97. * @param configItem 配置项 (包含 code 和 trans)
  98. */
  99. const getItemText = (currentItem: any, configItem: { code: string; trans?: any }) => {
  100. // 1. 获取原始值: 通过 code 字段从 currentItem 中取值
  101. const rawValue = currentItem[configItem.code];
  102. // 2. 如果有翻译映射 (trans),则进行翻译
  103. if (configItem.trans && configItem.trans[rawValue] !== undefined) {
  104. return configItem.trans[rawValue];
  105. }
  106. // 3. 没有翻译则直接返回原始值
  107. return rawValue;
  108. };
  109. const handleSpary = (state: boolean) => {
  110. if (state) {
  111. console.log('启动喷淋');
  112. } else {
  113. console.log('停止喷淋');
  114. }
  115. };
  116. const getStatusClass = (beltData: any, item: any) => {
  117. const val = getItemText(beltData, item);
  118. // 根据翻译后的值判断颜色(连接=绿,断开=红)
  119. if (val === '正常') return 'status-normal';
  120. if (val === '断开') return 'status-danger';
  121. return '';
  122. };
  123. onMounted(() => {
  124. console.log(props.config, '123');
  125. console.log(props.data, 'data');
  126. });
  127. </script>
  128. <style scoped lang="less">
  129. /* 全局面板样式 */
  130. .fire-safety-panel {
  131. border-radius: 8px;
  132. padding: 3px;
  133. color: #fff;
  134. font-family: 'Microsoft YaHei', sans-serif;
  135. padding-top: 10px;
  136. }
  137. /* 列表容器 */
  138. .sensor-list {
  139. display: flex;
  140. flex-direction: column;
  141. gap: 15px; /* 组间距 */
  142. }
  143. /** 按钮 **/
  144. .control-bar {
  145. background: url('@/assets/images/beltFire/yjkf/1-2title.png');
  146. background-size: 100% 100%;
  147. display: flex;
  148. justify-content: space-around;
  149. height: 50px;
  150. }
  151. .control-btn {
  152. background: linear-gradient(180deg, #34b7f1 0%, #1890ff 100%);
  153. border: 1px solid #40c4ff;
  154. color: #fff;
  155. font-size: 12px;
  156. padding: 2px 8px;
  157. height: 24px;
  158. margin: auto;
  159. box-shadow: 0 0 6px 2px rgba(24, 144, 255, 0.4);
  160. display: inline-flex;
  161. align-items: center;
  162. justify-content: center;
  163. }
  164. /* 分组卡片 */
  165. .sensor-group {
  166. background: url('@/assets/images/beltFire/fireMonitor/2-1.png') no-repeat;
  167. background-size: 100% 100%;
  168. padding: 10px;
  169. }
  170. /* 组标题 */
  171. .group-title {
  172. background: url('@/assets/images/beltFire/fireMonitor/2-2.png') no-repeat;
  173. background-size: 35% 100%;
  174. color: #fff;
  175. font-size: 12px;
  176. font-weight: bold;
  177. font-style: italic;
  178. margin-bottom: 8px;
  179. padding-bottom: 5px;
  180. padding-left: 10px;
  181. .check-btn {
  182. color: #fff;
  183. font-size: 12px;
  184. }
  185. }
  186. /* 列表项 */
  187. .sensor-item {
  188. height: 30px;
  189. background-size: 100% 100%;
  190. display: flex;
  191. align-items: center;
  192. margin-bottom: 5px;
  193. font-size: 13px;
  194. color: #c0d0e0;
  195. overflow-y: auto;
  196. background-repeat: no-repeat;
  197. }
  198. .sensor-item.bg-1 {
  199. background-image: url('@/assets/images/beltFire/yjkf/1-5.png');
  200. }
  201. .sensor-item.bg-2 {
  202. background-image: url('@/assets/images/beltFire/yjkf/1-6.png');
  203. }
  204. .item-icon {
  205. background-size: 100% 100%;
  206. width: 20px;
  207. height: 20px;
  208. margin-left: 10px;
  209. }
  210. .item-icon.bg-1 {
  211. background: url('@/assets/images/beltFire/yjkf/1-3area.svg') no-repeat;
  212. }
  213. .item-icon.bg-2 {
  214. background: url('@/assets/images/beltFire/yjkf/1-4wz.svg') no-repeat;
  215. }
  216. .container {
  217. display: flex;
  218. flex-wrap: wrap;
  219. gap: 10px;
  220. }
  221. .dataInfo-item {
  222. display: flex;
  223. justify-content: space-around;
  224. margin-bottom: 5px;
  225. font-size: 13px;
  226. width: calc(50% - 5px);
  227. color: #c0d0e0;
  228. }
  229. .dataInfo-item:nth-child(1),
  230. .dataInfo-item:nth-child(4) {
  231. background: url('@/assets/images/beltFire/yjkf/1-7.png') no-repeat;
  232. background-size: 100% 100%;
  233. }
  234. .dataInfo-item:nth-child(2),
  235. .dataInfo-item:nth-child(3) {
  236. background: url('@/assets/images/beltFire/yjkf/1-8.png') no-repeat;
  237. background-size: 100% 100%;
  238. }
  239. .item-status {
  240. width: 100%;
  241. display: flex;
  242. flex-direction: row;
  243. justify-content: space-between;
  244. margin: 0 15px 0 20px;
  245. .item-label {
  246. color: #fff;
  247. height: 30px;
  248. line-height: 30px;
  249. }
  250. .item-code {
  251. height: 30px;
  252. line-height: 30px;
  253. text-align: right;
  254. color: #91faff;
  255. font-size: 12px;
  256. font-family: 'douyuFont';
  257. }
  258. }
  259. .item-content {
  260. flex: 1;
  261. display: flex;
  262. align-items: center;
  263. .label {
  264. color: #fff;
  265. }
  266. }
  267. .item-value {
  268. margin-right: 10px;
  269. font-size: 12px;
  270. color: #36dae7;
  271. }
  272. .item-info {
  273. color: #ffff;
  274. font-size: 11px;
  275. }
  276. /* 状态指示灯 */
  277. .status-dot {
  278. display: inline-block;
  279. width: 10px;
  280. height: 10px;
  281. border-radius: 50%;
  282. margin-left: 5px;
  283. margin-top: 10px;
  284. animation: pulse 2s infinite;
  285. background: #3a3a3a;
  286. // position: absolute;
  287. // left: 0;
  288. // top: 50%;
  289. transform: translateY(-50%);
  290. box-shadow: 0 0 6px 2px rgba(90, 90, 90, 0.6);
  291. &.status-normal {
  292. background-color: #00ff00;
  293. box-shadow: 0 0 6px 2px rgba(104, 255, 45, 0.6);
  294. }
  295. &.status-danger {
  296. background-color: #ff4d4d;
  297. box-shadow: 0 0 6px 2px rgba(255, 48, 48, 0.6);
  298. animation: flash 1s infinite;
  299. }
  300. }
  301. /* 动画效果 */
  302. @keyframes pulse {
  303. 0% {
  304. transform: scale(1);
  305. opacity: 1;
  306. }
  307. 50% {
  308. transform: scale(1.2);
  309. opacity: 0.8;
  310. }
  311. 100% {
  312. transform: scale(1);
  313. opacity: 1;
  314. }
  315. }
  316. @keyframes flash {
  317. 0% {
  318. opacity: 1;
  319. }
  320. 50% {
  321. opacity: 0.5;
  322. }
  323. 100% {
  324. opacity: 1;
  325. }
  326. }
  327. </style>