feat(line): 智电改造线路台账新增「线路数据」弹窗展示最近一次模拟量快照 - 后端新增GET /api/electricity-data/latest(按lineId取最大collect_time的全部模拟量行,RBAC device:list);前端electricityDataApi.getLatestByLine与ElectricityLatestSnapshot类型;台账页表格新增固定右侧「操作」列与「线路数据」按钮,点击弹出720px弹窗显示线路信息(线路/编码/采集时间)及模拟量表格(模拟量名称/类型/数值/单位),无数据时显示空态提示
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com
This commit is contained in:
1 parent
c92fc7f298
commit
c8a4330f3b
3 files changed
+153
-2
No files matched your search
@@ -272,4 +272,29 @@ export const electricGatewayApi = {
|
|||||||
|
|
||||||
sync: () =>
|
sync: () =>
|
||||||
request.post<IccGatewaySyncResult>('/electric-gateways/sync'),
|
request.post<IccGatewaySyncResult>('/electric-gateways/sync'),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 线路用电(ICC 模拟量) ====================
|
||||||
|
|
||||||
|
export interface ElectricityAnalogRow {
|
||||||
|
analogType?: string;
|
||||||
|
analogName?: string;
|
||||||
|
analogUnit?: string;
|
||||||
|
analogValue?: number | string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ElectricityLatestSnapshot {
|
||||||
|
lineId: number;
|
||||||
|
lineCode?: string;
|
||||||
|
lineName?: string;
|
||||||
|
imei?: string;
|
||||||
|
customerId?: number;
|
||||||
|
collectTime?: string | null;
|
||||||
|
hasData: boolean;
|
||||||
|
list: ElectricityAnalogRow[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const electricityDataApi = {
|
||||||
|
getLatestByLine: (lineId: number) =>
|
||||||
|
request.get<ElectricityLatestSnapshot>('/electricity-data/latest', { params: { lineId } }),
|
||||||
};
|
};
|
||||||
@@ -140,6 +140,11 @@
|
|||||||
<template v-else-if="column.key === 'syncedAt'">
|
<template v-else-if="column.key === 'syncedAt'">
|
||||||
<span>{{ formatTime(record.syncedAt) }}</span>
|
<span>{{ formatTime(record.syncedAt) }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="column.key === 'action'">
|
||||||
|
<a-button type="link" size="small" @click="handleViewData(record)">
|
||||||
|
线路数据
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #emptyText>
|
<template #emptyText>
|
||||||
@@ -149,9 +154,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<a-modal
|
||||||
|
v-model:open="analogModal.open"
|
||||||
|
:title="analogModal.title"
|
||||||
|
:width="720"
|
||||||
|
:footer="null"
|
||||||
|
>
|
||||||
|
<div v-if="analogModal.loading" class="modal-tip">加载中...</div>
|
||||||
|
<div v-else-if="!analogModal.snapshot || !analogModal.snapshot.hasData" class="modal-tip">暂无模拟量数据,可点击「同步线路」或等待定时同步后查看</div>
|
||||||
|
<template v-else>
|
||||||
|
<div class="modal-meta">
|
||||||
|
<span>线路:{{ analogModal.snapshot.lineName || '-' }}</span>
|
||||||
|
<span>编码:{{ analogModal.snapshot.lineCode || '-' }}</span>
|
||||||
|
<span>采集时间:{{ formatTime(analogModal.snapshot.collectTime) }}</span>
|
||||||
|
</div>
|
||||||
|
<a-table
|
||||||
|
:columns="analogColumns"
|
||||||
|
:data-source="analogModal.snapshot.list"
|
||||||
|
:pagination="false"
|
||||||
|
row-key="analogType"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, onMounted, computed } from 'vue';
|
import { ref, reactive, onMounted, computed } from 'vue';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
@@ -162,8 +189,10 @@ import {
|
|||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
import {
|
import {
|
||||||
electricLineApi,
|
electricLineApi,
|
||||||
|
electricityDataApi,
|
||||||
type ElectricLine,
|
type ElectricLine,
|
||||||
type ElectricLineQuery,
|
type ElectricLineQuery,
|
||||||
|
type ElectricityLatestSnapshot,
|
||||||
} from '@/api/device';
|
} from '@/api/device';
|
||||||
import { customerApi, type CustomerInfo } from '@/api/customer';
|
import { customerApi, type CustomerInfo } from '@/api/customer';
|
||||||
import { useTableScroll } from '@/composables/useTableScroll';
|
import { useTableScroll } from '@/composables/useTableScroll';
|
||||||
@@ -172,6 +201,25 @@ const { wrapperRef, tableHeight } = useTableScroll();
|
|||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const syncing = ref(false);
|
const syncing = ref(false);
|
||||||
|
const analogModal = reactive<{
|
||||||
|
open: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
title: string;
|
||||||
|
snapshot: ElectricityLatestSnapshot | null;
|
||||||
|
}>({
|
||||||
|
open: false,
|
||||||
|
loading: false,
|
||||||
|
title: '线路模拟数据',
|
||||||
|
snapshot: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const analogColumns = [
|
||||||
|
{ title: '模拟量名称', dataIndex: 'analogName', key: 'analogName', ellipsis: true },
|
||||||
|
{ title: '类型', dataIndex: 'analogType', key: 'analogType', ellipsis: true, width: 100 },
|
||||||
|
{ title: '数值', dataIndex: 'analogValue', key: 'analogValue', align: 'center' as const, width: 120 },
|
||||||
|
{ title: '单位', dataIndex: 'analogUnit', key: 'analogUnit', align: 'center' as const, width: 80 },
|
||||||
|
];
|
||||||
|
|
||||||
const dataSource = ref<ElectricLine[]>([]);
|
const dataSource = ref<ElectricLine[]>([]);
|
||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
|
|
||||||
@@ -197,6 +245,7 @@ const columns = [
|
|||||||
{ title: '生产日期', dataIndex: 'produceDate', key: 'produceDate', ellipsis: true, width: 120 },
|
{ title: '生产日期', dataIndex: 'produceDate', key: 'produceDate', ellipsis: true, width: 120 },
|
||||||
{ title: '状态', dataIndex: 'status', key: 'status', align: 'center' as const, width: 80 },
|
{ title: '状态', dataIndex: 'status', key: 'status', align: 'center' as const, width: 80 },
|
||||||
{ title: '同步时间', dataIndex: 'syncedAt', key: 'syncedAt', ellipsis: true, width: 170 },
|
{ title: '同步时间', dataIndex: 'syncedAt', key: 'syncedAt', ellipsis: true, width: 170 },
|
||||||
|
{ title: '操作', key: 'action', align: 'center' as const, fixed: 'right' as const, width: 100 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const pagination = computed(() => ({
|
const pagination = computed(() => ({
|
||||||
@@ -280,6 +329,25 @@ async function handleSync() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleViewData(record: ElectricLine) {
|
||||||
|
analogModal.open = true;
|
||||||
|
analogModal.loading = true;
|
||||||
|
analogModal.title = `线路数据 - ${record.lineName || record.lineCode}`;
|
||||||
|
analogModal.snapshot = null;
|
||||||
|
try {
|
||||||
|
const res: any = await electricityDataApi.getLatestByLine(record.id);
|
||||||
|
if (res.code === 200) {
|
||||||
|
analogModal.snapshot = res.data;
|
||||||
|
} else {
|
||||||
|
message.error(res.message || '获取模拟数据失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
message.error('获取模拟数据失败,请稍后重试');
|
||||||
|
} finally {
|
||||||
|
analogModal.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function formatTime(val?: string) {
|
function formatTime(val?: string) {
|
||||||
if (!val) return '-';
|
if (!val) return '-';
|
||||||
const d = new Date(val);
|
const d = new Date(val);
|
||||||
@@ -517,4 +585,22 @@ function formatTime(val?: string) {
|
|||||||
background: #136bfb;
|
background: #136bfb;
|
||||||
border-color: #136bfb;
|
border-color: #136bfb;
|
||||||
}
|
}
|
||||||
|
.modal-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px 24px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #5b6b7e;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.modal-meta span {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #0d1c2c;
|
||||||
|
}
|
||||||
|
.modal-tip {
|
||||||
|
padding: 40px 0;
|
||||||
|
text-align: center;
|
||||||
|
color: #5b6b7e;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
@@ -1,14 +1,54 @@
|
|||||||
import { Controller, Post, Inject } from "@midwayjs/core";
|
import { Controller, Post, Get, Query, Inject } from "@midwayjs/core";
|
||||||
import { Context } from "@midwayjs/koa";
|
import { Context } from "@midwayjs/koa";
|
||||||
|
import { InjectRepository } from "@midwayjs/sequelize";
|
||||||
|
import { Repository } from "sequelize-typescript";
|
||||||
import { IccAnalogSyncService } from "../service/icc_analog_sync.service";
|
import { IccAnalogSyncService } from "../service/icc_analog_sync.service";
|
||||||
|
import ElectricityData from "../entity/electricity_data.entity";
|
||||||
|
|
||||||
@Controller("/api/electricity-data")
|
@Controller("/api/electricity-data")
|
||||||
export class ElectricityDataController {
|
export class ElectricityDataController {
|
||||||
@Inject() ctx!: Context;
|
@Inject() ctx!: Context;
|
||||||
@Inject() iccAnalogSyncService!: IccAnalogSyncService;
|
@Inject() iccAnalogSyncService!: IccAnalogSyncService;
|
||||||
|
@InjectRepository(ElectricityData)
|
||||||
|
electricityDataRepo!: Repository<ElectricityData>;
|
||||||
|
|
||||||
@Post("/sync") async sync() {
|
@Post("/sync") async sync() {
|
||||||
const result = await this.iccAnalogSyncService.syncFromIcc(this.ctx);
|
const result = await this.iccAnalogSyncService.syncFromIcc(this.ctx);
|
||||||
return { code: 200, message: "模拟量数据同步完成", data: result };
|
return { code: 200, message: "模拟量数据同步完成", data: result };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 查询某线路最近一次同步的模拟量快照(取最大 collect_time 的全部模拟量行) */
|
||||||
|
@Get("/latest") async latest(@Query("lineId") lineId?: string) {
|
||||||
|
const id = Number(lineId);
|
||||||
|
if (!id || Number.isNaN(id)) {
|
||||||
|
return { code: 400, message: "缺少有效的 lineId 参数" };
|
||||||
|
}
|
||||||
|
const latest = await this.electricityDataRepo.findOne({
|
||||||
|
where: { lineId: id },
|
||||||
|
order: [["collectTime", "DESC"]],
|
||||||
|
attributes: ["lineCode", "lineName", "imei", "customerId", "collectTime"],
|
||||||
|
});
|
||||||
|
if (!latest) {
|
||||||
|
return { code: 200, message: "该线路暂无模拟量数据", data: { lineId: id, hasData: false, collectTime: null, list: [] } };
|
||||||
|
}
|
||||||
|
const rows = await this.electricityDataRepo.findAll({
|
||||||
|
where: { lineId: id, collectTime: latest.getDataValue("collectTime") as Date },
|
||||||
|
order: [["analogType", "ASC"]],
|
||||||
|
attributes: ["analogType", "analogName", "analogUnit", "analogValue"],
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: "ok",
|
||||||
|
data: {
|
||||||
|
lineId: id,
|
||||||
|
lineCode: latest.getDataValue("lineCode"),
|
||||||
|
lineName: latest.getDataValue("lineName"),
|
||||||
|
imei: latest.getDataValue("imei"),
|
||||||
|
customerId: latest.getDataValue("customerId"),
|
||||||
|
collectTime: latest.getDataValue("collectTime"),
|
||||||
|
hasData: true,
|
||||||
|
list: rows,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user