feat: 添加设备日志查询功能
- 新增设备日志查询 API - 实现事件、功能、上下线日志查询 - 优化属性日志查询接口 - 修复事件和功能模拟相关问题
This commit is contained in:
parent
8d0b69cd4c
commit
2925fce7fb
|
@ -34,6 +34,46 @@ export function deviceInfo(id: ID) {
|
||||||
return requestClient.get<DeviceVO>(`/device/device/${id}`);
|
return requestClient.get<DeviceVO>(`/device/device/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询设备日志数据(属性/事件)
|
||||||
|
* @param params
|
||||||
|
* @returns 设备日志数据
|
||||||
|
*/
|
||||||
|
export function deviceLogList(params?: DeviceQuery) {
|
||||||
|
return requestClient.post('/device/device/data/field', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询设备功能操作日志列表
|
||||||
|
* @param params
|
||||||
|
* @returns 设备功能操作日志
|
||||||
|
*/
|
||||||
|
export function functionLogList(params?: DeviceQuery) {
|
||||||
|
return requestClient.get('/device/functionLog/list', {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询设备上下线日志列表
|
||||||
|
* @param params
|
||||||
|
* @returns 设备上下线日志
|
||||||
|
*/
|
||||||
|
export function onlineLogList(params?: DeviceQuery) {
|
||||||
|
return requestClient.get('/device/onlineLog/list', {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询设备日志数据(上报/下发)
|
||||||
|
* @param params
|
||||||
|
* @returns 设备日志数据
|
||||||
|
*/
|
||||||
|
export function deviceUpDownLogList(params?: DeviceQuery) {
|
||||||
|
return requestClient.post('/device/device/log', params);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增设备
|
* 新增设备
|
||||||
* @param data
|
* @param data
|
||||||
|
|
|
@ -4,14 +4,15 @@ import { onMounted, ref } from 'vue';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
DatePicker,
|
DatePicker,
|
||||||
Empty,
|
|
||||||
Modal,
|
Modal,
|
||||||
Select,
|
Select,
|
||||||
Space,
|
Space,
|
||||||
Table,
|
Table,
|
||||||
Tag,
|
Tag,
|
||||||
} from 'ant-design-vue';
|
} from 'ant-design-vue';
|
||||||
import { Dayjs } from 'dayjs';
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
|
import { deviceUpDownLogList } from '#/api/device/device';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
|
@ -32,15 +33,15 @@ const pagination = ref({ current: 1, pageSize: 10, total: 0 });
|
||||||
// 日志类型选项
|
// 日志类型选项
|
||||||
const logTypeOptions = [
|
const logTypeOptions = [
|
||||||
{ label: '全部', value: '' },
|
{ label: '全部', value: '' },
|
||||||
{ label: '上报', value: 'upload' },
|
{ label: '上报', value: 'up' },
|
||||||
{ label: '下发', value: 'download' },
|
{ label: '下发', value: 'down' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// 表格列
|
// 表格列
|
||||||
const columns = [
|
const columns = [
|
||||||
|
{ title: '时间', dataIndex: 'timestamp', key: 'timestamp', width: 200 },
|
||||||
{ title: '类型', dataIndex: 'logType', key: 'logType', width: 120 },
|
{ title: '类型', dataIndex: 'logType', key: 'logType', width: 120 },
|
||||||
{ title: '名称内容', dataIndex: 'content', key: 'content', ellipsis: true },
|
{ title: '名称内容', dataIndex: 'content', key: 'content', ellipsis: true },
|
||||||
{ title: '时间', dataIndex: 'timestamp', key: 'timestamp', width: 200 },
|
|
||||||
{ title: '操作', key: 'action', width: 100 },
|
{ title: '操作', key: 'action', width: 100 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -49,7 +50,13 @@ const viewVisible = ref(false);
|
||||||
const viewRecord = ref<any>(null);
|
const viewRecord = ref<any>(null);
|
||||||
|
|
||||||
const openView = (record: any) => {
|
const openView = (record: any) => {
|
||||||
viewRecord.value = record;
|
let content = '';
|
||||||
|
try {
|
||||||
|
content = JSON.stringify(JSON.parse(record.content), null, 2);
|
||||||
|
} catch {
|
||||||
|
content = record.content;
|
||||||
|
}
|
||||||
|
viewRecord.value = content;
|
||||||
viewVisible.value = true;
|
viewVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -62,53 +69,52 @@ const closeView = () => {
|
||||||
const loadList = async () => {
|
const loadList = async () => {
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const [start, end] = timeRange.value || [];
|
let startTime = null;
|
||||||
|
let endTime = null;
|
||||||
|
if (timeRange.value?.length === 2) {
|
||||||
|
startTime = timeRange.value[0].valueOf();
|
||||||
|
endTime = timeRange.value[1].valueOf();
|
||||||
|
}
|
||||||
const params = {
|
const params = {
|
||||||
deviceId: props.deviceId,
|
deviceKey: props.deviceInfo.deviceKey,
|
||||||
startTime: start?.format('YYYY-MM-DD HH:mm:ss'),
|
productKey: props.deviceInfo.productObj.productKey,
|
||||||
endTime: end?.format('YYYY-MM-DD HH:mm:ss'),
|
// direction: logType.value,
|
||||||
logType: logType.value || undefined,
|
orderType: 2,
|
||||||
pageNo: pagination.value.current,
|
current: pagination.value.current,
|
||||||
pageSize: pagination.value.pageSize,
|
size: pagination.value.pageSize,
|
||||||
|
listWhere: [],
|
||||||
};
|
};
|
||||||
|
if (startTime && endTime) {
|
||||||
|
params.startTime = startTime;
|
||||||
|
params.endTime = endTime;
|
||||||
|
}
|
||||||
|
if (logType.value) {
|
||||||
|
params.listWhere.push({
|
||||||
|
field: 'direction',
|
||||||
|
val: logType.value,
|
||||||
|
operator: 'eq',
|
||||||
|
valType: 'string',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// if (messageId.value) {
|
||||||
|
// params.listWhere.push({
|
||||||
|
// field: 'msg_id',
|
||||||
|
// val: messageId.value?.trim(),
|
||||||
|
// operator: 'eq',
|
||||||
|
// valType: 'string',
|
||||||
|
// });
|
||||||
|
// }
|
||||||
console.log('query logs with', params);
|
console.log('query logs with', params);
|
||||||
|
const data = await deviceUpDownLogList(params);
|
||||||
// TODO: 替换为真实接口
|
dataSource.value = data?.records.map((item: any) => {
|
||||||
const mock = [
|
return {
|
||||||
{
|
messageId: item.msg_id,
|
||||||
id: 1,
|
logType: item.direction,
|
||||||
timestamp: '2025-01-20 10:30:15',
|
timestamp: dayjs(item.time).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
logType: '上报',
|
content: item.log_data,
|
||||||
content: '设备启动成功,固件版本:v1.0.0',
|
};
|
||||||
},
|
});
|
||||||
{
|
pagination.value.total = data.total;
|
||||||
id: 2,
|
|
||||||
timestamp: '2025-01-20 10:30:20',
|
|
||||||
logType: '下发',
|
|
||||||
content: '{"switch": "on"}',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
timestamp: '2025-01-20 10:30:25',
|
|
||||||
logType: '上报',
|
|
||||||
content: '网络连接不稳定,重试次数:3',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
timestamp: '2025-01-20 10:30:30',
|
|
||||||
logType: '下发',
|
|
||||||
content:
|
|
||||||
'功能执行失败,错误码:E001,错误信息:参数无效功能执行失败,错误码:E001,错误信息:参数无效功能执行失败,错误码:E001,错误信息:参数无效功能执行失败,错误码:E001,错误信息:参数无效',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
timestamp: '2025-01-20 10:30:35',
|
|
||||||
logType: '上报',
|
|
||||||
content: '调试信息:设备响应时间 150ms',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
dataSource.value = mock;
|
|
||||||
pagination.value.total = mock.length;
|
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
@ -141,7 +147,7 @@ onMounted(() => {
|
||||||
<div class="log-management">
|
<div class="log-management">
|
||||||
<!-- 查询区 -->
|
<!-- 查询区 -->
|
||||||
<div class="query-bar">
|
<div class="query-bar">
|
||||||
<Space>
|
<Space wrap>
|
||||||
<span>日志类型:</span>
|
<span>日志类型:</span>
|
||||||
<Select
|
<Select
|
||||||
v-model:value="logType"
|
v-model:value="logType"
|
||||||
|
@ -173,8 +179,11 @@ onMounted(() => {
|
||||||
>
|
>
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'logType'">
|
<template v-if="column.key === 'logType'">
|
||||||
<Tag :color="record.logType === '上报' ? 'blue' : 'green'">
|
<Tag :color="record.logType === 'up' ? 'blue' : 'green'">
|
||||||
{{ record.logType }}
|
{{
|
||||||
|
logTypeOptions.find((item) => item.value === record.logType)
|
||||||
|
?.label
|
||||||
|
}}
|
||||||
</Tag>
|
</Tag>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="column.key === 'action'">
|
<template v-if="column.key === 'action'">
|
||||||
|
@ -185,10 +194,6 @@ onMounted(() => {
|
||||||
</template>
|
</template>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
<div v-if="!loading && dataSource.length === 0" class="empty-wrap">
|
|
||||||
<Empty />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 查看内容弹窗 -->
|
<!-- 查看内容弹窗 -->
|
||||||
<Modal
|
<Modal
|
||||||
v-model:open="viewVisible"
|
v-model:open="viewVisible"
|
||||||
|
@ -197,22 +202,7 @@ onMounted(() => {
|
||||||
@cancel="closeView"
|
@cancel="closeView"
|
||||||
:footer="null"
|
:footer="null"
|
||||||
>
|
>
|
||||||
<div class="log-detail">
|
<pre class="json-view">{{ viewRecord }}</pre>
|
||||||
<div class="detail-item">
|
|
||||||
<span class="label">类型:</span>
|
|
||||||
<Tag :color="viewRecord?.logType === '上报' ? 'blue' : 'green'">
|
|
||||||
{{ viewRecord?.logType }}
|
|
||||||
</Tag>
|
|
||||||
</div>
|
|
||||||
<div class="detail-item">
|
|
||||||
<span class="label">时间:</span>
|
|
||||||
<span>{{ viewRecord?.timestamp }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="detail-item">
|
|
||||||
<span class="label">内容:</span>
|
|
||||||
<pre class="content-view">{{ viewRecord?.content }}</pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -253,4 +243,15 @@ onMounted(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.json-view {
|
||||||
|
max-height: 420px;
|
||||||
|
padding: 12px;
|
||||||
|
margin: 0;
|
||||||
|
overflow: auto;
|
||||||
|
word-break: break-all;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
background: #fafafa;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -4,6 +4,9 @@ import { computed } from 'vue';
|
||||||
import { TabPane, Tabs } from 'ant-design-vue';
|
import { TabPane, Tabs } from 'ant-design-vue';
|
||||||
|
|
||||||
import EventsPanel from './running/EventsPanel.vue';
|
import EventsPanel from './running/EventsPanel.vue';
|
||||||
|
import FunctionPanel from './running/FunctionPanel.vue';
|
||||||
|
import OnlineStatusPanel from './running/OnlineStatusPanel.vue';
|
||||||
|
import PropertyPanel from './running/PropertyPanel.vue';
|
||||||
import RealtimePanel from './running/RealtimePanel.vue';
|
import RealtimePanel from './running/RealtimePanel.vue';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -17,23 +20,35 @@ const props = defineProps<Props>();
|
||||||
const metadata = computed(() => {
|
const metadata = computed(() => {
|
||||||
try {
|
try {
|
||||||
const raw = props.deviceInfo?.productObj?.metadata;
|
const raw = props.deviceInfo?.productObj?.metadata;
|
||||||
if (!raw) return { properties: [], propertyGroups: [], events: [] } as any;
|
if (!raw)
|
||||||
|
return {
|
||||||
|
properties: [],
|
||||||
|
propertyGroups: [],
|
||||||
|
events: [],
|
||||||
|
functions: [],
|
||||||
|
} as any;
|
||||||
const obj = JSON.parse(raw || '{}');
|
const obj = JSON.parse(raw || '{}');
|
||||||
return {
|
return {
|
||||||
properties: obj?.properties || [],
|
properties: obj?.properties || [],
|
||||||
propertyGroups: obj?.propertyGroups || [],
|
propertyGroups: obj?.propertyGroups || [],
|
||||||
events: obj?.events || [],
|
events: obj?.events || [],
|
||||||
|
functions: obj?.functions || [],
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('parse metadata error', error);
|
console.warn('parse metadata error', error);
|
||||||
return { properties: [], propertyGroups: [], events: [] } as any;
|
return {
|
||||||
|
properties: [],
|
||||||
|
propertyGroups: [],
|
||||||
|
events: [],
|
||||||
|
functions: [],
|
||||||
|
} as any;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="running-status">
|
<div class="running-status">
|
||||||
<Tabs type="card">
|
<Tabs type="card" :destroy-inactive-tab-pane="true">
|
||||||
<TabPane key="realtime" tab="实时数据">
|
<TabPane key="realtime" tab="实时数据">
|
||||||
<RealtimePanel
|
<RealtimePanel
|
||||||
:device-id="props.deviceId"
|
:device-id="props.deviceId"
|
||||||
|
@ -41,8 +56,33 @@ const metadata = computed(() => {
|
||||||
:device-info="deviceInfo"
|
:device-info="deviceInfo"
|
||||||
/>
|
/>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
|
<TabPane key="property" tab="属性">
|
||||||
|
<PropertyPanel
|
||||||
|
:device-id="props.deviceId"
|
||||||
|
:metadata="metadata"
|
||||||
|
:device-info="deviceInfo"
|
||||||
|
/>
|
||||||
|
</TabPane>
|
||||||
<TabPane key="events" tab="事件">
|
<TabPane key="events" tab="事件">
|
||||||
<EventsPanel :device-id="props.deviceId" :metadata="metadata" />
|
<EventsPanel
|
||||||
|
:device-id="props.deviceId"
|
||||||
|
:metadata="metadata"
|
||||||
|
:device-info="deviceInfo"
|
||||||
|
/>
|
||||||
|
</TabPane>
|
||||||
|
<TabPane key="functions" tab="功能">
|
||||||
|
<FunctionPanel
|
||||||
|
:device-id="props.deviceId"
|
||||||
|
:metadata="metadata"
|
||||||
|
:device-info="deviceInfo"
|
||||||
|
/>
|
||||||
|
</TabPane>
|
||||||
|
<TabPane key="online" tab="上下线">
|
||||||
|
<OnlineStatusPanel
|
||||||
|
:device-id="props.deviceId"
|
||||||
|
:metadata="metadata"
|
||||||
|
:device-info="deviceInfo"
|
||||||
|
/>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { computed, onMounted, ref } from 'vue';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
DatePicker,
|
DatePicker,
|
||||||
Empty,
|
|
||||||
Input,
|
Input,
|
||||||
Modal,
|
Modal,
|
||||||
Select,
|
Select,
|
||||||
|
@ -13,9 +12,12 @@ import {
|
||||||
} from 'ant-design-vue';
|
} from 'ant-design-vue';
|
||||||
import dayjs, { Dayjs } from 'dayjs';
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
|
import { deviceLogList } from '#/api/device/device';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
metadata: any;
|
metadata: any;
|
||||||
|
deviceInfo: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
|
@ -41,18 +43,21 @@ const eventNameMap = computed<Record<string, string>>(() => {
|
||||||
|
|
||||||
// 下拉选项(来自物模型)
|
// 下拉选项(来自物模型)
|
||||||
const eventOptions = computed(() => {
|
const eventOptions = computed(() => {
|
||||||
return (props.metadata?.events || []).map((e: any) => ({
|
return [
|
||||||
|
{ label: '全部', value: '' },
|
||||||
|
...(props.metadata?.events || []).map((e: any) => ({
|
||||||
label: e.name || e.id,
|
label: e.name || e.id,
|
||||||
value: e.id,
|
value: e.id,
|
||||||
}));
|
})),
|
||||||
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
// 表格列
|
// 表格列
|
||||||
const columns = [
|
const columns = [
|
||||||
{ title: '消息ID', dataIndex: 'messageId', key: 'messageId', width: 220 },
|
{ title: '消息ID', dataIndex: 'messageId', key: 'messageId', width: 140 },
|
||||||
{ title: '名称', dataIndex: 'eventName', key: 'eventName', width: 160 },
|
{ title: '时间', dataIndex: 'timestamp', key: 'timestamp', width: 160 },
|
||||||
{ title: '标识', dataIndex: 'eventId', key: 'eventId', width: 160 },
|
{ title: '事件', dataIndex: 'eventName', key: 'eventName', width: 200 },
|
||||||
{ title: '时间', dataIndex: 'timestamp', key: 'timestamp', width: 200 },
|
{ title: '内容', dataIndex: 'content', key: 'content', ellipsis: true },
|
||||||
{ title: '操作', key: 'action', width: 100 },
|
{ title: '操作', key: 'action', width: 100 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -61,7 +66,13 @@ const viewVisible = ref(false);
|
||||||
const viewRecord = ref<any>(null);
|
const viewRecord = ref<any>(null);
|
||||||
|
|
||||||
const openView = (record: any) => {
|
const openView = (record: any) => {
|
||||||
viewRecord.value = record;
|
let content = '';
|
||||||
|
try {
|
||||||
|
content = JSON.stringify(JSON.parse(record.content), null, 2);
|
||||||
|
} catch {
|
||||||
|
content = record.content;
|
||||||
|
}
|
||||||
|
viewRecord.value = content;
|
||||||
viewVisible.value = true;
|
viewVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -74,30 +85,53 @@ const closeView = () => {
|
||||||
const loadList = async () => {
|
const loadList = async () => {
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const [start, end] = timeRange.value || [];
|
let startTime = null;
|
||||||
const params = {
|
let endTime = null;
|
||||||
deviceId: props.deviceId,
|
if (timeRange.value?.length === 2) {
|
||||||
startTime: start?.format('YYYY-MM-DD HH:mm:ss'),
|
startTime = timeRange.value[0].valueOf();
|
||||||
endTime: end?.format('YYYY-MM-DD HH:mm:ss'),
|
endTime = timeRange.value[1].valueOf();
|
||||||
messageId: messageId.value?.trim() || undefined,
|
}
|
||||||
eventId: selectedEventId.value || undefined,
|
const params: any = {
|
||||||
pageNo: pagination.value.current,
|
deviceKey: props.deviceInfo.deviceKey,
|
||||||
pageSize: pagination.value.pageSize,
|
productKey: props.deviceInfo.productObj.productKey,
|
||||||
|
fields: selectedEventId.value || undefined,
|
||||||
|
orderType: 2,
|
||||||
|
current: pagination.value.current,
|
||||||
|
size: pagination.value.pageSize,
|
||||||
|
metaDataType: 'event',
|
||||||
|
listWhere: [],
|
||||||
};
|
};
|
||||||
console.log('query events with', params);
|
if (startTime && endTime) {
|
||||||
|
params.startTime = startTime;
|
||||||
// TODO: 替换为真实接口
|
params.endTime = endTime;
|
||||||
const eid =
|
}
|
||||||
selectedEventId.value || props.metadata?.events?.[0]?.id || 'temple';
|
if (messageId.value) {
|
||||||
const mock = Array.from({ length: 5 }).map((_, i) => ({
|
params.listWhere.push({
|
||||||
messageId: `${Date.now()}_${i}`,
|
field: 'msg_id',
|
||||||
eventId: eid,
|
val: messageId.value?.trim(),
|
||||||
eventName: eventNameMap.value[eid] || '事件',
|
operator: 'eq',
|
||||||
payload: { level: 'info', msg: '模拟事件' },
|
valType: 'string',
|
||||||
timestamp: dayjs().format('YYYY-MM-DD HH:mm:ss.SSS'),
|
});
|
||||||
}));
|
}
|
||||||
dataSource.value = mock;
|
if (selectedEventId.value) {
|
||||||
pagination.value.total = mock.length;
|
params.listWhere.push({
|
||||||
|
field: 'eventId',
|
||||||
|
val: selectedEventId.value?.trim(),
|
||||||
|
operator: 'eq',
|
||||||
|
valType: 'string',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const data = await deviceLogList(params);
|
||||||
|
console.log('eventNameMap', eventNameMap);
|
||||||
|
dataSource.value = data.records.map((item: any) => {
|
||||||
|
return {
|
||||||
|
messageId: item.msg_id,
|
||||||
|
eventName: eventNameMap.value[item.eventId],
|
||||||
|
timestamp: dayjs(item.time).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
content: item.raw_data,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
pagination.value.total = data.total;
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
@ -131,7 +165,7 @@ onMounted(() => {
|
||||||
<div class="events-panel">
|
<div class="events-panel">
|
||||||
<!-- 查询区 -->
|
<!-- 查询区 -->
|
||||||
<div class="query-bar">
|
<div class="query-bar">
|
||||||
<Space>
|
<Space wrap>
|
||||||
<span>消息ID:</span>
|
<span>消息ID:</span>
|
||||||
<Input
|
<Input
|
||||||
v-model:value="messageId"
|
v-model:value="messageId"
|
||||||
|
@ -146,6 +180,7 @@ onMounted(() => {
|
||||||
allow-clear
|
allow-clear
|
||||||
placeholder="请选择事件"
|
placeholder="请选择事件"
|
||||||
style="width: 220px"
|
style="width: 220px"
|
||||||
|
@change="handleSearch"
|
||||||
/>
|
/>
|
||||||
<span>时间范围:</span>
|
<span>时间范围:</span>
|
||||||
<DatePicker.RangePicker
|
<DatePicker.RangePicker
|
||||||
|
@ -153,6 +188,7 @@ onMounted(() => {
|
||||||
:show-time="true"
|
:show-time="true"
|
||||||
format="YYYY-MM-DD HH:mm:ss"
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
style="width: 360px"
|
style="width: 360px"
|
||||||
|
@change="handleSearch"
|
||||||
/>
|
/>
|
||||||
<Button @click="handleReset">重置</Button>
|
<Button @click="handleReset">重置</Button>
|
||||||
<Button type="primary" @click="handleSearch">查询</Button>
|
<Button type="primary" @click="handleSearch">查询</Button>
|
||||||
|
@ -177,10 +213,6 @@ onMounted(() => {
|
||||||
</template>
|
</template>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
<div v-if="!loading && dataSource.length === 0" class="empty-wrap">
|
|
||||||
<Empty />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 查看内容弹窗 -->
|
<!-- 查看内容弹窗 -->
|
||||||
<Modal
|
<Modal
|
||||||
v-model:open="viewVisible"
|
v-model:open="viewVisible"
|
||||||
|
@ -189,9 +221,7 @@ onMounted(() => {
|
||||||
@cancel="closeView"
|
@cancel="closeView"
|
||||||
:footer="null"
|
:footer="null"
|
||||||
>
|
>
|
||||||
<pre class="json-view">{{
|
<pre class="json-view">{{ viewRecord }}</pre>
|
||||||
JSON.stringify(viewRecord?.payload ?? {}, null, 2)
|
|
||||||
}}</pre>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -206,14 +236,14 @@ onMounted(() => {
|
||||||
padding: 40px 0;
|
padding: 40px 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.json-view {
|
.json-view {
|
||||||
max-height: 420px;
|
max-height: 420px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,241 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
DatePicker,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
Select,
|
||||||
|
Space,
|
||||||
|
Table,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
import { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
|
import { functionLogList } from '#/api/device/device';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
deviceId: string;
|
||||||
|
metadata: any;
|
||||||
|
deviceInfo: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
// 查询条件
|
||||||
|
const messageId = ref<string>('');
|
||||||
|
const timeRange = ref<[Dayjs, Dayjs] | undefined>();
|
||||||
|
const selectedFuncId = ref<string | undefined>();
|
||||||
|
|
||||||
|
// 列表数据
|
||||||
|
const loading = ref(false);
|
||||||
|
const dataSource = ref<any[]>([]);
|
||||||
|
const pagination = ref({ current: 1, pageSize: 10, total: 0 });
|
||||||
|
|
||||||
|
// 功能映射(id -> name)
|
||||||
|
const funcNameMap = computed<Record<string, string>>(() => {
|
||||||
|
const map: Record<string, string> = {};
|
||||||
|
(props.metadata?.functions || []).forEach((f: any) => {
|
||||||
|
map[f.id] = f.name || f.id;
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
defaultWrite: '设备写入',
|
||||||
|
defaultRead: '设备读取',
|
||||||
|
...map,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 下拉选项(来自物模型)
|
||||||
|
const funcOptions = computed(() => {
|
||||||
|
return [
|
||||||
|
{ label: '全部', value: '' },
|
||||||
|
{ label: '设备写入', value: 'defaultWrite' },
|
||||||
|
{ label: '设备读取', value: 'defaultRead' },
|
||||||
|
...(props.metadata?.functions || []).map((f: any) => ({
|
||||||
|
label: f.name || f.id,
|
||||||
|
value: f.id,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
// 表格列
|
||||||
|
const columns = [
|
||||||
|
{ title: '消息ID', dataIndex: 'messageId', key: 'messageId', width: 180 },
|
||||||
|
{ title: '时间', dataIndex: 'timestamp', key: 'timestamp', width: 160 },
|
||||||
|
{ title: '功能', dataIndex: 'funcName', key: 'funcName', width: 200 },
|
||||||
|
{ title: '操作人', dataIndex: 'operator', key: 'operator', width: 120 },
|
||||||
|
{ title: '内容', dataIndex: 'content', key: 'content', ellipsis: true },
|
||||||
|
{ title: '操作', key: 'action', width: 100 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 详情弹窗
|
||||||
|
const viewVisible = ref(false);
|
||||||
|
const viewRecord = ref<any>(null);
|
||||||
|
|
||||||
|
const openView = (record: any) => {
|
||||||
|
let content = '';
|
||||||
|
try {
|
||||||
|
content = JSON.stringify(JSON.parse(record.content), null, 2);
|
||||||
|
} catch {
|
||||||
|
content = record.content;
|
||||||
|
}
|
||||||
|
viewRecord.value = content;
|
||||||
|
viewVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeView = () => {
|
||||||
|
viewVisible.value = false;
|
||||||
|
viewRecord.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载功能日志列表
|
||||||
|
const loadList = async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
let startTime = null;
|
||||||
|
let endTime = null;
|
||||||
|
if (timeRange.value?.length === 2) {
|
||||||
|
startTime = timeRange.value[0].format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
endTime = timeRange.value[1].format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
}
|
||||||
|
const params: any = {
|
||||||
|
deviceKey: props.deviceInfo.deviceKey,
|
||||||
|
productKey: props.deviceInfo.productObj.productKey,
|
||||||
|
msgId: messageId.value?.trim() || undefined,
|
||||||
|
funcId: selectedFuncId.value || undefined,
|
||||||
|
isAsc: 'desc',
|
||||||
|
pageNum: pagination.value.current,
|
||||||
|
pageSize: pagination.value.pageSize,
|
||||||
|
listWhere: [],
|
||||||
|
params: {
|
||||||
|
beginTime: undefined,
|
||||||
|
endTime: undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (startTime && endTime) {
|
||||||
|
params.params.beginTime = startTime;
|
||||||
|
params.params.endTime = endTime;
|
||||||
|
}
|
||||||
|
const data = await functionLogList(params);
|
||||||
|
dataSource.value = (data.rows || []).map((item: any) => {
|
||||||
|
return {
|
||||||
|
messageId: item.msgId,
|
||||||
|
funcName: funcNameMap.value[item.funcId] || item.funcId,
|
||||||
|
timestamp: item.serverTime,
|
||||||
|
operator: item.operatorName || '-',
|
||||||
|
content: item.funcParams || '-',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
pagination.value.total = data.total || 0;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
pagination.value.current = 1;
|
||||||
|
loadList();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
messageId.value = '';
|
||||||
|
timeRange.value = undefined;
|
||||||
|
selectedFuncId.value = undefined;
|
||||||
|
pagination.value.current = 1;
|
||||||
|
loadList();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTableChange = (page: any) => {
|
||||||
|
pagination.value.current = page.current;
|
||||||
|
pagination.value.pageSize = page.pageSize;
|
||||||
|
loadList();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadList();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="function-panel">
|
||||||
|
<!-- 查询区 -->
|
||||||
|
<div class="query-bar">
|
||||||
|
<Space wrap>
|
||||||
|
<span>消息ID:</span>
|
||||||
|
<Input
|
||||||
|
v-model:value="messageId"
|
||||||
|
placeholder="请输入"
|
||||||
|
style="width: 220px"
|
||||||
|
allow-clear
|
||||||
|
/>
|
||||||
|
<span>功能:</span>
|
||||||
|
<Select
|
||||||
|
v-model:value="selectedFuncId"
|
||||||
|
:options="funcOptions"
|
||||||
|
allow-clear
|
||||||
|
placeholder="请选择功能"
|
||||||
|
style="width: 220px"
|
||||||
|
@change="handleSearch"
|
||||||
|
/>
|
||||||
|
<span>时间范围:</span>
|
||||||
|
<DatePicker.RangePicker
|
||||||
|
v-model:value="timeRange"
|
||||||
|
:show-time="true"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
style="width: 360px"
|
||||||
|
@change="handleSearch"
|
||||||
|
/>
|
||||||
|
<Button @click="handleReset">重置</Button>
|
||||||
|
<Button type="primary" @click="handleSearch">查询</Button>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<Table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="dataSource"
|
||||||
|
:loading="loading"
|
||||||
|
:pagination="pagination"
|
||||||
|
row-key="messageId"
|
||||||
|
@change="handleTableChange"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<Button type="link" size="small" @click="openView(record)">
|
||||||
|
查看
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
<!-- 查看内容弹窗 -->
|
||||||
|
<Modal
|
||||||
|
v-model:open="viewVisible"
|
||||||
|
title="功能日志内容"
|
||||||
|
width="720px"
|
||||||
|
@cancel="closeView"
|
||||||
|
:footer="null"
|
||||||
|
>
|
||||||
|
<pre class="json-view">{{ viewRecord }}</pre>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.function-panel {
|
||||||
|
.query-bar {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-view {
|
||||||
|
max-height: 420px;
|
||||||
|
padding: 12px;
|
||||||
|
margin: 0;
|
||||||
|
overflow: auto;
|
||||||
|
word-break: break-all;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
background: #fafafa;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,204 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
DatePicker,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
Space,
|
||||||
|
Table,
|
||||||
|
Tag,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
import { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
|
import { onlineLogList } from '#/api/device/device';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
deviceId: string;
|
||||||
|
metadata: any;
|
||||||
|
deviceInfo: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
// 查询条件
|
||||||
|
const messageId = ref<string>('');
|
||||||
|
const timeRange = ref<[Dayjs, Dayjs] | undefined>();
|
||||||
|
|
||||||
|
// 列表数据
|
||||||
|
const loading = ref(false);
|
||||||
|
const dataSource = ref<any[]>([]);
|
||||||
|
const pagination = ref({ current: 1, pageSize: 10, total: 0 });
|
||||||
|
|
||||||
|
// 表格列
|
||||||
|
const columns = [
|
||||||
|
{ title: '消息ID', dataIndex: 'messageId', key: 'messageId', width: 180 },
|
||||||
|
{ title: '时间', dataIndex: 'timestamp', key: 'timestamp', width: 160 },
|
||||||
|
{ title: '类型', dataIndex: 'type', key: 'type', width: 120 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 详情弹窗
|
||||||
|
const viewVisible = ref(false);
|
||||||
|
const viewRecord = ref<any>(null);
|
||||||
|
|
||||||
|
const openView = (record: any) => {
|
||||||
|
let content = '';
|
||||||
|
try {
|
||||||
|
content = JSON.stringify(JSON.parse(record.content), null, 2);
|
||||||
|
} catch {
|
||||||
|
content = record.content;
|
||||||
|
}
|
||||||
|
viewRecord.value = content;
|
||||||
|
viewVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeView = () => {
|
||||||
|
viewVisible.value = false;
|
||||||
|
viewRecord.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载上下线日志列表
|
||||||
|
const loadList = async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
let startTime = null;
|
||||||
|
let endTime = null;
|
||||||
|
if (timeRange.value?.length === 2) {
|
||||||
|
startTime = timeRange.value[0].format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
endTime = timeRange.value[1].format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
}
|
||||||
|
|
||||||
|
const params: any = {
|
||||||
|
deviceKey: props.deviceInfo.deviceKey,
|
||||||
|
productKey: props.deviceInfo.productObj.productKey,
|
||||||
|
msgId: messageId.value?.trim() || undefined,
|
||||||
|
isAsc: 'desc',
|
||||||
|
pageNum: pagination.value.current,
|
||||||
|
pageSize: pagination.value.pageSize,
|
||||||
|
params: {
|
||||||
|
beginTime: undefined,
|
||||||
|
endTime: undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (startTime && endTime) {
|
||||||
|
params.params.beginTime = startTime;
|
||||||
|
params.params.endTime = endTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await onlineLogList(params);
|
||||||
|
dataSource.value = (data.rows || []).map((item: any) => {
|
||||||
|
return {
|
||||||
|
messageId: item.msgId,
|
||||||
|
timestamp: item.createTime,
|
||||||
|
type: item.deviceStatus,
|
||||||
|
content: item.raw_data ?? '-',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
pagination.value.total = data.total || 0;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
pagination.value.current = 1;
|
||||||
|
loadList();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
messageId.value = '';
|
||||||
|
timeRange.value = undefined;
|
||||||
|
pagination.value.current = 1;
|
||||||
|
loadList();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTableChange = (page: any) => {
|
||||||
|
pagination.value.current = page.current;
|
||||||
|
pagination.value.pageSize = page.pageSize;
|
||||||
|
loadList();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadList();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="online-status-panel">
|
||||||
|
<!-- 查询区 -->
|
||||||
|
<div class="query-bar">
|
||||||
|
<Space wrap>
|
||||||
|
<span>消息ID:</span>
|
||||||
|
<Input
|
||||||
|
v-model:value="messageId"
|
||||||
|
placeholder="请输入"
|
||||||
|
style="width: 220px"
|
||||||
|
allow-clear
|
||||||
|
/>
|
||||||
|
<span>时间范围:</span>
|
||||||
|
<DatePicker.RangePicker
|
||||||
|
v-model:value="timeRange"
|
||||||
|
:show-time="true"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
style="width: 360px"
|
||||||
|
@change="handleSearch"
|
||||||
|
/>
|
||||||
|
<Button @click="handleReset">重置</Button>
|
||||||
|
<Button type="primary" @click="handleSearch">查询</Button>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<Table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="dataSource"
|
||||||
|
:loading="loading"
|
||||||
|
:pagination="pagination"
|
||||||
|
row-key="messageId"
|
||||||
|
@change="handleTableChange"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'type'">
|
||||||
|
<Tag color="processing" v-if="record.type === 'online'">上线</Tag>
|
||||||
|
<Tag color="error" v-else-if="record.type === 'offline'">下线</Tag>
|
||||||
|
<span v-else>--</span>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<Button type="link" size="small" @click="openView(record)">
|
||||||
|
查看
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
<!-- 查看内容弹窗 -->
|
||||||
|
<Modal
|
||||||
|
v-model:open="viewVisible"
|
||||||
|
title="上下线日志内容"
|
||||||
|
width="720px"
|
||||||
|
@cancel="closeView"
|
||||||
|
:footer="null"
|
||||||
|
>
|
||||||
|
<pre class="json-view">{{ viewRecord }}</pre>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.online-status-panel {
|
||||||
|
.query-bar {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-view {
|
||||||
|
max-height: 420px;
|
||||||
|
padding: 12px;
|
||||||
|
margin: 0;
|
||||||
|
overflow: auto;
|
||||||
|
background: #fafafa;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,202 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import { Button, DatePicker, Input, Modal, Space, Table } from 'ant-design-vue';
|
||||||
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
|
import { deviceLogList } from '#/api/device/device';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
deviceId: string;
|
||||||
|
metadata: any;
|
||||||
|
deviceInfo: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
// 查询条件
|
||||||
|
const messageId = ref<string>('');
|
||||||
|
const timeRange = ref<[Dayjs, Dayjs] | undefined>();
|
||||||
|
// 属性分组选择
|
||||||
|
const selectedGroup = ref('all');
|
||||||
|
|
||||||
|
// 列表数据
|
||||||
|
const loading = ref(false);
|
||||||
|
const dataSource = ref<any[]>([]);
|
||||||
|
const pagination = ref({ current: 1, pageSize: 10, total: 0 });
|
||||||
|
|
||||||
|
// 表格列
|
||||||
|
const columns = [
|
||||||
|
{ title: '消息ID', dataIndex: 'messageId', key: 'messageId', width: 140 },
|
||||||
|
{ title: '时间', dataIndex: 'timestamp', key: 'timestamp', width: 160 },
|
||||||
|
{ title: '内容', dataIndex: 'content', key: 'content', ellipsis: true },
|
||||||
|
{ title: '操作', key: 'action', width: 100 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 详情弹窗
|
||||||
|
const viewVisible = ref(false);
|
||||||
|
const viewRecord = ref<any>(null);
|
||||||
|
|
||||||
|
const openView = (record: any) => {
|
||||||
|
let content = '';
|
||||||
|
try {
|
||||||
|
content = JSON.stringify(JSON.parse(record.content), null, 2);
|
||||||
|
} catch {
|
||||||
|
content = record.content;
|
||||||
|
}
|
||||||
|
viewRecord.value = content;
|
||||||
|
viewVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeView = () => {
|
||||||
|
viewVisible.value = false;
|
||||||
|
viewRecord.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载属性列表(占位,后续接入API)
|
||||||
|
const loadList = async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
let startTime = null;
|
||||||
|
let endTime = null;
|
||||||
|
if (timeRange.value?.length === 2) {
|
||||||
|
startTime = timeRange.value[0].valueOf();
|
||||||
|
endTime = timeRange.value[1].valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
const params: any = {
|
||||||
|
deviceKey: props.deviceInfo.deviceKey,
|
||||||
|
productKey: props.deviceInfo.productObj.productKey,
|
||||||
|
orderType: 2,
|
||||||
|
current: pagination.value.current,
|
||||||
|
size: pagination.value.pageSize,
|
||||||
|
metaDataType: 'property',
|
||||||
|
listWhere: [],
|
||||||
|
};
|
||||||
|
if (startTime && endTime) {
|
||||||
|
params.startTime = startTime;
|
||||||
|
params.endTime = endTime;
|
||||||
|
}
|
||||||
|
if (messageId.value) {
|
||||||
|
params.listWhere.push({
|
||||||
|
field: 'msg_id',
|
||||||
|
val: messageId.value?.trim(),
|
||||||
|
operator: 'eq',
|
||||||
|
valType: 'string',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const data = await deviceLogList(params);
|
||||||
|
dataSource.value = data.records.map((item: any) => {
|
||||||
|
return {
|
||||||
|
messageId: item.msg_id,
|
||||||
|
timestamp: dayjs(item.time).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
content: item.raw_data,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
pagination.value.total = data.total;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
pagination.value.current = 1;
|
||||||
|
loadList();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
messageId.value = '';
|
||||||
|
timeRange.value = undefined;
|
||||||
|
selectedGroup.value = 'all';
|
||||||
|
pagination.value.current = 1;
|
||||||
|
loadList();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTableChange = (page: any) => {
|
||||||
|
pagination.value.current = page.current;
|
||||||
|
pagination.value.pageSize = page.pageSize;
|
||||||
|
loadList();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadList();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="events-panel">
|
||||||
|
<!-- 查询区 -->
|
||||||
|
<div class="query-bar">
|
||||||
|
<Space wrap>
|
||||||
|
<span>消息ID:</span>
|
||||||
|
<Input
|
||||||
|
v-model:value="messageId"
|
||||||
|
placeholder="请输入"
|
||||||
|
style="width: 220px"
|
||||||
|
allow-clear
|
||||||
|
/>
|
||||||
|
<span>时间范围:</span>
|
||||||
|
<DatePicker.RangePicker
|
||||||
|
v-model:value="timeRange"
|
||||||
|
:show-time="true"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
style="width: 360px"
|
||||||
|
@change="handleSearch"
|
||||||
|
/>
|
||||||
|
<Button @click="handleReset">重置</Button>
|
||||||
|
<Button type="primary" @click="handleSearch">查询</Button>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<Table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="dataSource"
|
||||||
|
:loading="loading"
|
||||||
|
:pagination="pagination"
|
||||||
|
row-key="messageId"
|
||||||
|
@change="handleTableChange"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<Button type="link" size="small" @click="openView(record)">
|
||||||
|
查看
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
<!-- 查看内容弹窗 -->
|
||||||
|
<Modal
|
||||||
|
v-model:open="viewVisible"
|
||||||
|
title="属性内容"
|
||||||
|
width="720px"
|
||||||
|
@cancel="closeView"
|
||||||
|
:footer="null"
|
||||||
|
>
|
||||||
|
<pre class="json-view">{{ viewRecord }}</pre>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.events-panel {
|
||||||
|
.query-bar {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-wrap {
|
||||||
|
padding: 40px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-view {
|
||||||
|
max-height: 420px;
|
||||||
|
padding: 12px;
|
||||||
|
margin: 0;
|
||||||
|
overflow: auto;
|
||||||
|
background: #fafafa;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -9,11 +9,11 @@ import {
|
||||||
Card,
|
Card,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Col,
|
Col,
|
||||||
DatePicker,
|
|
||||||
Empty,
|
Empty,
|
||||||
Modal,
|
Modal,
|
||||||
RadioButton,
|
RadioButton,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
|
RangePicker,
|
||||||
Row,
|
Row,
|
||||||
Select,
|
Select,
|
||||||
Space,
|
Space,
|
||||||
|
@ -22,6 +22,7 @@ import {
|
||||||
} from 'ant-design-vue';
|
} from 'ant-design-vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
import { deviceLogList } from '#/api/device/device';
|
||||||
import { getWebSocket } from '#/utils/websocket';
|
import { getWebSocket } from '#/utils/websocket';
|
||||||
|
|
||||||
import RealtimeChart from './RealtimeChart.vue';
|
import RealtimeChart from './RealtimeChart.vue';
|
||||||
|
@ -129,10 +130,33 @@ const logModalVisible = ref(false);
|
||||||
const currentProperty = ref<any>(null);
|
const currentProperty = ref<any>(null);
|
||||||
const logTabActiveKey = ref('list');
|
const logTabActiveKey = ref('list');
|
||||||
const activeQuickTime = ref('today');
|
const activeQuickTime = ref('today');
|
||||||
|
const dates = ref<any>();
|
||||||
const logDateRange = ref([dayjs().startOf('day'), dayjs()]);
|
const logDateRange = ref([dayjs().startOf('day'), dayjs()]);
|
||||||
const logLoading = ref(false);
|
const logLoading = ref(false);
|
||||||
const logData = ref<any[]>([]);
|
const logData = ref<any[]>([]);
|
||||||
|
|
||||||
|
const disabledDate = (current: Dayjs) => {
|
||||||
|
const now = dayjs();
|
||||||
|
// 不能选择未来时间
|
||||||
|
if (current.isAfter(now, 'second')) return true;
|
||||||
|
if (!dates.value || (dates.value as any).length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const tooLate = dates.value[0] && current.diff(dates.value[0], 'days') > 60;
|
||||||
|
const tooEarly = dates.value[1] && dates.value[1].diff(current, 'days') > 60;
|
||||||
|
return tooEarly || tooLate;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOpenChange = (open) => {
|
||||||
|
if (open) {
|
||||||
|
dates.value = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCalendarChange = (val: any) => {
|
||||||
|
dates.value = val;
|
||||||
|
};
|
||||||
|
|
||||||
const logColumns = [
|
const logColumns = [
|
||||||
{ title: '时间', dataIndex: 'timestamp', key: 'timestamp', width: 200 },
|
{ title: '时间', dataIndex: 'timestamp', key: 'timestamp', width: 200 },
|
||||||
{
|
{
|
||||||
|
@ -147,46 +171,52 @@ const logColumns = [
|
||||||
|
|
||||||
const openPropertyLog = (property: any) => {
|
const openPropertyLog = (property: any) => {
|
||||||
currentProperty.value = property;
|
currentProperty.value = property;
|
||||||
|
logTabActiveKey.value = 'list';
|
||||||
|
activeQuickTime.value = 'today';
|
||||||
logModalVisible.value = true;
|
logModalVisible.value = true;
|
||||||
loadPropertyLog();
|
const startTime = dayjs().startOf('day').valueOf();
|
||||||
|
const endTime = dayjs().valueOf();
|
||||||
|
loadPropertyLog(property.id, startTime, endTime);
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadPropertyLog = async () => {
|
const loadPropertyLog = async (field, startTime, endTime) => {
|
||||||
if (!currentProperty.value) return;
|
if (!currentProperty.value) return;
|
||||||
try {
|
try {
|
||||||
logLoading.value = true;
|
logLoading.value = true;
|
||||||
// TODO: 调用属性日志接口
|
// TODO: 调用属性日志接口
|
||||||
logData.value = [
|
const data = await deviceLogList({
|
||||||
{ timestamp: '2025-08-19 16:19:40', value: '0.5' },
|
deviceKey: props.deviceInfo.deviceKey,
|
||||||
{ timestamp: '2025-08-19 16:18:40', value: '0.4' },
|
productKey: props.deviceInfo.productObj.productKey,
|
||||||
{ timestamp: '2025-08-19 16:17:40', value: '0.3' },
|
fields: field,
|
||||||
{ timestamp: '2025-08-20 16:19:40', value: '0.5' },
|
startTime,
|
||||||
{ timestamp: '2025-08-20 16:18:40', value: '0.4' },
|
endTime,
|
||||||
{ timestamp: '2025-08-20 16:17:40', value: '0.3' },
|
orderType: 2,
|
||||||
{ timestamp: '2025-08-21 14:19:40', value: '0.5' },
|
metaDataType: 'property',
|
||||||
{ timestamp: '2025-08-21 16:18:40', value: '0.4' },
|
});
|
||||||
{ timestamp: '2025-08-21 18:17:40', value: '0.3' },
|
logData.value = data.records.map((item: any) => {
|
||||||
{ timestamp: '2025-08-22 16:19:40', value: '0.5' },
|
const itemValue = formatValue({
|
||||||
{ timestamp: '2025-08-22 16:18:40', value: '0.4' },
|
...currentProperty.value,
|
||||||
{ timestamp: '2025-08-22 16:17:40', value: '0.3' },
|
value: item[field],
|
||||||
{ timestamp: '2025-08-23 16:19:40', value: '0.5' },
|
});
|
||||||
{ timestamp: '2025-08-23 16:18:40', value: '0.4' },
|
return {
|
||||||
{ timestamp: '2025-08-23 16:17:40', value: '0.3' },
|
timestamp: dayjs(item.time).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
{ timestamp: '2025-08-24 16:19:40', value: '0.5' },
|
value: itemValue,
|
||||||
{ timestamp: '2025-08-24 16:18:40', value: '0.4' },
|
};
|
||||||
{ timestamp: '2025-08-24 16:17:40', value: '0.3' },
|
});
|
||||||
{ timestamp: '2025-08-25 16:19:40', value: '0.5' },
|
|
||||||
{ timestamp: '2025-08-25 16:18:40', value: '0.4' },
|
|
||||||
{ timestamp: '2025-08-25 16:17:40', value: '0.3' },
|
|
||||||
];
|
|
||||||
} finally {
|
} finally {
|
||||||
logLoading.value = false;
|
logLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDateRangeChange = (dates: any) => {
|
const handleDateRangeChange = (dates: any) => {
|
||||||
|
console.log('选择日期范围', dates);
|
||||||
logDateRange.value = dates;
|
logDateRange.value = dates;
|
||||||
if (dates && dates.length === 2) loadPropertyLog();
|
const [start, end] = dates;
|
||||||
|
if (dates && dates.length === 2) {
|
||||||
|
const startTime = start.valueOf();
|
||||||
|
const endTime = end.valueOf();
|
||||||
|
loadPropertyLog(currentProperty.value.id, startTime, endTime);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleQuickTimeSelect = (e: any) => {
|
const handleQuickTimeSelect = (e: any) => {
|
||||||
|
@ -204,7 +234,9 @@ const handleQuickTimeSelect = (e: any) => {
|
||||||
logDateRange.value = [now.startOf('day'), now];
|
logDateRange.value = [now.startOf('day'), now];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loadPropertyLog();
|
const startTime = logDateRange.value[0].valueOf();
|
||||||
|
const endTime = logDateRange.value[1].valueOf();
|
||||||
|
loadPropertyLog(currentProperty.value.id, startTime, endTime);
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatValue = (property: any) => {
|
const formatValue = (property: any) => {
|
||||||
|
@ -361,11 +393,14 @@ watch(
|
||||||
<RadioButton value="week">近一周</RadioButton>
|
<RadioButton value="week">近一周</RadioButton>
|
||||||
<RadioButton value="month">近一月</RadioButton>
|
<RadioButton value="month">近一月</RadioButton>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
<DatePicker.RangePicker
|
<RangePicker
|
||||||
v-model:value="logDateRange"
|
v-model:value="logDateRange"
|
||||||
:show-time="true"
|
:show-time="true"
|
||||||
format="YYYY-MM-DD HH:mm:ss"
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
:disabled-date="disabledDate"
|
||||||
@change="handleDateRangeChange"
|
@change="handleDateRangeChange"
|
||||||
|
@open-change="onOpenChange"
|
||||||
|
@calendar-change="onCalendarChange"
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -132,18 +132,22 @@ const initializeSelectedRows = () => {
|
||||||
const initializeFormData = () => {
|
const initializeFormData = () => {
|
||||||
const defaultData = {};
|
const defaultData = {};
|
||||||
currentOutputs.value.forEach((output) => {
|
currentOutputs.value.forEach((output) => {
|
||||||
const { formType } = output.valueParams;
|
const { formType, scale } = output.valueParams;
|
||||||
|
|
||||||
// 为不同类型的组件设置默认值
|
// 为不同类型的组件设置默认值
|
||||||
switch (formType) {
|
switch (formType) {
|
||||||
case 'number': {
|
case 'number': {
|
||||||
// 数字输入框默认为最小值或0
|
// 数字输入框默认为最小值或0
|
||||||
defaultData[output.id] = output.valueParams.min || 0;
|
defaultData[output.id] = scale
|
||||||
|
? Number.parseFloat((output.valueParams.min || 0).toFixed(scale))
|
||||||
|
: output.valueParams.min || 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'progress': {
|
case 'progress': {
|
||||||
// 滚动条默认为最小值
|
// 滚动条默认为最小值
|
||||||
defaultData[output.id] = output.valueParams.min || 0;
|
defaultData[output.id] = scale
|
||||||
|
? Number.parseFloat((output.valueParams.min || 0).toFixed(scale))
|
||||||
|
: output.valueParams.min || 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'select': {
|
case 'select': {
|
||||||
|
@ -184,17 +188,20 @@ watch(selectedEventId, (newEventId) => {
|
||||||
currentOutputs.value = selectedEvt.outputs || [];
|
currentOutputs.value = selectedEvt.outputs || [];
|
||||||
// 初始化选中行
|
// 初始化选中行
|
||||||
initializeSelectedRows();
|
initializeSelectedRows();
|
||||||
|
resetForm();
|
||||||
// 初始化表单默认值
|
// 初始化表单默认值
|
||||||
initializeFormData();
|
initializeFormData();
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
resetForm();
|
resetForm();
|
||||||
|
}
|
||||||
generateDefaultContent();
|
generateDefaultContent();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听模式切换,重置表单
|
// 监听模式切换,重置表单
|
||||||
watch(currentMode, () => {
|
watch(currentMode, () => {
|
||||||
resetForm();
|
resetForm();
|
||||||
|
initializeFormData();
|
||||||
generateDefaultContent();
|
generateDefaultContent();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -285,7 +292,7 @@ const executeSubmit = async () => {
|
||||||
(isSwitch && value === false)
|
(isSwitch && value === false)
|
||||||
) {
|
) {
|
||||||
if (output) {
|
if (output) {
|
||||||
const { dataType, formType } = output.valueParams;
|
const { dataType, formType, format } = output.valueParams;
|
||||||
let processedValue = value;
|
let processedValue = value;
|
||||||
|
|
||||||
// 根据dataType和formType处理数据类型转换
|
// 根据dataType和formType处理数据类型转换
|
||||||
|
@ -295,16 +302,18 @@ const executeSubmit = async () => {
|
||||||
processedValue = Boolean(value);
|
processedValue = Boolean(value);
|
||||||
} else if (dataType === 'string') {
|
} else if (dataType === 'string') {
|
||||||
// string类型的开关,提交字符串值
|
// string类型的开关,提交字符串值
|
||||||
processedValue = value
|
processedValue = value;
|
||||||
? output.valueParams.trueValue || 'true'
|
|
||||||
: output.valueParams.falseValue || 'false';
|
|
||||||
}
|
}
|
||||||
} else if (formType === 'time' && dataType === 'date') {
|
} else if (formType === 'time' && dataType === 'date') {
|
||||||
// date类型的时间选择器,提交时间戳
|
// date类型的时间选择器,提交时间戳
|
||||||
if (value && typeof value === 'object' && value.valueOf) {
|
if (value && typeof value === 'object' && value.valueOf) {
|
||||||
processedValue = value.valueOf(); // 转换为时间戳
|
processedValue = value.valueOf(); // 转换为时间戳
|
||||||
} else if (value && typeof value === 'string') {
|
} else if (value && typeof value === 'string') {
|
||||||
processedValue = new Date(value).getTime(); // 字符串转时间戳
|
let timeValue = value;
|
||||||
|
if (format === 'YYYY-MM-DD') {
|
||||||
|
timeValue += ' 00:00:00';
|
||||||
|
}
|
||||||
|
processedValue = new Date(timeValue).getTime(); // 字符串转时间戳
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -500,9 +509,15 @@ onMounted(() => {
|
||||||
record.valueParams.dataType === 'string'
|
record.valueParams.dataType === 'string'
|
||||||
"
|
"
|
||||||
v-model:checked="formData[record.id]"
|
v-model:checked="formData[record.id]"
|
||||||
|
:checked-value="
|
||||||
|
record?.valueParams?.trueValue || 'true'
|
||||||
|
"
|
||||||
:checked-children="
|
:checked-children="
|
||||||
record?.valueParams?.trueText || '是'
|
record?.valueParams?.trueText || '是'
|
||||||
"
|
"
|
||||||
|
:un-checked-value="
|
||||||
|
record?.valueParams?.falseValue || 'false'
|
||||||
|
"
|
||||||
:un-checked-children="
|
:un-checked-children="
|
||||||
record?.valueParams?.falseText || '否'
|
record?.valueParams?.falseText || '否'
|
||||||
"
|
"
|
||||||
|
@ -517,6 +532,7 @@ onMounted(() => {
|
||||||
record?.valueParams?.format?.includes('HH:mm:ss')
|
record?.valueParams?.format?.includes('HH:mm:ss')
|
||||||
"
|
"
|
||||||
:format="record?.valueParams?.format"
|
:format="record?.valueParams?.format"
|
||||||
|
:value-format="record?.valueParams?.format"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
<TimePicker
|
<TimePicker
|
||||||
|
@ -526,6 +542,7 @@ onMounted(() => {
|
||||||
"
|
"
|
||||||
v-model:value="formData[record.id]"
|
v-model:value="formData[record.id]"
|
||||||
:format="record?.valueParams?.format"
|
:format="record?.valueParams?.format"
|
||||||
|
:value-format="record?.valueParams?.format"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
|
@ -146,30 +146,34 @@ const initializeSelectedRows = () => {
|
||||||
const initializeFormData = () => {
|
const initializeFormData = () => {
|
||||||
const defaultData = {};
|
const defaultData = {};
|
||||||
currentInputs.value.forEach((input) => {
|
currentInputs.value.forEach((input) => {
|
||||||
const { formType } = input.valueParams;
|
const { formType, scale } = input.valueParams;
|
||||||
|
|
||||||
// 为不同类型的组件设置默认值
|
// 为不同类型的组件设置默认值
|
||||||
switch (formType) {
|
switch (formType) {
|
||||||
// case 'number': {
|
case 'number': {
|
||||||
// // 数字输入框默认为最小值或0
|
// 数字输入框默认为最小值或0
|
||||||
// defaultData[input.id] = input.valueParams.min || 0;
|
defaultData[input.id] = scale
|
||||||
// break;
|
? Number.parseFloat((input.valueParams.min || 0).toFixed(scale))
|
||||||
// }
|
: input.valueParams.min || 0;
|
||||||
case 'progress': {
|
break;
|
||||||
// 滚动条默认为最小值
|
}
|
||||||
defaultData[input.id] = input.valueParams.min || 0;
|
case 'progress': {
|
||||||
|
// 滚动条默认为最小值
|
||||||
|
defaultData[input.id] = scale
|
||||||
|
? Number.parseFloat((input.valueParams.min || 0).toFixed(scale))
|
||||||
|
: input.valueParams.min || 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'select': {
|
||||||
|
// 下拉框默认为第一个选项
|
||||||
|
if (
|
||||||
|
input.valueParams.enumConf &&
|
||||||
|
input.valueParams.enumConf.length > 0
|
||||||
|
) {
|
||||||
|
defaultData[input.id] = input.valueParams.enumConf[0].value;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// case 'select': {
|
|
||||||
// // 下拉框默认为第一个选项
|
|
||||||
// if (
|
|
||||||
// input.valueParams.enumConf &&
|
|
||||||
// input.valueParams.enumConf.length > 0
|
|
||||||
// ) {
|
|
||||||
// defaultData[input.id] = input.valueParams.enumConf[0].value;
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
case 'switch': {
|
case 'switch': {
|
||||||
// 开关默认值根据数据类型设置
|
// 开关默认值根据数据类型设置
|
||||||
defaultData[input.id] =
|
defaultData[input.id] =
|
||||||
|
@ -236,10 +240,9 @@ watch(selectedFunctionId, (newFunctionId) => {
|
||||||
currentInputs.value = selectedFunc.inputs || [];
|
currentInputs.value = selectedFunc.inputs || [];
|
||||||
// 初始化选中行
|
// 初始化选中行
|
||||||
initializeSelectedRows();
|
initializeSelectedRows();
|
||||||
|
resetForm();
|
||||||
// 初始化表单默认值
|
// 初始化表单默认值
|
||||||
initializeFormData();
|
initializeFormData();
|
||||||
} else {
|
|
||||||
resetForm();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
resetForm();
|
resetForm();
|
||||||
|
@ -251,6 +254,7 @@ watch(selectedFunctionId, (newFunctionId) => {
|
||||||
// 监听模式切换,重置表单
|
// 监听模式切换,重置表单
|
||||||
watch(currentMode, () => {
|
watch(currentMode, () => {
|
||||||
resetForm();
|
resetForm();
|
||||||
|
initializeFormData();
|
||||||
generateDefaultContent();
|
generateDefaultContent();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -370,7 +374,7 @@ const executeSubmit = async (checkValue?: string) => {
|
||||||
(isSwitch && value === false)
|
(isSwitch && value === false)
|
||||||
) {
|
) {
|
||||||
if (input) {
|
if (input) {
|
||||||
const { dataType, formType } = input.valueParams;
|
const { dataType, formType, format } = input.valueParams;
|
||||||
let processedValue = value;
|
let processedValue = value;
|
||||||
|
|
||||||
// 根据dataType和formType处理数据类型转换
|
// 根据dataType和formType处理数据类型转换
|
||||||
|
@ -380,9 +384,7 @@ const executeSubmit = async (checkValue?: string) => {
|
||||||
processedValue = Boolean(value);
|
processedValue = Boolean(value);
|
||||||
} else if (dataType === 'string') {
|
} else if (dataType === 'string') {
|
||||||
// string类型的开关,提交字符串值
|
// string类型的开关,提交字符串值
|
||||||
processedValue = value
|
processedValue = value;
|
||||||
? input.valueParams.trueValue || 'true'
|
|
||||||
: input.valueParams.falseValue || 'false';
|
|
||||||
}
|
}
|
||||||
} else if (formType === 'time' && dataType === 'date') {
|
} else if (formType === 'time' && dataType === 'date') {
|
||||||
// date类型的时间选择器,提交时间戳
|
// date类型的时间选择器,提交时间戳
|
||||||
|
@ -392,7 +394,11 @@ const executeSubmit = async (checkValue?: string) => {
|
||||||
`时间字段 ${key} (date): ${value} -> ${processedValue}`,
|
`时间字段 ${key} (date): ${value} -> ${processedValue}`,
|
||||||
);
|
);
|
||||||
} else if (value && typeof value === 'string') {
|
} else if (value && typeof value === 'string') {
|
||||||
processedValue = new Date(value).getTime(); // 字符串转时间戳
|
let timeValue = value;
|
||||||
|
if (format === 'YYYY-MM-DD') {
|
||||||
|
timeValue += ' 00:00:00';
|
||||||
|
}
|
||||||
|
processedValue = new Date(timeValue).getTime(); // 字符串转时间戳
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -130,7 +130,7 @@ const generateDefaultContent = () => {
|
||||||
if (currentMode.value === 'advanced') {
|
if (currentMode.value === 'advanced') {
|
||||||
// 高级模式:状态参数key:null
|
// 高级模式:状态参数key:null
|
||||||
const params = {
|
const params = {
|
||||||
status: null,
|
status: '1',
|
||||||
};
|
};
|
||||||
jsonContent.value = JSON.stringify(params, null, 2);
|
jsonContent.value = JSON.stringify(params, null, 2);
|
||||||
parameterContent.value = '';
|
parameterContent.value = '';
|
||||||
|
|
|
@ -156,6 +156,22 @@ const columns = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 表格列配置
|
||||||
|
const readColumns = [
|
||||||
|
{
|
||||||
|
title: '属性名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '数据类型',
|
||||||
|
dataIndex: 'dataType',
|
||||||
|
key: 'dataType',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
// 表格数据源
|
// 表格数据源
|
||||||
const tableDataSource = computed(() => {
|
const tableDataSource = computed(() => {
|
||||||
return filteredProperties.value.map((property) => ({
|
return filteredProperties.value.map((property) => ({
|
||||||
|
@ -225,6 +241,13 @@ const initializeSelectedRows = () => {
|
||||||
.map((row) => row.key);
|
.map((row) => row.key);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sidebarClick = (item: any) => {
|
||||||
|
if (selectedPropertyFunction.value !== item.value) {
|
||||||
|
selectedPropertyFunction.value = item.value;
|
||||||
|
selectedGroup.value = 'all';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 初始化表单默认值
|
// 初始化表单默认值
|
||||||
const initializeFormData = () => {
|
const initializeFormData = () => {
|
||||||
const defaultData = {};
|
const defaultData = {};
|
||||||
|
@ -232,13 +255,32 @@ const initializeFormData = () => {
|
||||||
const property = filteredProperties.value.find((p) => p.id === row.id);
|
const property = filteredProperties.value.find((p) => p.id === row.id);
|
||||||
if (!property) return;
|
if (!property) return;
|
||||||
|
|
||||||
const { formType } = property.valueParams;
|
const { formType, scale } = property.valueParams;
|
||||||
|
|
||||||
// 为不同类型的组件设置默认值
|
// 为不同类型的组件设置默认值
|
||||||
switch (formType) {
|
switch (formType) {
|
||||||
|
case 'number': {
|
||||||
|
// 数字输入框默认为最小值或0
|
||||||
|
defaultData[row.id] = scale
|
||||||
|
? Number.parseFloat((property.valueParams.min || 0).toFixed(scale))
|
||||||
|
: property.valueParams.min || 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'progress': {
|
case 'progress': {
|
||||||
// 滚动条默认为最小值
|
// 滚动条默认为最小值
|
||||||
defaultData[row.id] = property.valueParams?.min || 0;
|
defaultData[row.id] = scale
|
||||||
|
? Number.parseFloat((property.valueParams.min || 0).toFixed(scale))
|
||||||
|
: property.valueParams.min || 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'select': {
|
||||||
|
// 下拉框默认为第一个选项
|
||||||
|
if (
|
||||||
|
property.valueParams.enumConf &&
|
||||||
|
property.valueParams.enumConf.length > 0
|
||||||
|
) {
|
||||||
|
defaultData[row.id] = property.valueParams.enumConf[0].value;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'switch': {
|
case 'switch': {
|
||||||
|
@ -262,6 +304,7 @@ const initializeFormData = () => {
|
||||||
// 监听属性功能切换,重置表单和内容
|
// 监听属性功能切换,重置表单和内容
|
||||||
watch(selectedPropertyFunction, () => {
|
watch(selectedPropertyFunction, () => {
|
||||||
resetForm();
|
resetForm();
|
||||||
|
initializeFormData();
|
||||||
initializeSelectedRows();
|
initializeSelectedRows();
|
||||||
generateDefaultContent();
|
generateDefaultContent();
|
||||||
});
|
});
|
||||||
|
@ -269,6 +312,7 @@ watch(selectedPropertyFunction, () => {
|
||||||
// 监听模式切换,重置表单
|
// 监听模式切换,重置表单
|
||||||
watch(currentMode, () => {
|
watch(currentMode, () => {
|
||||||
resetForm();
|
resetForm();
|
||||||
|
initializeFormData();
|
||||||
generateDefaultContent();
|
generateDefaultContent();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -298,10 +342,24 @@ const generateDefaultContent = () => {
|
||||||
if (currentMode.value === 'advanced') {
|
if (currentMode.value === 'advanced') {
|
||||||
// 高级模式:所有属性key:null
|
// 高级模式:所有属性key:null
|
||||||
const params = {};
|
const params = {};
|
||||||
|
if (selectedPropertyFunction.value === 'read') {
|
||||||
|
const propertyStr = filteredProperties.value
|
||||||
|
.map((item) => item.id)
|
||||||
|
.join(',');
|
||||||
|
jsonContent.value = JSON.stringify(
|
||||||
|
{
|
||||||
|
propertyStr,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
filteredProperties.value.forEach((property) => {
|
filteredProperties.value.forEach((property) => {
|
||||||
params[property.id] = null;
|
params[property.id] = null;
|
||||||
});
|
});
|
||||||
jsonContent.value = JSON.stringify(params, null, 2);
|
jsonContent.value = JSON.stringify(params, null, 2);
|
||||||
|
}
|
||||||
|
|
||||||
parameterContent.value = '';
|
parameterContent.value = '';
|
||||||
} else {
|
} else {
|
||||||
// 精简模式:清空参数框
|
// 精简模式:清空参数框
|
||||||
|
@ -321,10 +379,16 @@ const executeSubmit = async () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (selectedPropertyFunction.value === 'read') {
|
||||||
|
const value = selectedRowKeys.value.join(',');
|
||||||
|
parameters = {
|
||||||
|
propertyStr: value,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
// 精简模式:验证表单并获取值
|
// 精简模式:验证表单并获取值
|
||||||
await formRef.value.validate();
|
await formRef.value.validate();
|
||||||
const formValues = formData.value;
|
const formValues = formData.value;
|
||||||
console.log('表单值:', formValues);
|
console.log('表单值:', selectedRowKeys.value);
|
||||||
|
|
||||||
// 验证选中的属性
|
// 验证选中的属性
|
||||||
for (const row of tableDataSource.value) {
|
for (const row of tableDataSource.value) {
|
||||||
|
@ -361,11 +425,13 @@ const executeSubmit = async () => {
|
||||||
const row = tableDataSource.value.find((r) => r.key === key);
|
const row = tableDataSource.value.find((r) => r.key === key);
|
||||||
if (!row) return;
|
if (!row) return;
|
||||||
|
|
||||||
const property = filteredProperties.value.find((p) => p.id === row.id);
|
const property = filteredProperties.value.find(
|
||||||
|
(p) => p.id === row.id,
|
||||||
|
);
|
||||||
if (!property) return;
|
if (!property) return;
|
||||||
|
|
||||||
const value = formValues[row.id];
|
const value = formValues[row.id];
|
||||||
const { dataType, formType } = property.valueParams;
|
const { dataType, formType, format } = property.valueParams;
|
||||||
let processedValue = value;
|
let processedValue = value;
|
||||||
|
|
||||||
// 根据dataType和formType处理数据类型转换
|
// 根据dataType和formType处理数据类型转换
|
||||||
|
@ -373,20 +439,23 @@ const executeSubmit = async () => {
|
||||||
if (dataType === 'boolean') {
|
if (dataType === 'boolean') {
|
||||||
processedValue = Boolean(value);
|
processedValue = Boolean(value);
|
||||||
} else if (dataType === 'string') {
|
} else if (dataType === 'string') {
|
||||||
processedValue = value
|
processedValue = value;
|
||||||
? property.valueParams?.trueValue || 'true'
|
|
||||||
: property.valueParams?.falseValue || 'false';
|
|
||||||
}
|
}
|
||||||
} else if (formType === 'time' && dataType === 'date') {
|
} else if (formType === 'time' && dataType === 'date') {
|
||||||
if (value && typeof value === 'object' && value.valueOf) {
|
if (value && typeof value === 'object' && value.valueOf) {
|
||||||
processedValue = value.valueOf();
|
processedValue = value.valueOf();
|
||||||
} else if (value && typeof value === 'string') {
|
} else if (value && typeof value === 'string') {
|
||||||
processedValue = new Date(value).getTime();
|
let timeValue = value;
|
||||||
|
if (format === 'YYYY-MM-DD') {
|
||||||
|
timeValue += ' 00:00:00';
|
||||||
|
}
|
||||||
|
processedValue = new Date(timeValue).getTime();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parameters[row.id] = processedValue;
|
parameters[row.id] = processedValue;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 高级模式:验证JSON格式
|
// 高级模式:验证JSON格式
|
||||||
try {
|
try {
|
||||||
|
@ -477,7 +546,7 @@ onMounted(() => {
|
||||||
:key="func.value"
|
:key="func.value"
|
||||||
class="sidebar-item"
|
class="sidebar-item"
|
||||||
:class="{ active: selectedPropertyFunction === func.value }"
|
:class="{ active: selectedPropertyFunction === func.value }"
|
||||||
@click="selectedPropertyFunction = func.value"
|
@click="sidebarClick(func)"
|
||||||
>
|
>
|
||||||
<div class="function-name">{{ func.label }}</div>
|
<div class="function-name">{{ func.label }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -487,7 +556,7 @@ onMounted(() => {
|
||||||
<div class="property-main">
|
<div class="property-main">
|
||||||
<!-- 筛选控制面板 -->
|
<!-- 筛选控制面板 -->
|
||||||
<div class="control-panel">
|
<div class="control-panel">
|
||||||
<div class="control-item">
|
<div class="control-item" v-if="selectedPropertyFunction !== 'read'">
|
||||||
<span class="control-label">属性分组:</span>
|
<span class="control-label">属性分组:</span>
|
||||||
<Select
|
<Select
|
||||||
v-model:value="selectedGroup"
|
v-model:value="selectedGroup"
|
||||||
|
@ -516,12 +585,18 @@ onMounted(() => {
|
||||||
<div class="mode-info">
|
<div class="mode-info">
|
||||||
<ExclamationCircleOutlined />
|
<ExclamationCircleOutlined />
|
||||||
精简模式下属性只支持输入框的方式录入
|
精简模式下属性只支持输入框的方式录入
|
||||||
|
<span v-if="selectedPropertyFunction === 'read'">
|
||||||
|
请选择要读取的属性
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
<TabPane key="advanced" tab="高级模式">
|
<TabPane key="advanced" tab="高级模式">
|
||||||
<div class="mode-info">
|
<div class="mode-info">
|
||||||
<ExclamationCircleOutlined />
|
<ExclamationCircleOutlined />
|
||||||
高级模式下支持JSON格式直接编辑
|
高级模式下支持JSON格式直接编辑
|
||||||
|
<span v-if="selectedPropertyFunction === 'read'">
|
||||||
|
填写读取属性id,使用逗号分开
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
@ -530,9 +605,10 @@ onMounted(() => {
|
||||||
<div class="content-area">
|
<div class="content-area">
|
||||||
<!-- 左侧属性输入 -->
|
<!-- 左侧属性输入 -->
|
||||||
<div class="input-section">
|
<div class="input-section">
|
||||||
<div v-if="currentMode === 'simple'" class="simple-form">
|
<div class="simple-form" v-if="currentMode === 'simple'">
|
||||||
<Form ref="formRef" :model="formData" layout="vertical">
|
<Form ref="formRef" :model="formData" layout="vertical">
|
||||||
<Table
|
<Table
|
||||||
|
v-if="selectedPropertyFunction !== 'read'"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data-source="tableDataSource"
|
:data-source="tableDataSource"
|
||||||
:row-selection="rowSelection"
|
:row-selection="rowSelection"
|
||||||
|
@ -666,9 +742,52 @@ onMounted(() => {
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</Table>
|
</Table>
|
||||||
|
<Table
|
||||||
|
v-else-if="selectedPropertyFunction === 'read'"
|
||||||
|
:columns="readColumns"
|
||||||
|
:data-source="tableDataSource"
|
||||||
|
:row-selection="rowSelection"
|
||||||
|
:pagination="false"
|
||||||
|
:scroll="{ y: 300 }"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'name'">
|
||||||
|
<span>{{ record.name }}</span>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'dataType'">
|
||||||
|
<span>{{ record.dataType }}</span>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- <div
|
||||||
|
v-else-if="
|
||||||
|
currentMode === 'simple' && selectedPropertyFunction === 'read'
|
||||||
|
"
|
||||||
|
class="simple-form"
|
||||||
|
>
|
||||||
|
<Form ref="formRef" :model="formData" layout="vertical">
|
||||||
|
<Table
|
||||||
|
:columns="readColumns"
|
||||||
|
:data-source="tableDataSource"
|
||||||
|
:row-selection="rowSelection"
|
||||||
|
:pagination="false"
|
||||||
|
:scroll="{ y: 300 }"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'name'">
|
||||||
|
<span>{{ record.name }}</span>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'dataType'">
|
||||||
|
<span>{{ record.dataType }}</span>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
</Form>
|
||||||
|
</div> -->
|
||||||
<div v-else class="advanced-form">
|
<div v-else class="advanced-form">
|
||||||
<MonacoEditor
|
<MonacoEditor
|
||||||
:model-value="jsonContent"
|
:model-value="jsonContent"
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { columns, querySchema } from './data';
|
||||||
import productCategoryDrawer from './productCategory-drawer.vue';
|
import productCategoryDrawer from './productCategory-drawer.vue';
|
||||||
|
|
||||||
const formOptions: VbenFormProps = {
|
const formOptions: VbenFormProps = {
|
||||||
|
collapsed: true,
|
||||||
commonConfig: {
|
commonConfig: {
|
||||||
labelWidth: 80,
|
labelWidth: 80,
|
||||||
componentProps: {
|
componentProps: {
|
||||||
|
|
Loading…
Reference in New Issue