|
@@ -3,7 +3,8 @@ import { Page } from '@vben/common-ui';
|
|
|
|
|
|
|
|
import { computed, h, onMounted, onUnmounted, ref } from 'vue';
|
|
import { computed, h, onMounted, onUnmounted, ref } from 'vue';
|
|
|
|
|
|
|
|
-import { Button, message, Space, Switch, Table, Tag } from 'ant-design-vue';
|
|
|
|
|
|
|
+import { DesktopOutlined } from '@ant-design/icons-vue';
|
|
|
|
|
+import { Button, Form, InputNumber, message, Modal, Space, Switch, Table, Tag, Tooltip } from 'ant-design-vue';
|
|
|
import dayjs from 'dayjs';
|
|
import dayjs from 'dayjs';
|
|
|
|
|
|
|
|
import { requestClient } from '#/api/request';
|
|
import { requestClient } from '#/api/request';
|
|
@@ -11,6 +12,7 @@ import { requestClient } from '#/api/request';
|
|
|
type SyncClient = {
|
|
type SyncClient = {
|
|
|
dataType?: string;
|
|
dataType?: string;
|
|
|
device?: string;
|
|
device?: string;
|
|
|
|
|
+ deviceId?: number;
|
|
|
firstRequestTime?: number;
|
|
firstRequestTime?: number;
|
|
|
groupSequence?: string;
|
|
groupSequence?: string;
|
|
|
key: string;
|
|
key: string;
|
|
@@ -57,6 +59,9 @@ const MARKET_TYPE_ORDER = ['2', '1', '0'];
|
|
|
const clients = ref<SyncClient[]>([]);
|
|
const clients = ref<SyncClient[]>([]);
|
|
|
const loading = ref(false);
|
|
const loading = ref(false);
|
|
|
const autoRefresh = ref(true);
|
|
const autoRefresh = ref(true);
|
|
|
|
|
+const editClient = ref<SyncClient>();
|
|
|
|
|
+const editDeviceId = ref<number>();
|
|
|
|
|
+const editVisible = ref(false);
|
|
|
const refreshTimer = ref<ReturnType<typeof setTimeout>>();
|
|
const refreshTimer = ref<ReturnType<typeof setTimeout>>();
|
|
|
|
|
|
|
|
const columns = [
|
|
const columns = [
|
|
@@ -67,35 +72,52 @@ const columns = [
|
|
|
width: 180,
|
|
width: 180,
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
|
|
+ align: 'center',
|
|
|
dataIndex: 'groupSequence',
|
|
dataIndex: 'groupSequence',
|
|
|
key: 'groupSequence',
|
|
key: 'groupSequence',
|
|
|
title: '分组序列',
|
|
title: '分组序列',
|
|
|
width: 80,
|
|
width: 80,
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
|
|
+ align: 'center',
|
|
|
dataIndex: 'requestCount',
|
|
dataIndex: 'requestCount',
|
|
|
key: 'requestCount',
|
|
key: 'requestCount',
|
|
|
title: '请求次数',
|
|
title: '请求次数',
|
|
|
width: 100,
|
|
width: 100,
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
|
|
+ align: 'center',
|
|
|
dataIndex: 'lastRequestTime',
|
|
dataIndex: 'lastRequestTime',
|
|
|
key: 'lastRequestTime',
|
|
key: 'lastRequestTime',
|
|
|
title: '最后请求时间',
|
|
title: '最后请求时间',
|
|
|
width: 180,
|
|
width: 180,
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
|
|
+ align: 'center',
|
|
|
dataIndex: 'firstRequestTime',
|
|
dataIndex: 'firstRequestTime',
|
|
|
key: 'firstRequestTime',
|
|
key: 'firstRequestTime',
|
|
|
title: '初次请求时间',
|
|
title: '初次请求时间',
|
|
|
width: 180,
|
|
width: 180,
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
|
|
+ align: 'center',
|
|
|
dataIndex: 'route',
|
|
dataIndex: 'route',
|
|
|
key: 'route',
|
|
key: 'route',
|
|
|
title: '请求接口',
|
|
title: '请求接口',
|
|
|
width: 180,
|
|
width: 180,
|
|
|
},
|
|
},
|
|
|
|
|
+ {
|
|
|
|
|
+ align: 'center',
|
|
|
|
|
+ dataIndex: 'deviceId',
|
|
|
|
|
+ key: 'deviceId',
|
|
|
|
|
+ title: '设备',
|
|
|
|
|
+ width: 90,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ key: 'action',
|
|
|
|
|
+ title: '操作',
|
|
|
|
|
+ width: 120,
|
|
|
|
|
+ },
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
const onlineThreshold = 5 * 60 * 1000;
|
|
const onlineThreshold = 5 * 60 * 1000;
|
|
@@ -123,6 +145,14 @@ const displayMappedValue = (
|
|
|
return CHAR_MAP[type]?.[String(value)] ?? value;
|
|
return CHAR_MAP[type]?.[String(value)] ?? value;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+const getDeviceUrl = (deviceId?: number) => {
|
|
|
|
|
+ if (!Number.isInteger(deviceId)) {
|
|
|
|
|
+ return '';
|
|
|
|
|
+ }
|
|
|
|
|
+ const payload = btoa(`${deviceId}\x00c\x00mysql`);
|
|
|
|
|
+ return `https://rdp.long.bid/#/client/${payload}`;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
const isOnline = (time?: number) => {
|
|
const isOnline = (time?: number) => {
|
|
|
return !!time && Date.now() - time <= onlineThreshold;
|
|
return !!time && Date.now() - time <= onlineThreshold;
|
|
|
};
|
|
};
|
|
@@ -308,7 +338,7 @@ const scheduleRefresh = () => {
|
|
|
if (!autoRefresh.value) {
|
|
if (!autoRefresh.value) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- refreshTimer.value = setTimeout(fetchClients, 10 * 1000);
|
|
|
|
|
|
|
+ refreshTimer.value = setTimeout(fetchClients, 30 * 1000);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const fetchClients = async () => {
|
|
const fetchClients = async () => {
|
|
@@ -327,6 +357,52 @@ const fetchClients = async () => {
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+const openEditClient = (client: SyncClientRow) => {
|
|
|
|
|
+ editClient.value = client;
|
|
|
|
|
+ editDeviceId.value = client.deviceId;
|
|
|
|
|
+ editVisible.value = true;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const saveClient = async () => {
|
|
|
|
|
+ if (!editClient.value?.key) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ await requestClient.post('/pstery/update_client', {
|
|
|
|
|
+ deviceId: editDeviceId.value,
|
|
|
|
|
+ key: editClient.value.key,
|
|
|
|
|
+ });
|
|
|
|
|
+ message.success('保存成功');
|
|
|
|
|
+ editVisible.value = false;
|
|
|
|
|
+ await fetchClients();
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (error) {
|
|
|
|
|
+ console.error('Failed to save sync client:', error);
|
|
|
|
|
+ message.error('保存客户端信息失败');
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const deleteClient = (client: SyncClientRow) => {
|
|
|
|
|
+ Modal.confirm({
|
|
|
|
|
+ cancelText: '取消',
|
|
|
|
|
+ content: `确定删除客户端 ${client.title} 吗?`,
|
|
|
|
|
+ okText: '删除',
|
|
|
|
|
+ okType: 'danger',
|
|
|
|
|
+ title: '删除客户端',
|
|
|
|
|
+ onOk: async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await requestClient.post('/pstery/delete_client', { key: client.key });
|
|
|
|
|
+ message.success('删除成功');
|
|
|
|
|
+ await fetchClients();
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (error) {
|
|
|
|
|
+ console.error('Failed to delete sync client:', error);
|
|
|
|
|
+ message.error('删除客户端失败');
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
const handleAutoRefreshChange = () => {
|
|
const handleAutoRefreshChange = () => {
|
|
|
scheduleRefresh();
|
|
scheduleRefresh();
|
|
|
};
|
|
};
|
|
@@ -393,14 +469,52 @@ onUnmounted(() => {
|
|
|
<template v-else-if="record.rowType !== 'client'">
|
|
<template v-else-if="record.rowType !== 'client'">
|
|
|
-
|
|
-
|
|
|
</template>
|
|
</template>
|
|
|
|
|
+ <template v-else-if="column.key === 'action'">
|
|
|
|
|
+ <Space>
|
|
|
|
|
+ <Button size="small" type="link" @click="openEditClient(record)">编辑</Button>
|
|
|
|
|
+ <Button danger size="small" type="link" @click="deleteClient(record)">删除</Button>
|
|
|
|
|
+ </Space>
|
|
|
|
|
+ </template>
|
|
|
<template v-else-if="column.key === 'requestCount'">
|
|
<template v-else-if="column.key === 'requestCount'">
|
|
|
{{ record.requestCount ?? 0 }}
|
|
{{ record.requestCount ?? 0 }}
|
|
|
</template>
|
|
</template>
|
|
|
|
|
+ <template v-else-if="column.key === 'deviceId'">
|
|
|
|
|
+ <Tooltip v-if="Number.isInteger(record.deviceId)" title="打开设备">
|
|
|
|
|
+ <a
|
|
|
|
|
+ class="device-link"
|
|
|
|
|
+ :href="getDeviceUrl(record.deviceId)"
|
|
|
|
|
+ rel="noopener noreferrer"
|
|
|
|
|
+ target="_blank"
|
|
|
|
|
+ >
|
|
|
|
|
+ <DesktopOutlined />
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </Tooltip>
|
|
|
|
|
+ <span v-else>-</span>
|
|
|
|
|
+ </template>
|
|
|
<template v-else>
|
|
<template v-else>
|
|
|
{{ displayValue(text) }}
|
|
{{ displayValue(text) }}
|
|
|
</template>
|
|
</template>
|
|
|
</template>
|
|
</template>
|
|
|
</Table>
|
|
</Table>
|
|
|
|
|
+
|
|
|
|
|
+ <Modal
|
|
|
|
|
+ v-model:visible="editVisible"
|
|
|
|
|
+ title="编辑客户端"
|
|
|
|
|
+ ok-text="保存"
|
|
|
|
|
+ cancel-text="取消"
|
|
|
|
|
+ @ok="saveClient"
|
|
|
|
|
+ >
|
|
|
|
|
+ <Form layout="vertical">
|
|
|
|
|
+ <Form.Item label="设备ID">
|
|
|
|
|
+ <InputNumber
|
|
|
|
|
+ v-model:value="editDeviceId"
|
|
|
|
|
+ :min="0"
|
|
|
|
|
+ :precision="0"
|
|
|
|
|
+ style="width: 100%"
|
|
|
|
|
+ />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ </Form>
|
|
|
|
|
+ </Modal>
|
|
|
</Page>
|
|
</Page>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
@@ -428,6 +542,15 @@ onUnmounted(() => {
|
|
|
font-size: 12px;
|
|
font-size: 12px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+.device-link {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ width: 24px;
|
|
|
|
|
+ height: 24px;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
:deep(.hidden-expand-icon) {
|
|
:deep(.hidden-expand-icon) {
|
|
|
display: none;
|
|
display: none;
|
|
|
}
|
|
}
|