| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094 |
- <!-- 物联感知设备管理 -->
- <template>
- <div class="app-container equipment-page">
- <!-- 页头 -->
- <div class="page-head">
- <div class="page-head-titles">
- <div class="page-title">设备管理</div>
- </div>
- <el-button type="primary" icon="Plus" class="btn-primary" @click="handleAdd">新增设备</el-button>
- </div>
- <!-- KPI -->
- <div class="kpi-row">
- <el-card shadow="never" class="kpi">
- <div class="kpi-icon total"><el-icon><monitor /></el-icon></div>
- <div class="kpi-data">
- <div class="kpi-label">设备总数(台)</div>
- <div class="kpi-value">{{ statistics.total }}</div>
- </div>
- </el-card>
- <el-card shadow="never" class="kpi">
- <div class="kpi-icon online"><el-icon><circle-check /></el-icon></div>
- <div class="kpi-data">
- <div class="kpi-label">在线设备(台)</div>
- <div class="kpi-value">{{ statistics.online }}</div>
- </div>
- </el-card>
- <el-card shadow="never" class="kpi">
- <div class="kpi-icon offline"><el-icon><circle-close /></el-icon></div>
- <div class="kpi-data">
- <div class="kpi-label">离线设备(台)</div>
- <div class="kpi-value">{{ statistics.offline }}</div>
- </div>
- </el-card>
- <el-card shadow="never" class="kpi">
- <div class="kpi-icon alarm"><el-icon><warning /></el-icon></div>
- <div class="kpi-data">
- <div class="kpi-label">报警设备(台)</div>
- <div class="kpi-value">{{ statistics.alarm }}</div>
- </div>
- </el-card>
- </div>
- <!-- 设备台账 -->
- <el-card shadow="never" class="ledger">
- <div class="ledger-head">
- <div class="ledger-title">设备台账</div>
- <div class="ledger-sub">一览管网监测设备台账与运行状态</div>
- </div>
- <el-form ref="queryRef" :model="queryParams" :inline="true" class="filters">
- <el-form-item label="设备编码" prop="equipmentCode">
- <el-input
- v-model="queryParams.equipmentCode"
- placeholder="请输入"
- clearable
- class="input--code"
- @keyup.enter="handleQuery"
- />
- </el-form-item>
- <el-form-item label="设备名称" prop="equipmentName">
- <el-input
- v-model="queryParams.equipmentName"
- placeholder="请输入"
- clearable
- class="input--name"
- @keyup.enter="handleQuery"
- />
- </el-form-item>
- <el-form-item label="设备类型" prop="equipmentTypeId">
- <el-tree-select
- v-model="queryParams.equipmentTypeId"
- :data="equipmentTypeTree"
- node-key="id"
- :props="{ label: 'typeName', children: 'children', disabled: 'disabled' }"
- check-strictly
- :render-after-expand="false"
- clearable
- placeholder="请选择"
- class="select--type"
- />
- </el-form-item>
- <el-form-item label="在线状态" prop="onlineStatus">
- <el-select v-model="queryParams.onlineStatus" clearable placeholder="请选择" class="select--online">
- <el-option label="在线" :value="1" />
- <el-option label="离线" :value="0" />
- </el-select>
- </el-form-item>
- <el-form-item label="报警" prop="alarmOnly">
- <el-checkbox v-model="alarmOnly" @change="handleQuery" />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" class="btn-query" @click="handleQuery">查询</el-button>
- <el-button class="btn-reset" @click="resetQuery">重置</el-button>
- </el-form-item>
- </el-form>
- <div v-loading="loading" class="grid">
- <el-empty v-if="!equipmentList.length" description="暂无设备数据" />
- <div
- v-for="row in equipmentList"
- :key="row.equipmentId"
- class="card"
- :class="{ 'is-selected': selectedId === row.equipmentId, 'is-alarm': getRowAlarm(row.equipmentId) === 1 }"
- @click="handleView(row)"
- >
- <div class="card-top">
- <div class="card-name">{{ row.equipmentName || "未命名设备" }}</div>
- <div class="status-group">
- <span class="badge" :class="getRowOnline(row.equipmentId) === 1 ? 'online' : 'offline'">
- <span class="badge-dot"></span>
- <span class="badge-text">{{ getRowOnline(row.equipmentId) === 1 ? "在线" : "离线" }}</span>
- </span>
- <span v-if="getRowAlarm(row.equipmentId) === 1" class="badge alarm">
- <el-icon class="badge-icon"><warning-filled /></el-icon>
- <span class="badge-text">报警</span>
- </span>
- </div>
- </div>
- <div class="card-code">{{ row.equipmentCode }}</div>
- <div class="card-bottom">
- <span class="type-tag">
- {{ (row.equipmentTypeId && typeNameMap[row.equipmentTypeId]) || "未分类" }}
- </span>
- <div class="card-actions">
- <el-button link class="link-detail" @click.stop="handleView(row)">详情</el-button>
- <el-button link class="link-delete" @click.stop="handleDelete(row)">删除</el-button>
- </div>
- </div>
- </div>
- </div>
- <pagination
- v-show="total > 0"
- :total="total"
- v-model:page="queryParams.pageNum"
- v-model:limit="queryParams.pageSize"
- @pagination="getList"
- />
- </el-card>
- <!-- 设备详情弹窗 -->
- <el-dialog title="设备详情" v-model="detailOpen" width="800px" append-to-body class="detail-dialog">
- <template v-if="selectedDetail.equipmentInfo">
- <div class="dlg-summary">
- <div class="summary-name">{{ selectedDetail.equipmentInfo.equipmentName || "未命名设备" }}</div>
- <div class="summary-code">{{ selectedDetail.equipmentInfo.equipmentCode }}</div>
- <span class="summary-tag">
- {{ selectedDetail.typeInfo?.typeName || (selectedDetail.equipmentInfo.equipmentTypeId && typeNameMap[selectedDetail.equipmentInfo.equipmentTypeId]) || "未分类" }}
- </span>
- <div class="status-group">
- <span class="badge" :class="selectedDetail.statusInfo?.onlineStatus === 1 ? 'online' : 'offline'">
- <span class="badge-dot"></span>
- <span class="badge-text">{{ selectedDetail.statusInfo?.onlineStatus === 1 ? "在线" : "离线" }}</span>
- </span>
- <span v-if="selectedDetail.statusInfo?.alarmStatus === 1" class="badge alarm">
- <el-icon class="badge-icon"><warning-filled /></el-icon>
- <span class="badge-text">报警</span>
- </span>
- </div>
- </div>
- <el-descriptions :column="2" border size="small" class="dlg-grid">
- <el-descriptions-item label="设备编码">{{ selectedDetail.equipmentInfo.equipmentCode || "-" }}</el-descriptions-item>
- <el-descriptions-item label="设备名称">{{ selectedDetail.equipmentInfo.equipmentName || "-" }}</el-descriptions-item>
- <el-descriptions-item label="设备型号">{{ selectedDetail.equipmentInfo.equipmentModel || "-" }}</el-descriptions-item>
- <el-descriptions-item label="设备类型">{{ selectedDetail.typeInfo?.typeName || "-" }}</el-descriptions-item>
- <el-descriptions-item label="制造商">{{ selectedDetail.equipmentInfo.manufacturer || "-" }}</el-descriptions-item>
- <el-descriptions-item label="生产日期">{{ selectedDetail.equipmentInfo.productionDate || "-" }}</el-descriptions-item>
- <el-descriptions-item label="规格参数" :span="2">{{ selectedDetail.equipmentInfo.equipmentSpec || "-" }}</el-descriptions-item>
- <el-descriptions-item label="资产价值(元)">{{ selectedDetail.equipmentInfo.assetValue ?? "-" }}</el-descriptions-item>
- <el-descriptions-item label="使用年限(年)">{{ selectedDetail.equipmentInfo.useLife ?? "-" }}</el-descriptions-item>
- <el-descriptions-item label="资产状态">{{ currentStatusMeta(selectedDetail.statusInfo?.currentStatus).text }}</el-descriptions-item>
- <el-descriptions-item label="所属片区">{{ selectedDetail.equipmentInfo.district || "-" }}</el-descriptions-item>
- <el-descriptions-item label="设备位置" :span="2">{{ selectedDetail.equipmentInfo.equipmentLocation || "-" }}</el-descriptions-item>
- <el-descriptions-item label="经度">{{ selectedDetail.equipmentInfo.longitude ?? "-" }}</el-descriptions-item>
- <el-descriptions-item label="纬度">{{ selectedDetail.equipmentInfo.latitude ?? "-" }}</el-descriptions-item>
- <el-descriptions-item label="安装时间">{{ selectedDetail.equipmentInfo.installTime || "-" }}</el-descriptions-item>
- <el-descriptions-item label="维护人员">{{ selectedDetail.equipmentInfo.maintainer || "-" }}</el-descriptions-item>
- <el-descriptions-item label="联系电话">{{ selectedDetail.equipmentInfo.maintainerPhone || "-" }}</el-descriptions-item>
- <el-descriptions-item label="备注" :span="2">{{ selectedDetail.equipmentInfo.remark || "-" }}</el-descriptions-item>
- </el-descriptions>
- <div v-if="selectedDetail.equipmentInfo.equipmentImage" class="detail-image">
- <div class="field-label">设备图片</div>
- <el-image
- :src="resolveImageUrl(selectedDetail.equipmentInfo.equipmentImage)"
- :preview-src-list="[resolveImageUrl(selectedDetail.equipmentInfo.equipmentImage)]"
- preview-teleported
- fit="cover"
- class="field-media"
- />
- </div>
- </template>
- <el-empty v-else description="未选中设备" />
- <template #footer>
- <div class="dlg-footer">
- <el-button class="btn-ghost" @click="detailOpen = false">关 闭</el-button>
- <el-button type="primary" icon="Edit" @click="handleUpdate(selectedDetail.equipmentInfo)">编辑设备</el-button>
- </div>
- </template>
- </el-dialog>
- <!-- 新增/修改弹窗 -->
- <el-dialog :title="title" v-model="open" width="860px" append-to-body>
- <el-form ref="equipmentRef" :model="form" :rules="rules" label-width="110px">
- <el-row :gutter="16">
- <el-col :span="12">
- <el-form-item label="设备编码" prop="equipmentCode">
- <el-input v-model="form.equipmentCode" placeholder="请输入设备编码" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="设备名称" prop="equipmentName">
- <el-input v-model="form.equipmentName" placeholder="请输入设备名称" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="设备型号" prop="equipmentModel">
- <el-input v-model="form.equipmentModel" placeholder="请输入设备型号" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="设备类型" prop="equipmentTypeId">
- <el-tree-select
- v-model="form.equipmentTypeId"
- :data="equipmentTypeTree"
- node-key="id"
- :props="{ label: 'typeName', children: 'children', disabled: 'disabled' }"
- default-expand-all
- :render-after-expand="false"
- placeholder="请选择设备类型"
- style="width: 100%"
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="制造商" prop="manufacturer">
- <el-input v-model="form.manufacturer" placeholder="请输入制造商" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="生产日期" prop="productionDate">
- <el-date-picker
- v-model="form.productionDate"
- type="date"
- value-format="YYYY-MM-DD"
- placeholder="请选择生产日期"
- style="width: 100%"
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="资产状态" prop="currentStatus">
- <el-select v-model="form.currentStatus" placeholder="请选择资产状态" style="width: 100%">
- <el-option
- v-for="item in equipmentStatusOptions"
- :key="item.value"
- :label="item.label"
- :value="item.value"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="所属片区" prop="district">
- <el-select v-model="form.district" clearable placeholder="请选择所属片区" style="width: 100%">
- <el-option v-for="item in districtOptions" :key="item" :label="item" :value="item" />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="规格参数" prop="equipmentSpec">
- <el-input v-model="form.equipmentSpec" placeholder="请输入设备规格参数" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="资产价值(元)" prop="assetValue">
- <el-input-number v-model="form.assetValue" :precision="2" :step="100" style="width: 100%" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="使用年限(年)" prop="useLife">
- <el-input-number v-model="form.useLife" :precision="0" :step="1" style="width: 100%" />
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="设备位置" prop="equipmentLocation">
- <el-input v-model="form.equipmentLocation" placeholder="请输入设备位置" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="经度" prop="longitude">
- <el-input-number v-model="form.longitude" :precision="6" :step="0.000001" style="width: 100%" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="纬度" prop="latitude">
- <el-input-number v-model="form.latitude" :precision="6" :step="0.000001" style="width: 100%" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="安装时间" prop="installTime">
- <el-date-picker
- v-model="form.installTime"
- type="datetime"
- value-format="YYYY-MM-DD HH:mm:ss"
- placeholder="请选择安装时间"
- style="width: 100%"
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="维护人员" prop="maintainer">
- <el-input v-model="form.maintainer" placeholder="请输入维护人员" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="联系电话" prop="maintainerPhone">
- <el-input v-model="form.maintainerPhone" placeholder="请输入联系电话" />
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="备注" prop="remark">
- <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" />
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="设备图片" prop="equipmentImage">
- <image-upload v-model="form.equipmentImage" :limit="1" />
- </el-form-item>
- </el-col>
- </el-row>
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="submitForm">确 定</el-button>
- <el-button @click="cancel">取 消</el-button>
- </div>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup>
- import { computed, getCurrentInstance, onMounted, reactive, ref, toRefs } from "vue";
- import { useRoute } from "vue-router";
- import {
- addEquipment,
- delEquipment,
- getEquipment,
- getEquipmentDetail,
- getEquipmentStatusStatistics,
- listEquipment,
- listEquipmentStatusAll,
- listEquipmentTypeAll,
- updateEquipment,
- updateEquipmentStatus
- } from "@/api/pipeNetwork/pdeviceInform";
- const { proxy } = getCurrentInstance();
- // 数据范围:由系统菜单 query 配置 {"rootTypeId":1} 注入,对应 EquipmentTypeEnum 的 ROOT_ 大类
- const route = useRoute();
- const rootTypeId = ref(route.query.rootTypeId ? Number(route.query.rootTypeId) : undefined);
- const equipmentList = ref([]);
- const allTypeList = ref([]);
- const equipmentStatusList = ref([]);
- const loading = ref(false);
- const open = ref(false);
- const detailOpen = ref(false);
- const alarmOnly = ref(false);
- const total = ref(0);
- const title = ref("");
- const selectedDetail = ref({});
- const selectedId = ref("");
- const statistics = reactive({
- total: 0,
- online: 0,
- offline: 0,
- alarm: 0
- });
- // 资产状态:0-在用,1-维修,2-报废
- const equipmentStatusOptions = [
- { label: "在用", value: 0 },
- { label: "维修", value: 1 },
- { label: "报废", value: 2 }
- ];
- // 所属片区
- const districtOptions = ["城南", "城北", "太常"];
- const typeNameMap = computed(() => {
- return allTypeList.value.reduce((acc, item) => {
- acc[item.id] = item.typeName;
- return acc;
- }, {});
- });
- // 设备状态索引:equipmentId -> EquipmentStatus
- const statusMap = computed(() => {
- return equipmentStatusList.value.reduce((acc, item) => {
- acc[item.equipmentId] = item;
- return acc;
- }, {});
- });
- function getRowStatus(equipmentId) {
- return statusMap.value[equipmentId]?.currentStatus;
- }
- function getRowOnline(equipmentId) {
- return statusMap.value[equipmentId]?.onlineStatus;
- }
- function getRowAlarm(equipmentId) {
- return statusMap.value[equipmentId]?.alarmStatus;
- }
- // parentId 为 0(或空)表示父类
- function isTopLevelType(item) {
- const parentId = item?.parentId;
- return parentId === 0 || parentId === "0" || parentId === null || parentId === undefined || parentId === "";
- }
- function buildTypeTree(list) {
- const entries = [];
- const nodeIndex = {};
- list.forEach((item) => {
- const node = {
- id: item.id,
- typeName: item.typeName,
- disabled: isTopLevelType(item),
- children: []
- };
- entries.push({ item, node });
- if (item.id !== undefined && item.id !== null) {
- nodeIndex[String(item.id)] = node;
- }
- });
- const roots = [];
- const fallbackNode = { id: "__other__", typeName: "未分类", disabled: true, children: [] };
- entries.forEach(({ item, node }) => {
- if (isTopLevelType(item)) {
- roots.push(node);
- return;
- }
- const parent = nodeIndex[String(item.parentId)];
- if (parent && parent !== node) {
- parent.children.push(node);
- } else {
- fallbackNode.children.push(node);
- }
- });
- const prune = (node) => {
- if (node.children && node.children.length) {
- node.disabled = true;
- node.children.forEach(prune);
- } else {
- delete node.children;
- }
- return node;
- };
- const result = roots.map(prune);
- if (fallbackNode.children.length) {
- result.push(prune(fallbackNode));
- }
- return result;
- }
- // 全量类型构建树后按 rootTypeId 取对应大类分支:
- // 父节点展示真实大类名称(如“供水设备”),而非笼统的“未分类”
- const equipmentTypeTree = computed(() => {
- const tree = buildTypeTree(allTypeList.value || []);
- if (!rootTypeId.value) {
- return tree;
- }
- const branch = tree.find((node) => String(node.id) === String(rootTypeId.value));
- return branch ? [branch] : tree;
- });
- function currentStatusMeta(value) {
- const map = {
- 0: { text: "在用", type: "success" },
- 1: { text: "维修", type: "warning" },
- 2: { text: "报废", type: "danger" }
- };
- return map[value] || { text: "未知", type: "info" };
- }
- const baseUrl = import.meta.env.VITE_APP_BASE_API;
- function resolveImageUrl(url) {
- if (!url) return "";
- const value = String(url).trim();
- if (/^(https?:)?\/\//i.test(value) || value.startsWith("data:")) return value;
- return baseUrl + (value.startsWith("/") ? value : "/" + value);
- }
- const data = reactive({
- queryParams: {
- pageNum: 1,
- pageSize: 10,
- equipmentCode: undefined,
- equipmentName: undefined,
- equipmentTypeId: undefined,
- onlineStatus: undefined
- },
- form: {},
- rules: {
- equipmentCode: [{ required: true, message: "设备编码不能为空", trigger: "blur" }],
- equipmentName: [{ required: true, message: "设备名称不能为空", trigger: "blur" }],
- equipmentTypeId: [{ required: true, message: "设备类型不能为空", trigger: "change" }]
- }
- });
- const { queryParams, form, rules } = toRefs(data);
- function createEmptyForm() {
- return {
- equipmentId: undefined,
- equipmentCode: undefined,
- equipmentName: undefined,
- equipmentModel: undefined,
- equipmentSpec: undefined,
- manufacturer: undefined,
- productionDate: undefined,
- equipmentTypeId: undefined,
- district: undefined,
- currentStatus: 0,
- assetValue: undefined,
- useLife: undefined,
- longitude: undefined,
- latitude: undefined,
- equipmentLocation: undefined,
- installTime: undefined,
- maintainer: undefined,
- maintainerPhone: undefined,
- equipmentImage: undefined,
- remark: undefined
- };
- }
- function getList() {
- loading.value = true;
- return listEquipment({
- ...queryParams.value,
- rootTypeId: rootTypeId.value,
- alarmStatus: alarmOnly.value ? 1 : undefined
- })
- .then((response) => {
- equipmentList.value = response.records || [];
- total.value = response.total || 0;
- })
- .finally(() => {
- loading.value = false;
- });
- }
- function loadAssistData() {
- return Promise.all([listEquipmentTypeAll(), listEquipmentStatusAll(rootTypeId.value)]).then(([typeRes, statusRes]) => {
- allTypeList.value = typeRes.data || [];
- equipmentStatusList.value = statusRes.data || [];
- });
- }
- function loadStatistics() {
- return getEquipmentStatusStatistics(rootTypeId.value).then((res) => {
- const data = res.data || {};
- statistics.total = data.total || 0;
- statistics.online = data.online || 0;
- statistics.offline = data.offline || 0;
- statistics.alarm = data.alarm || 0;
- });
- }
- function reset() {
- form.value = createEmptyForm();
- proxy.resetForm("equipmentRef");
- }
- function handleQuery() {
- queryParams.value.pageNum = 1;
- getList();
- }
- function resetQuery() {
- proxy.resetForm("queryRef");
- alarmOnly.value = false;
- handleQuery();
- }
- function handleAdd() {
- reset();
- open.value = true;
- title.value = "新增设备";
- }
- function handleUpdate(row) {
- detailOpen.value = false;
- reset();
- const equipmentId = row?.equipmentId;
- if (!equipmentId) {
- return;
- }
- getEquipment(equipmentId).then((response) => {
- form.value = { ...createEmptyForm(), ...(response.data || {}) };
- form.value.equipmentTypeId =
- form.value.equipmentTypeId !== undefined &&
- form.value.equipmentTypeId !== null &&
- form.value.equipmentTypeId !== ""
- ? Number(form.value.equipmentTypeId)
- : undefined;
- form.value.currentStatus = getRowStatus(equipmentId) ?? 0;
- open.value = true;
- title.value = "修改设备";
- });
- }
- function handleView(row) {
- if (!row?.equipmentId) {
- return;
- }
- selectedId.value = row.equipmentId;
- return getEquipmentDetail(row.equipmentId).then((response) => {
- selectedDetail.value = response.data || {};
- detailOpen.value = true;
- });
- }
- function handleDelete(row) {
- if (!row?.equipmentId) {
- return;
- }
- proxy.$modal
- .confirm(`即将删除 ${row.equipmentName || "该设备"}(${row.equipmentCode}),确认继续?`)
- .then(() => delEquipment(row.equipmentId))
- .then(() => {
- proxy.$modal.msgSuccess("删除成功");
- if (selectedDetail.value?.equipmentInfo?.equipmentId === row.equipmentId) {
- selectedDetail.value = {};
- selectedId.value = "";
- detailOpen.value = false;
- }
- return Promise.all([getList(), loadAssistData(), loadStatistics()]);
- });
- }
- function cancel() {
- open.value = false;
- reset();
- }
- function submitForm() {
- proxy.$refs.equipmentRef.validate((valid) => {
- if (!valid) {
- return;
- }
- const isEdit = !!form.value.equipmentId;
- const equipmentId = form.value.equipmentId;
- const currentStatus = form.value.currentStatus;
- const request = isEdit ? updateEquipment(form.value) : addEquipment(form.value);
- request.then(() => {
- proxy.$modal.msgSuccess(isEdit ? "修改成功" : "新增成功");
- open.value = false;
- const tasks = [getList(), loadAssistData(), loadStatistics()];
- if (isEdit && currentStatus !== undefined && currentStatus !== null) {
- tasks.push(updateEquipmentStatus(equipmentId, currentStatus));
- }
- Promise.all(tasks).then(() => {
- if (isEdit) {
- handleView({ equipmentId });
- }
- });
- });
- });
- }
- onMounted(() => {
- reset();
- Promise.all([getList(), loadAssistData(), loadStatistics()]);
- });
- </script>
- <style scoped>
- .equipment-page {
- background: #f9f9f9;
- }
- /* 页头 */
- .page-head {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 20px;
- }
- .page-title {
- font-size: 20px;
- font-weight: 600;
- color: #0a1628;
- line-height: 1.4;
- }
- .btn-primary :deep(.el-icon) {
- font-size: 14px;
- }
- /* KPI */
- .kpi-row {
- display: flex;
- gap: 16px;
- margin-bottom: 20px;
- }
- .kpi {
- flex: 1 1 0;
- min-width: 0;
- border-radius: 12px;
- }
- .kpi :deep(.el-card__body) {
- display: flex;
- align-items: center;
- gap: 16px;
- padding: 20px;
- }
- .kpi-icon {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 44px;
- height: 44px;
- flex: 0 0 44px;
- border-radius: 10px;
- font-size: 22px;
- }
- .kpi-icon.total {
- background: #edf3fc;
- color: #014db2;
- }
- .kpi-icon.online {
- background: #e7f8f1;
- color: #10b981;
- }
- .kpi-icon.offline {
- background: #fef4e2;
- color: #f59e0b;
- }
- .kpi-icon.alarm {
- background: #fdecec;
- color: #ef4444;
- }
- .kpi-label {
- font-size: 13px;
- font-weight: 400;
- color: #6b7280;
- }
- .kpi-value {
- margin-top: 6px;
- font-size: 28px;
- font-weight: 700;
- line-height: 1;
- color: #0a1628;
- }
- /* 台账卡片 */
- .ledger {
- border-radius: 12px;
- }
- .ledger :deep(.el-card__body) {
- display: flex;
- flex-direction: column;
- gap: 14px;
- padding: 20px;
- }
- .ledger-head {
- display: flex;
- align-items: baseline;
- gap: 8px;
- }
- .ledger-title {
- font-size: 16px;
- font-weight: 600;
- color: #0a1628;
- }
- .ledger-sub {
- font-size: 12px;
- font-weight: 400;
- color: #9ca3af;
- }
- /* 筛选栏 */
- .filters {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- gap: 4px 16px;
- }
- .filters :deep(.el-form-item) {
- margin-right: 0;
- margin-bottom: 0;
- }
- .filters :deep(.el-form-item__label) {
- font-size: 12px;
- font-weight: 400;
- color: #6b7280;
- padding-right: 8px;
- }
- .input--code,
- .input--name {
- width: 150px;
- }
- .select--type {
- width: 150px;
- }
- .select--online {
- width: 120px;
- }
- .btn-query {
- height: 34px;
- padding: 0 16px;
- border-radius: 8px;
- font-size: 13px;
- font-weight: 500;
- }
- .btn-reset {
- height: 34px;
- padding: 0 16px;
- border-radius: 8px;
- font-size: 13px;
- font-weight: 400;
- }
- /* 设备卡片 */
- .grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
- gap: 12px;
- min-height: 200px;
- }
- .card {
- display: flex;
- flex-direction: column;
- gap: 10px;
- padding: 14px;
- border-radius: 10px;
- background: #fff;
- border: 1px solid #ebebed;
- cursor: pointer;
- transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
- }
- .card:hover {
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
- }
- .card.is-selected {
- background: #f5f9ff;
- border: 1.5px solid #014db2;
- }
- .card.is-alarm {
- border: 1.5px solid #fca5a5;
- }
- .card-top {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 8px;
- }
- .card-name {
- min-width: 0;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- font-size: 14px;
- font-weight: 500;
- color: #0a1628;
- }
- .status-group {
- display: flex;
- align-items: center;
- gap: 10px;
- flex-shrink: 0;
- }
- .badge {
- display: flex;
- align-items: center;
- gap: 5px;
- }
- .badge-dot {
- width: 6px;
- height: 6px;
- border-radius: 3px;
- flex-shrink: 0;
- }
- .badge-text {
- font-size: 12px;
- font-weight: 500;
- }
- .badge.online .badge-dot {
- background: #10b981;
- }
- .badge.online .badge-text {
- color: #10b981;
- }
- .badge.offline .badge-dot {
- background: #f59e0b;
- }
- .badge.offline .badge-text {
- color: #f59e0b;
- }
- .badge.alarm .badge-text {
- color: #ef4444;
- }
- .badge-icon {
- font-size: 13px;
- color: #ef4444;
- }
- .card-code {
- font-size: 12px;
- font-weight: 400;
- color: #9ca3af;
- }
- .card-bottom {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 8px;
- }
- .type-tag {
- display: inline-flex;
- align-items: center;
- padding: 3px 8px;
- border-radius: 4px;
- background: #edf3fc;
- font-size: 11px;
- font-weight: 500;
- color: #014db2;
- }
- .card-actions {
- display: flex;
- align-items: center;
- gap: 12px;
- }
- .card-actions :deep(.el-button) {
- height: auto;
- padding: 0;
- font-size: 12px;
- font-weight: 500;
- }
- .link-detail {
- color: #014db2;
- }
- .link-delete {
- color: #ef4444;
- }
- /* 详情弹窗 */
- .dlg-summary {
- display: flex;
- align-items: center;
- gap: 12px;
- flex-wrap: wrap;
- padding: 14px;
- border-radius: 8px;
- background: #f5f9ff;
- }
- .summary-name {
- font-size: 15px;
- font-weight: 500;
- color: #014db2;
- }
- .summary-code {
- font-size: 12px;
- font-weight: 400;
- color: #6b7280;
- }
- .summary-tag {
- display: inline-flex;
- align-items: center;
- padding: 3px 8px;
- border-radius: 4px;
- background: #fff;
- font-size: 11px;
- font-weight: 500;
- color: #014db2;
- }
- .dlg-grid {
- margin-top: 16px;
- }
- .detail-image {
- margin-top: 16px;
- }
- .field-label {
- margin-bottom: 6px;
- font-size: 12px;
- font-weight: 400;
- color: #6b7280;
- }
- .field-media {
- width: 320px;
- max-width: 100%;
- height: 180px;
- border-radius: 6px;
- border: 1px solid #e6e6eb;
- }
- .dlg-footer,
- .dialog-footer {
- display: flex;
- align-items: center;
- justify-content: flex-end;
- gap: 10px;
- }
- @media (max-width: 1200px) {
- .kpi-row {
- flex-wrap: wrap;
- }
- .kpi {
- flex: 1 1 45%;
- }
- }
- @media (max-width: 768px) {
- .kpi {
- flex: 1 1 100%;
- }
- .page-head {
- flex-direction: column;
- align-items: flex-start;
- gap: 12px;
- }
- }
- </style>
|