PequipmentLed.vue 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871
  1. <!-- 物联感知设备管理 -->
  2. <template>
  3. <div class="app-container equipment-page">
  4. <div class="summary-grid">
  5. <el-card shadow="hover" class="summary-card primary">
  6. <div class="summary-label">设备总数</div>
  7. <div class="summary-value">{{ summary.total }}</div>
  8. <div class="summary-desc">平台已接入物联感知设备</div>
  9. </el-card>
  10. <el-card shadow="hover" class="summary-card success">
  11. <div class="summary-label">在用设备</div>
  12. <div class="summary-value">{{ summary.active }}</div>
  13. <div class="summary-desc">当前状态为在用的设备</div>
  14. </el-card>
  15. <el-card shadow="hover" class="summary-card warning">
  16. <div class="summary-label">维修/待处理</div>
  17. <div class="summary-value">{{ summary.warning }}</div>
  18. <div class="summary-desc">维修、待入库、闲置等状态</div>
  19. </el-card>
  20. <el-card shadow="hover" class="summary-card info">
  21. <div class="summary-label">设备类型</div>
  22. <div class="summary-value">{{ summary.typeCount }}</div>
  23. <div class="summary-desc">当前已维护的设备分类数</div>
  24. </el-card>
  25. </div>
  26. <el-card shadow="never" class="query-card">
  27. <el-form ref="queryRef" :model="queryParams" :inline="true" v-show="showSearch">
  28. <el-form-item label="设备编码" prop="equipmentCode">
  29. <el-input
  30. v-model="queryParams.equipmentCode"
  31. placeholder="请输入设备编码"
  32. clearable
  33. style="width: 220px"
  34. @keyup.enter="handleQuery"
  35. />
  36. </el-form-item>
  37. <el-form-item label="设备名称" prop="equipmentName">
  38. <el-input
  39. v-model="queryParams.equipmentName"
  40. placeholder="请输入设备名称"
  41. clearable
  42. style="width: 220px"
  43. @keyup.enter="handleQuery"
  44. />
  45. </el-form-item>
  46. <el-form-item label="设备型号" prop="equipmentModel">
  47. <el-input
  48. v-model="queryParams.equipmentModel"
  49. placeholder="请输入设备型号"
  50. clearable
  51. style="width: 220px"
  52. @keyup.enter="handleQuery"
  53. />
  54. </el-form-item>
  55. <el-form-item label="制造商" prop="manufacturer">
  56. <el-input
  57. v-model="queryParams.manufacturer"
  58. placeholder="请输入制造商"
  59. clearable
  60. style="width: 220px"
  61. @keyup.enter="handleQuery"
  62. />
  63. </el-form-item>
  64. <el-form-item>
  65. <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
  66. <el-button icon="Refresh" @click="resetQuery">重置</el-button>
  67. </el-form-item>
  68. </el-form>
  69. </el-card>
  70. <el-row :gutter="16" class="main-row">
  71. <el-col :xs="24" :lg="16">
  72. <el-card shadow="never" class="table-card">
  73. <template #header>
  74. <div class="card-header-row">
  75. <div>
  76. <div class="card-title">设备台账</div>
  77. <div class="card-subtitle">对平台的传感器类型监测设备的基本信息进行管理</div>
  78. </div>
  79. <div class="card-actions">
  80. <el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
  81. <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">删除</el-button>
  82. <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
  83. </div>
  84. </div>
  85. </template>
  86. <el-table
  87. v-loading="loading"
  88. :data="equipmentList"
  89. highlight-current-row
  90. @selection-change="handleSelectionChange"
  91. @current-change="handleCurrentChange"
  92. @row-click="handleRowClick"
  93. >
  94. <el-table-column type="selection" width="55" align="center" />
  95. <el-table-column label="设备编码" align="center" prop="equipmentCode" min-width="140" />
  96. <el-table-column label="设备名称" align="center" prop="equipmentName" min-width="160" show-overflow-tooltip />
  97. <el-table-column label="设备型号" align="center" prop="equipmentModel" min-width="120" />
  98. <el-table-column label="设备类型" align="center" min-width="140">
  99. <template #default="scope">
  100. {{ typeNameMap[scope.row.equipmentTypeId] || scope.row.equipmentTypeId || "-" }}
  101. </template>
  102. </el-table-column>
  103. <el-table-column label="资产状态" align="center" width="130">
  104. <template #default="scope">
  105. <div class="status-cell">
  106. <el-tag :type="getStatusTagType(getRowStatus(scope.row.equipmentId))">
  107. {{ getStatusText(getRowStatus(scope.row.equipmentId)) }}
  108. </el-tag>
  109. <el-tooltip content="资产状态维护" placement="top">
  110. <el-button
  111. link
  112. type="warning"
  113. icon="Edit"
  114. aria-label="资产状态维护"
  115. class="status-edit-button"
  116. @click.stop="openStatusDialog(scope.row)"
  117. />
  118. </el-tooltip>
  119. </div>
  120. </template>
  121. </el-table-column>
  122. <el-table-column label="制造商" align="center" prop="manufacturer" min-width="140" show-overflow-tooltip />
  123. <el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
  124. <template #default="scope">
  125. <el-button link type="primary" icon="Edit" @click.stop="handleUpdate(scope.row)">修改</el-button>
  126. <el-button link type="success" icon="Aim" @click.stop="openTestDialog(scope.row)">测试验证</el-button>
  127. </template>
  128. </el-table-column>
  129. </el-table>
  130. <pagination
  131. v-show="total > 0"
  132. :total="total"
  133. v-model:page="queryParams.pageNum"
  134. v-model:limit="queryParams.pageSize"
  135. @pagination="getList"
  136. />
  137. </el-card>
  138. </el-col>
  139. <el-col :xs="24" :lg="8">
  140. <el-card shadow="never" class="detail-card">
  141. <template #header>
  142. <div class="card-header-row">
  143. <div>
  144. <div class="card-title">设备详情</div>
  145. <div class="card-subtitle">选中设备后查看基本信息、关联点位与测试记录</div>
  146. </div>
  147. </div>
  148. </template>
  149. <el-empty v-if="!selectedDetail.equipmentInfo" description="请从左侧选择一台设备" />
  150. <template v-else>
  151. <div class="detail-hero">
  152. <div class="hero-title">{{ selectedDetail.equipmentInfo.equipmentName }}</div>
  153. <div class="hero-meta">
  154. <span>{{ selectedDetail.equipmentInfo.equipmentCode }}</span>
  155. <span>{{ selectedDetail.typeInfo?.typeName || "-" }}</span>
  156. <span>{{ getStatusText(selectedDetail.statusInfo?.currentStatus) }}</span>
  157. </div>
  158. </div>
  159. <el-descriptions :column="1" border size="small">
  160. <el-descriptions-item label="设备型号">{{ selectedDetail.equipmentInfo.equipmentModel || "-" }}</el-descriptions-item>
  161. <el-descriptions-item label="制造商">{{ selectedDetail.equipmentInfo.manufacturer || "-" }}</el-descriptions-item>
  162. <el-descriptions-item label="设备位置">{{ selectedDetail.equipmentInfo.equipmentLocation || "-" }}</el-descriptions-item>
  163. <el-descriptions-item label="规格参数">{{ selectedDetail.equipmentInfo.equipmentSpec || "-" }}</el-descriptions-item>
  164. <el-descriptions-item label="维护人员">
  165. {{ selectedDetail.equipmentInfo.maintainer || "-" }}
  166. </el-descriptions-item>
  167. <el-descriptions-item label="联系电话">
  168. {{ selectedDetail.equipmentInfo.maintainerPhone || "-" }}
  169. </el-descriptions-item>
  170. </el-descriptions>
  171. <div class="section-block">
  172. <div class="section-title">关联点位</div>
  173. <el-table :data="selectedDetail.pointRelList || []" size="small" max-height="180">
  174. <el-table-column label="监测点编码" prop="pointCode" min-width="160" show-overflow-tooltip />
  175. <el-table-column label="监测点名称" prop="pointName" min-width="160" show-overflow-tooltip />
  176. </el-table>
  177. </div>
  178. <div class="section-block">
  179. <div class="section-title">测试记录</div>
  180. <el-table :data="selectedDetail.testList || []" size="small" max-height="180">
  181. <el-table-column label="时间" prop="testTime" min-width="150" />
  182. <el-table-column label="类型" prop="testType" min-width="100" />
  183. <el-table-column label="结果" prop="testResult" width="80" />
  184. </el-table>
  185. </div>
  186. </template>
  187. </el-card>
  188. </el-col>
  189. </el-row>
  190. <el-dialog :title="title" v-model="open" width="860px" append-to-body>
  191. <el-form ref="equipmentRef" :model="form" :rules="rules" label-width="110px">
  192. <el-row :gutter="16">
  193. <el-col :span="12">
  194. <el-form-item label="设备编码" prop="equipmentCode">
  195. <el-input v-model="form.equipmentCode" placeholder="请输入设备编码" />
  196. </el-form-item>
  197. </el-col>
  198. <el-col :span="12">
  199. <el-form-item label="设备名称" prop="equipmentName">
  200. <el-input v-model="form.equipmentName" placeholder="请输入设备名称" />
  201. </el-form-item>
  202. </el-col>
  203. <el-col :span="12">
  204. <el-form-item label="设备型号" prop="equipmentModel">
  205. <el-input v-model="form.equipmentModel" placeholder="请输入设备型号" />
  206. </el-form-item>
  207. </el-col>
  208. <el-col :span="12">
  209. <el-form-item label="设备类型" prop="equipmentTypeId">
  210. <el-tree-select
  211. v-model="form.equipmentTypeId"
  212. :data="equipmentTypeTree"
  213. node-key="id"
  214. :props="{ label: 'typeName', children: 'children', disabled: 'disabled' }"
  215. default-expand-all
  216. :render-after-expand="false"
  217. placeholder="请选择设备类型"
  218. style="width: 100%"
  219. />
  220. </el-form-item>
  221. </el-col>
  222. <el-col :span="12">
  223. <el-form-item label="制造商" prop="manufacturer">
  224. <el-input v-model="form.manufacturer" placeholder="请输入制造商" />
  225. </el-form-item>
  226. </el-col>
  227. <el-col :span="12">
  228. <el-form-item label="生产日期" prop="productionDate">
  229. <el-date-picker
  230. v-model="form.productionDate"
  231. type="date"
  232. value-format="YYYY-MM-DD"
  233. placeholder="请选择生产日期"
  234. style="width: 100%"
  235. />
  236. </el-form-item>
  237. </el-col>
  238. <el-col :span="24">
  239. <el-form-item label="规格参数" prop="equipmentSpec">
  240. <el-input v-model="form.equipmentSpec" placeholder="请输入设备规格参数" />
  241. </el-form-item>
  242. </el-col>
  243. <el-col :span="12">
  244. <el-form-item label="资产价值(元)" prop="assetValue">
  245. <el-input-number v-model="form.assetValue" :precision="2" :step="100" style="width: 100%" />
  246. </el-form-item>
  247. </el-col>
  248. <el-col :span="12">
  249. <el-form-item label="使用年限(年)" prop="useLife">
  250. <el-input-number v-model="form.useLife" :precision="0" :step="1" style="width: 100%" />
  251. </el-form-item>
  252. </el-col>
  253. <el-col :span="24">
  254. <el-form-item label="设备位置" prop="equipmentLocation">
  255. <el-input v-model="form.equipmentLocation" placeholder="请输入设备位置" />
  256. </el-form-item>
  257. </el-col>
  258. <el-col :span="12">
  259. <el-form-item label="经度" prop="longitude">
  260. <el-input-number v-model="form.longitude" :precision="6" :step="0.000001" style="width: 100%" />
  261. </el-form-item>
  262. </el-col>
  263. <el-col :span="12">
  264. <el-form-item label="纬度" prop="latitude">
  265. <el-input-number v-model="form.latitude" :precision="6" :step="0.000001" style="width: 100%" />
  266. </el-form-item>
  267. </el-col>
  268. <el-col :span="12">
  269. <el-form-item label="安装时间" prop="installTime">
  270. <el-date-picker
  271. v-model="form.installTime"
  272. type="datetime"
  273. value-format="YYYY-MM-DD HH:mm:ss"
  274. placeholder="请选择安装时间"
  275. style="width: 100%"
  276. />
  277. </el-form-item>
  278. </el-col>
  279. <el-col :span="12">
  280. <el-form-item label="维护人员" prop="maintainer">
  281. <el-input v-model="form.maintainer" placeholder="请输入维护人员" />
  282. </el-form-item>
  283. </el-col>
  284. <el-col :span="12">
  285. <el-form-item label="联系电话" prop="maintainerPhone">
  286. <el-input v-model="form.maintainerPhone" placeholder="请输入联系电话" />
  287. </el-form-item>
  288. </el-col>
  289. <el-col :span="24">
  290. <el-form-item label="备注" prop="remark">
  291. <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" />
  292. </el-form-item>
  293. </el-col>
  294. </el-row>
  295. </el-form>
  296. <template #footer>
  297. <div class="dialog-footer">
  298. <el-button type="primary" @click="submitForm">确 定</el-button>
  299. <el-button @click="cancel">取 消</el-button>
  300. </div>
  301. </template>
  302. </el-dialog>
  303. <el-dialog title="设备状态维护" v-model="statusOpen" width="420px" append-to-body>
  304. <el-form label-width="90px">
  305. <el-form-item label="设备名称">
  306. <span>{{ currentRow.equipmentName || "-" }}</span>
  307. </el-form-item>
  308. <el-form-item label="资产状态">
  309. <el-select v-model="statusForm.currentStatus" style="width: 100%">
  310. <el-option
  311. v-for="item in equipmentStatusOptions"
  312. :key="item.value"
  313. :label="item.label"
  314. :value="item.value"
  315. />
  316. </el-select>
  317. </el-form-item>
  318. </el-form>
  319. <template #footer>
  320. <div class="dialog-footer">
  321. <el-button type="primary" @click="submitStatus">确 定</el-button>
  322. <el-button @click="statusOpen = false">取 消</el-button>
  323. </div>
  324. </template>
  325. </el-dialog>
  326. <el-dialog title="设备测试验证" v-model="testOpen" width="520px" append-to-body>
  327. <el-form ref="testRef" :model="testForm" label-width="90px">
  328. <el-form-item label="设备名称">
  329. <span>{{ currentRow.equipmentName || "-" }}</span>
  330. </el-form-item>
  331. <el-form-item label="测试类型">
  332. <el-input v-model="testForm.testType" placeholder="例如:通信测试" />
  333. </el-form-item>
  334. <el-form-item label="测试人员">
  335. <el-input v-model="testForm.testUser" placeholder="请输入测试人员" />
  336. </el-form-item>
  337. <el-form-item label="测试详情">
  338. <el-input v-model="testForm.testDetail" type="textarea" :rows="4" placeholder="请输入测试详情" />
  339. </el-form-item>
  340. </el-form>
  341. <template #footer>
  342. <div class="dialog-footer">
  343. <el-button type="primary" @click="submitTest">确 定</el-button>
  344. <el-button @click="testOpen = false">取 消</el-button>
  345. </div>
  346. </template>
  347. </el-dialog>
  348. </div>
  349. </template>
  350. <script setup>
  351. import { computed, getCurrentInstance, onMounted, reactive, ref, toRefs } from "vue";
  352. import {
  353. addEquipment,
  354. delEquipment,
  355. getEquipment,
  356. getEquipmentDetail,
  357. listEquipment,
  358. listEquipmentStatusAll,
  359. listEquipmentTypeAll,
  360. testEquipment,
  361. updateEquipment,
  362. updateEquipmentStatus
  363. } from "@/api/pipeNetwork/pdeviceInform";
  364. const { proxy } = getCurrentInstance();
  365. const equipmentList = ref([]);
  366. const equipmentTypeOptions = ref([]);
  367. const equipmentStatusList = ref([]);
  368. const loading = ref(false);
  369. const open = ref(false);
  370. const statusOpen = ref(false);
  371. const testOpen = ref(false);
  372. const showSearch = ref(true);
  373. const ids = ref([]);
  374. const multiple = ref(true);
  375. const total = ref(0);
  376. const title = ref("");
  377. const currentRow = ref({});
  378. const selectedDetail = ref({});
  379. const equipmentStatusOptions = [
  380. { label: "在用", value: 1 },
  381. { label: "闲置", value: 2 },
  382. { label: "维修", value: 3 },
  383. { label: "报废", value: 4 },
  384. { label: "待入库", value: 5 }
  385. ];
  386. const summary = reactive({
  387. total: 0,
  388. active: 0,
  389. warning: 0,
  390. typeCount: 0
  391. });
  392. const typeNameMap = computed(() => {
  393. return equipmentTypeOptions.value.reduce((acc, item) => {
  394. acc[item.id] = item.typeName;
  395. return acc;
  396. }, {});
  397. });
  398. // parentTypeId 为 0(或空)表示父类
  399. function isTopLevelType(item) {
  400. const parentTypeId = item?.parentTypeId;
  401. return parentTypeId === 0 || parentTypeId === "0" || parentTypeId === null || parentTypeId === undefined || parentTypeId === "";
  402. }
  403. const equipmentTypeTree = computed(() => {
  404. const list = equipmentTypeOptions.value || [];
  405. const entries = [];
  406. // 同时按 typeId / id 建索引,兼容 parentTypeId 指向不同主键字段的情况
  407. const nodeIndex = {};
  408. list.forEach((item) => {
  409. const node = {
  410. id: item.id,
  411. typeId: item.typeId,
  412. typeName: item.typeName,
  413. disabled: isTopLevelType(item),
  414. children: []
  415. };
  416. entries.push({ item, node });
  417. [item.typeId, item.id].forEach((key) => {
  418. if (key !== undefined && key !== null) {
  419. nodeIndex[String(key)] = node;
  420. }
  421. });
  422. });
  423. const roots = [];
  424. // 父节点不在返回列表中时兜底归类,避免子类型在下拉中丢失
  425. const fallbackNode = { id: "__other__", typeId: "__other__", typeName: "未分类", disabled: true, children: [] };
  426. entries.forEach(({ item, node }) => {
  427. if (isTopLevelType(item)) {
  428. roots.push(node);
  429. return;
  430. }
  431. const parent = nodeIndex[String(item.parentTypeId)];
  432. if (parent && parent !== node) {
  433. parent.children.push(node);
  434. } else {
  435. fallbackNode.children.push(node);
  436. }
  437. });
  438. // 带子级的节点(即父类)统一禁选,叶子节点可选;空 children 去掉渲染为叶子
  439. const prune = (node) => {
  440. if (node.children && node.children.length) {
  441. node.disabled = true;
  442. node.children.forEach(prune);
  443. } else {
  444. delete node.children;
  445. }
  446. return node;
  447. };
  448. const result = roots.map(prune);
  449. if (fallbackNode.children.length) {
  450. result.push(prune(fallbackNode));
  451. }
  452. return result;
  453. });
  454. const statusMap = computed(() => {
  455. return equipmentStatusList.value.reduce((acc, item) => {
  456. acc[item.equipmentId] = item;
  457. return acc;
  458. }, {});
  459. });
  460. const data = reactive({
  461. queryParams: {
  462. pageNum: 1,
  463. pageSize: 10,
  464. equipmentCode: undefined,
  465. equipmentName: undefined,
  466. equipmentModel: undefined,
  467. manufacturer: undefined
  468. },
  469. form: {},
  470. statusForm: {
  471. currentStatus: 1
  472. },
  473. testForm: {
  474. testType: undefined,
  475. testDetail: undefined,
  476. testUser: undefined
  477. },
  478. rules: {
  479. equipmentCode: [{ required: true, message: "设备编码不能为空", trigger: "blur" }],
  480. equipmentName: [{ required: true, message: "设备名称不能为空", trigger: "blur" }],
  481. equipmentTypeId: [{ required: true, message: "设备类型不能为空", trigger: "change" }]
  482. }
  483. });
  484. const { queryParams, form, statusForm, testForm, rules } = toRefs(data);
  485. function createEmptyForm() {
  486. return {
  487. equipmentId: undefined,
  488. equipmentCode: undefined,
  489. equipmentName: undefined,
  490. equipmentModel: undefined,
  491. equipmentSpec: undefined,
  492. manufacturer: undefined,
  493. productionDate: undefined,
  494. equipmentTypeId: undefined,
  495. assetValue: undefined,
  496. useLife: undefined,
  497. longitude: undefined,
  498. latitude: undefined,
  499. equipmentLocation: undefined,
  500. installTime: undefined,
  501. maintainer: undefined,
  502. maintainerPhone: undefined,
  503. remark: undefined
  504. };
  505. }
  506. function getRowStatus(equipmentId) {
  507. return statusMap.value[equipmentId]?.currentStatus;
  508. }
  509. function getStatusText(status) {
  510. const found = equipmentStatusOptions.find((item) => item.value === status);
  511. return found?.label || "未维护";
  512. }
  513. function getStatusTagType(status) {
  514. const map = {
  515. 1: "success",
  516. 2: "info",
  517. 3: "warning",
  518. 4: "danger",
  519. 5: ""
  520. };
  521. return map[status] || "";
  522. }
  523. function buildSummary() {
  524. summary.total = equipmentStatusList.value.length;
  525. summary.active = equipmentStatusList.value.filter((item) => item.currentStatus === 1).length;
  526. summary.warning = equipmentStatusList.value.filter((item) => item.currentStatus !== 1).length;
  527. summary.typeCount = new Set(equipmentTypeOptions.value.map((item) => item.typeName).filter(Boolean)).size;
  528. }
  529. function getList() {
  530. loading.value = true;
  531. return listEquipment(queryParams.value)
  532. .then((response) => {
  533. equipmentList.value = response.records || [];
  534. total.value = response.total || 0;
  535. if (!selectedDetail.value.equipmentInfo && equipmentList.value.length) {
  536. return handleView(equipmentList.value[0]);
  537. }
  538. return undefined;
  539. })
  540. .finally(() => {
  541. loading.value = false;
  542. });
  543. }
  544. function loadAssistData() {
  545. return Promise.all([listEquipmentTypeAll(), listEquipmentStatusAll()]).then(([typeRes, statusRes]) => {
  546. equipmentTypeOptions.value = typeRes.data || [];
  547. equipmentStatusList.value = statusRes.data || [];
  548. buildSummary();
  549. });
  550. }
  551. function reset() {
  552. form.value = createEmptyForm();
  553. proxy.resetForm("equipmentRef");
  554. }
  555. function handleQuery() {
  556. queryParams.value.pageNum = 1;
  557. getList();
  558. }
  559. function resetQuery() {
  560. proxy.resetForm("queryRef");
  561. handleQuery();
  562. }
  563. function handleSelectionChange(selection) {
  564. ids.value = selection.map((item) => item.equipmentId);
  565. multiple.value = !selection.length;
  566. }
  567. function handleCurrentChange(currentRow) {
  568. if (currentRow?.equipmentId) {
  569. handleView(currentRow);
  570. }
  571. }
  572. function handleRowClick(row) {
  573. handleView(row);
  574. }
  575. function handleAdd() {
  576. reset();
  577. open.value = true;
  578. title.value = "新增物联感知设备";
  579. }
  580. function handleUpdate(row) {
  581. reset();
  582. const equipmentId = row?.equipmentId || ids.value[0];
  583. if (!equipmentId) {
  584. return;
  585. }
  586. getEquipment(equipmentId).then((response) => {
  587. form.value = { ...createEmptyForm(), ...(response.data || {}) };
  588. open.value = true;
  589. title.value = "修改物联感知设备";
  590. });
  591. }
  592. function handleView(row) {
  593. return getEquipmentDetail(row.equipmentId).then((response) => {
  594. selectedDetail.value = response.data || {};
  595. });
  596. }
  597. function cancel() {
  598. open.value = false;
  599. reset();
  600. }
  601. function submitForm() {
  602. proxy.$refs.equipmentRef.validate((valid) => {
  603. if (!valid) {
  604. return;
  605. }
  606. const request = form.value.equipmentId ? updateEquipment(form.value) : addEquipment(form.value);
  607. request.then(() => {
  608. proxy.$modal.msgSuccess(form.value.equipmentId ? "修改成功" : "新增成功");
  609. open.value = false;
  610. Promise.all([getList(), loadAssistData()]).then(() => {
  611. if (form.value.equipmentId) {
  612. handleView({ equipmentId: form.value.equipmentId });
  613. }
  614. });
  615. });
  616. });
  617. }
  618. function handleDelete() {
  619. const deleteIds = ids.value;
  620. if (!deleteIds.length) {
  621. return;
  622. }
  623. proxy.$modal
  624. .confirm(`是否确认删除选中的 ${deleteIds.length} 台设备?`)
  625. .then(() => Promise.all(deleteIds.map((id) => delEquipment(id))))
  626. .then(() => {
  627. proxy.$modal.msgSuccess("删除成功");
  628. if (deleteIds.includes(selectedDetail.value?.equipmentInfo?.equipmentId)) {
  629. selectedDetail.value = {};
  630. }
  631. return Promise.all([getList(), loadAssistData()]);
  632. });
  633. }
  634. function openStatusDialog(row) {
  635. currentRow.value = row;
  636. statusForm.value.currentStatus = getRowStatus(row.equipmentId) || 1;
  637. statusOpen.value = true;
  638. }
  639. function submitStatus() {
  640. updateEquipmentStatus(currentRow.value.equipmentId, statusForm.value.currentStatus).then(() => {
  641. proxy.$modal.msgSuccess("状态维护成功");
  642. statusOpen.value = false;
  643. Promise.all([loadAssistData(), handleView(currentRow.value)]);
  644. });
  645. }
  646. function openTestDialog(row) {
  647. currentRow.value = row;
  648. testForm.value = {
  649. testType: "通信测试",
  650. testDetail: undefined,
  651. testUser: undefined
  652. };
  653. testOpen.value = true;
  654. }
  655. function submitTest() {
  656. testEquipment(currentRow.value.equipmentId, testForm.value).then(() => {
  657. proxy.$modal.msgSuccess("测试记录已提交");
  658. testOpen.value = false;
  659. handleView(currentRow.value);
  660. });
  661. }
  662. onMounted(() => {
  663. reset();
  664. Promise.all([getList(), loadAssistData()]);
  665. });
  666. </script>
  667. <style scoped>
  668. .equipment-page {
  669. background: #f5f8fc;
  670. }
  671. .summary-grid {
  672. display: grid;
  673. grid-template-columns: repeat(4, minmax(0, 1fr));
  674. gap: 16px;
  675. margin-bottom: 16px;
  676. }
  677. .summary-card {
  678. border: none;
  679. color: #fff;
  680. }
  681. .summary-card :deep(.el-card__body) {
  682. padding: 20px;
  683. }
  684. .summary-card.primary {
  685. background: linear-gradient(135deg, #2563eb, #5b93ff);
  686. }
  687. .summary-card.success {
  688. background: linear-gradient(135deg, #059669, #34d399);
  689. }
  690. .summary-card.warning {
  691. background: linear-gradient(135deg, #d97706, #f7b955);
  692. }
  693. .summary-card.info {
  694. background: linear-gradient(135deg, #7c3aed, #9d72ff);
  695. }
  696. .summary-label {
  697. font-size: 14px;
  698. opacity: 0.9;
  699. }
  700. .summary-value {
  701. margin: 8px 0 10px;
  702. font-size: 30px;
  703. font-weight: 700;
  704. line-height: 1;
  705. }
  706. .summary-desc {
  707. font-size: 12px;
  708. opacity: 0.9;
  709. }
  710. .query-card,
  711. .table-card,
  712. .detail-card {
  713. border-radius: 16px;
  714. }
  715. .query-card {
  716. margin-bottom: 16px;
  717. }
  718. .card-header-row {
  719. display: flex;
  720. justify-content: space-between;
  721. align-items: center;
  722. gap: 12px;
  723. }
  724. .card-title {
  725. font-size: 16px;
  726. font-weight: 600;
  727. color: #1f2937;
  728. }
  729. .card-subtitle {
  730. margin-top: 4px;
  731. font-size: 12px;
  732. color: #6b7280;
  733. }
  734. .card-actions {
  735. display: flex;
  736. align-items: center;
  737. gap: 8px;
  738. flex-wrap: wrap;
  739. }
  740. .status-cell {
  741. display: inline-flex;
  742. align-items: center;
  743. gap: 4px;
  744. }
  745. .status-edit-button {
  746. padding: 2px;
  747. }
  748. .detail-hero {
  749. margin-bottom: 16px;
  750. padding: 16px;
  751. border-radius: 14px;
  752. background: linear-gradient(135deg, #eff6ff, #f8fbff);
  753. border: 1px solid #dbeafe;
  754. }
  755. .hero-title {
  756. font-size: 18px;
  757. font-weight: 700;
  758. color: #1d4ed8;
  759. }
  760. .hero-meta {
  761. display: flex;
  762. gap: 10px;
  763. flex-wrap: wrap;
  764. margin-top: 8px;
  765. font-size: 12px;
  766. color: #4b5563;
  767. }
  768. .section-block {
  769. margin-top: 16px;
  770. }
  771. .section-title {
  772. margin-bottom: 10px;
  773. font-size: 15px;
  774. font-weight: 600;
  775. color: #1f2937;
  776. }
  777. @media (max-width: 1200px) {
  778. .summary-grid {
  779. grid-template-columns: repeat(2, minmax(0, 1fr));
  780. }
  781. }
  782. @media (max-width: 768px) {
  783. .summary-grid {
  784. grid-template-columns: 1fr;
  785. }
  786. .card-header-row {
  787. flex-direction: column;
  788. align-items: flex-start;
  789. }
  790. }
  791. </style>