Compare commits
6 Commits
fa44554593
..
vimp
| Author | SHA1 | Date | |
|---|---|---|---|
| f78d3b32c0 | |||
| ad9a0011a5 | |||
| 8403a41cef | |||
| e8aa9b96f8 | |||
| 6f14370751 | |||
| 6fed499d78 |
@@ -13,9 +13,27 @@ import destr from 'destr';
|
||||
import { isFunction } from 'es-toolkit';
|
||||
import localforage from 'localforage';
|
||||
import { DownloadIcon, Trash2Icon, UploadIcon } from 'lucide-vue-next';
|
||||
import { NButton, NButtonGroup, NDivider, NDrawer, NDrawerContent, NDropdown, NFlex, NFormItem, NIcon, NInput, NInputNumber, NModal, NSwitch, NText, NTooltip, type DropdownOption } from 'naive-ui';
|
||||
import {
|
||||
NButton,
|
||||
NButtonGroup,
|
||||
NDivider,
|
||||
NDrawer,
|
||||
NDrawerContent,
|
||||
NDropdown,
|
||||
NFlex,
|
||||
NFormItem,
|
||||
NIcon,
|
||||
NInput,
|
||||
NInputNumber,
|
||||
NModal,
|
||||
NSwitch,
|
||||
NText,
|
||||
NTooltip,
|
||||
type DropdownOption,
|
||||
type InputInst,
|
||||
} from 'naive-ui';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { computed, nextTick, ref, useTemplateRef, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
@@ -149,8 +167,14 @@ useEventListener('keydown', (event) => {
|
||||
});
|
||||
|
||||
const expectToShowDebugCodeInput = ref(false);
|
||||
const debugCodeInputRef = useTemplateRef<InputInst>('debug-code-input-ref');
|
||||
const onModalAfterEnter = () => {
|
||||
expectToShowDebugCodeInput.value = !debugMode.value;
|
||||
if (expectToShowDebugCodeInput.value) {
|
||||
nextTick(() => {
|
||||
debugCodeInputRef.value?.focus();
|
||||
});
|
||||
}
|
||||
};
|
||||
const onModalAfterLeave = () => {
|
||||
expectToShowDebugCodeInput.value = false;
|
||||
@@ -412,7 +436,7 @@ const onClickVersion = () => {
|
||||
<NText v-else>确认关闭调试模式</NText>
|
||||
</template>
|
||||
<template #default>
|
||||
<NInput v-if="expectToShowDebugCodeInput" v-model:value="debugCode" placeholder="输入调试码" @keyup.enter="enableDebugMode" />
|
||||
<NInput ref="debug-code-input-ref" v-if="expectToShowDebugCodeInput" v-model:value="debugCode" placeholder="输入调试码" @keyup.enter="enableDebugMode" />
|
||||
</template>
|
||||
<template #action>
|
||||
<NButton @click="showDebugCodeModal = false">取消</NButton>
|
||||
|
||||
@@ -1,9 +1,59 @@
|
||||
import type { AxiosError, AxiosRequestConfig, CreateAxiosDefaults } from 'axios';
|
||||
import axios from 'axios';
|
||||
import type { AxiosError, AxiosRequestConfig, AxiosResponse, CreateAxiosDefaults, InternalAxiosRequestConfig } from 'axios';
|
||||
import axios, { isAxiosError } from 'axios';
|
||||
import type { VimpResponse, VimpResult } from '../../types';
|
||||
import { useUserStore } from '@/stores';
|
||||
import { getAppEnvConfig } from '@/utils';
|
||||
import router from '@/router';
|
||||
|
||||
export interface VimpRequestOptions extends CreateAxiosDefaults {
|
||||
requestInterceptor?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig | Promise<InternalAxiosRequestConfig>;
|
||||
responseInterceptor?: (resp: AxiosResponse) => AxiosResponse | Promise<AxiosResponse>;
|
||||
responseErrorInterceptor?: (error: any) => any;
|
||||
}
|
||||
|
||||
export const createVimpClient = (config?: VimpRequestOptions) => {
|
||||
const defaultRequestInterceptor = (config: InternalAxiosRequestConfig) => config;
|
||||
const defaultResponseInterceptor = (response: AxiosResponse) => response;
|
||||
const defaultResponseErrorInterceptor = (error: any) => {
|
||||
if (isAxiosError(error)) {
|
||||
if (error.status === 401) {
|
||||
// 处理 401 错误
|
||||
}
|
||||
if (error.status === 404) {
|
||||
// 处理 404 错误
|
||||
}
|
||||
}
|
||||
return Promise.reject(error);
|
||||
};
|
||||
|
||||
const requestInterceptor = config?.requestInterceptor ?? defaultRequestInterceptor;
|
||||
const responseInterceptor = config?.responseInterceptor ?? defaultResponseInterceptor;
|
||||
const responseErrorInterceptor = config?.responseErrorInterceptor ?? defaultResponseErrorInterceptor;
|
||||
|
||||
export const createVimpClient = (config?: CreateAxiosDefaults) => {
|
||||
const instance = axios.create(config);
|
||||
instance.interceptors.request.use(requestInterceptor);
|
||||
instance.interceptors.response.use(responseInterceptor, responseErrorInterceptor);
|
||||
|
||||
const vimpGet = <T>(url: string, options?: AxiosRequestConfig & { retRaw?: boolean }): Promise<VimpResponse<T>> => {
|
||||
const { retRaw, ...reqConfig } = options ?? {};
|
||||
return new Promise((resolve) => {
|
||||
instance
|
||||
.get(url, {
|
||||
...reqConfig,
|
||||
})
|
||||
.then((res) => {
|
||||
if (retRaw) {
|
||||
resolve([null, res.data as T, null]);
|
||||
} else {
|
||||
const resData = res.data as VimpResult<T>;
|
||||
resolve([null, resData.data, resData]);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
resolve([err as AxiosError, null, null]);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const vimpPost = <T>(url: string, data?: AxiosRequestConfig['data'], options?: Partial<Omit<AxiosRequestConfig, 'data'>> & { retRaw?: boolean; upload?: boolean }): Promise<VimpResponse<T>> => {
|
||||
const { retRaw, upload, ...reqConfig } = options ?? {};
|
||||
@@ -24,9 +74,40 @@ export const createVimpClient = (config?: CreateAxiosDefaults) => {
|
||||
});
|
||||
};
|
||||
|
||||
const httpPut = <T>(url: string, data?: AxiosRequestConfig['data'], options?: Partial<Omit<AxiosRequestConfig, 'data'>>): Promise<VimpResponse<T>> => {
|
||||
const reqConfig = options ?? {};
|
||||
return new Promise((resolve) => {
|
||||
instance
|
||||
.put<VimpResult<T>>(url, data, { ...reqConfig })
|
||||
.then((res) => {
|
||||
resolve([null, res.data.data, res.data]);
|
||||
})
|
||||
.catch((err) => {
|
||||
resolve([err as AxiosError, null, null]);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const httpDelete = <T>(url: string, idList: string[], options?: Partial<Omit<AxiosRequestConfig, 'data'>>): Promise<VimpResponse<T>> => {
|
||||
const reqConfig = options ?? {};
|
||||
return new Promise((resolve) => {
|
||||
instance
|
||||
.delete<VimpResult<T>>(url, { ...reqConfig, data: idList })
|
||||
.then((res) => {
|
||||
resolve([null, res.data.data, res.data]);
|
||||
})
|
||||
.catch((err) => {
|
||||
resolve([err as AxiosError, null, null]);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
instance,
|
||||
get: vimpGet,
|
||||
post: vimpPost,
|
||||
put: httpPut,
|
||||
delete: httpDelete,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -42,4 +123,30 @@ export const unwrapVimpResponse = <T>(resp: VimpResponse<T>) => {
|
||||
|
||||
export const vimpClient = createVimpClient({
|
||||
baseURL: `/vimp/api/client`,
|
||||
requestInterceptor: (config) => {
|
||||
const userStore = useUserStore();
|
||||
const { lampAuthorization, lampClientId, lampClientSecret } = getAppEnvConfig();
|
||||
const newAuthorization = window.btoa(`${lampClientId}:${lampClientSecret}`);
|
||||
const authorization = lampAuthorization.trim() !== '' ? lampAuthorization : newAuthorization;
|
||||
config.headers.set('accept-language', 'zh-CN,zh;q=0.9');
|
||||
config.headers.set('accept', 'application/json, text/plain, */*');
|
||||
config.headers.set('Applicationid', '');
|
||||
config.headers.set('Tenantid', '1');
|
||||
config.headers.set('Authorization', authorization);
|
||||
config.headers.set('token', userStore.userLoginResult?.token ?? '');
|
||||
return config;
|
||||
},
|
||||
responseInterceptor: (response) => {
|
||||
return response;
|
||||
},
|
||||
responseErrorInterceptor: (error) => {
|
||||
const err = error as AxiosError;
|
||||
if (err.response?.status === 401) {
|
||||
window.$message.error('登录超时,请重新登录');
|
||||
const userStore = useUserStore();
|
||||
userStore.resetStore();
|
||||
router.push({ path: '/login' });
|
||||
}
|
||||
return Promise.reject(error);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from './vimp-channel';
|
||||
export * from './vimp-station';
|
||||
export * from './vimp-site';
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
export interface VimpRawSite {
|
||||
code: string;
|
||||
name: string;
|
||||
online?: boolean;
|
||||
}
|
||||
|
||||
export interface VimpSite {
|
||||
code: string;
|
||||
name: string;
|
||||
online: boolean;
|
||||
}
|
||||
|
||||
export const normalizeVimpSite = (site: VimpRawSite): VimpSite => ({
|
||||
code: site.code,
|
||||
name: site.name,
|
||||
online: site.online ?? true,
|
||||
});
|
||||
@@ -1,5 +0,0 @@
|
||||
export interface VimpStation {
|
||||
code: string;
|
||||
name: string;
|
||||
online: boolean;
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import { unwrapVimpResponse, vimpClient } from '../client';
|
||||
import type { VimpStation } from '../model';
|
||||
import { normalizeVimpSite, type VimpRawSite } from '../model';
|
||||
|
||||
export const catalogAllDeviceApi = async (options?: { signal?: AbortSignal }) => {
|
||||
const { signal } = options ?? {};
|
||||
const client = vimpClient;
|
||||
const endpoint = `/catalog/allDevice`;
|
||||
const resp = await client.post<VimpStation[]>(endpoint, {}, { signal });
|
||||
const resp = await client.post<VimpRawSite[]>(endpoint, {}, { signal });
|
||||
const data = unwrapVimpResponse(resp);
|
||||
return data;
|
||||
return data?.map(normalizeVimpSite) ?? null;
|
||||
};
|
||||
|
||||
@@ -3,11 +3,11 @@ import { NIcon, NTabPane, NTabs, NTree, type TreeOverrideNodeClickBehavior, type
|
||||
import { h, type CSSProperties } from 'vue';
|
||||
import { useAlarmStore, useResourcePanelStore } from '../stores';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useDeviceCenterQuery } from '../composables';
|
||||
import { useChannelsQuery } from '../composables';
|
||||
import { isAlarmNode, isAlarmSiteNode, isAlarmAreaNode } from '../types';
|
||||
import { SirenIcon } from 'lucide-vue-next';
|
||||
|
||||
const { isLoading } = useDeviceCenterQuery();
|
||||
const { isLoading } = useChannelsQuery();
|
||||
|
||||
const alarmStore = useAlarmStore();
|
||||
const { lineTabPanes } = storeToRefs(alarmStore);
|
||||
|
||||
@@ -3,13 +3,13 @@ import { NIcon, NTabPane, NTabs, NTree, type TreeOverrideNodeClickBehavior, type
|
||||
import { h, type CSSProperties } from 'vue';
|
||||
import { useCameraStore, useResourcePanelStore } from '../stores';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useDeviceCenterQuery } from '../composables';
|
||||
import { useChannelsQuery } from '../composables';
|
||||
import { isCameraNode, isCameraSiteNode, isCameraAreaNode } from '../types';
|
||||
import PtzCamera from './icon/ptz-camera.vue';
|
||||
import HemiPtzCamera from './icon/hemi-ptz-camera.vue';
|
||||
import BulletCamera from './icon/bullet-camera.vue';
|
||||
|
||||
const { isLoading } = useDeviceCenterQuery();
|
||||
const { isLoading } = useChannelsQuery();
|
||||
|
||||
const cameraStore = useCameraStore();
|
||||
const { lineTabPanes } = storeToRefs(cameraStore);
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './use-device-center-query';
|
||||
export * from './use-channels-query';
|
||||
|
||||
+9
-6
@@ -4,7 +4,8 @@ import type { AxiosRequestConfig } from 'axios';
|
||||
import axios from 'axios';
|
||||
import { compileCodeAreas, type CodeArea, type CodeLines, type CodeSites } from '../../types';
|
||||
import { useCameraStore, useAlarmStore } from '../../stores';
|
||||
import { catalogAllDeviceApi, catalogChannelApi, type VimpChannel, type VimpStation } from '../../apis';
|
||||
import { catalogAllDeviceApi, catalogChannelApi, type VimpChannel, type VimpSite } from '../../apis';
|
||||
import { VIMP_CHANNELS_QUERY_KEY } from '../../constants';
|
||||
|
||||
const config: AxiosRequestConfig = {
|
||||
headers: {
|
||||
@@ -35,7 +36,7 @@ const compareByCode = <T extends { code: string }>(a: T, b: T) => {
|
||||
if (a.code > b.code) return 1;
|
||||
return 0;
|
||||
};
|
||||
const sortSitesByCode = (sites: VimpStation[]) => {
|
||||
const sortSitesByCode = (sites: VimpSite[]) => {
|
||||
sites.sort(compareByCode);
|
||||
};
|
||||
const sortChannelsMapByCode = (siteCodeToChannelsMap: Map<string, VimpChannel[]>) => {
|
||||
@@ -44,12 +45,12 @@ const sortChannelsMapByCode = (siteCodeToChannelsMap: Map<string, VimpChannel[]>
|
||||
}
|
||||
};
|
||||
|
||||
export const useDeviceCenterQuery = () => {
|
||||
export const useChannelsQuery = () => {
|
||||
const cameraStore = useCameraStore();
|
||||
const alarmStore = useAlarmStore();
|
||||
|
||||
return useQuery({
|
||||
queryKey: computed(() => ['vimp-device']),
|
||||
queryKey: computed(() => [VIMP_CHANNELS_QUERY_KEY]),
|
||||
refetchInterval: 10 * 1000,
|
||||
refetchOnWindowFocus: false,
|
||||
queryFn: async ({ signal }) => {
|
||||
@@ -73,8 +74,8 @@ export const useDeviceCenterQuery = () => {
|
||||
// 从 /allDevice 接口获取的站点信息并不保证真实性和完整性,
|
||||
// 例如有一个站点的编码是 010699 开头,但是其下的通道是 010199 和 010599 开头,
|
||||
// 而 010699 是一个不存在的站点编码,所以需要基于通道的编码来确定所有的站点。
|
||||
const cameraSites: VimpStation[] = [];
|
||||
const alarmSites: VimpStation[] = [];
|
||||
const cameraSites: VimpSite[] = [];
|
||||
const alarmSites: VimpSite[] = [];
|
||||
const cameraBuiltSitesSet = new Set<string>();
|
||||
const alarmBuiltSitesSet = new Set<string>();
|
||||
const siteCodeToCamerasMap = new Map<string, VimpChannel[]>();
|
||||
@@ -133,6 +134,7 @@ export const useDeviceCenterQuery = () => {
|
||||
codeSites,
|
||||
compiledCodeAreas,
|
||||
});
|
||||
cameraStore.buildCameraRecord(siteCodeToCamerasMap);
|
||||
|
||||
alarmStore.buildLineTabPanes({
|
||||
sites: alarmSites,
|
||||
@@ -141,6 +143,7 @@ export const useDeviceCenterQuery = () => {
|
||||
codeSites,
|
||||
compiledCodeAreas,
|
||||
});
|
||||
alarmStore.buildAlarmRecord(siteCodeToAlarmsMap);
|
||||
|
||||
return null;
|
||||
},
|
||||
@@ -0,0 +1 @@
|
||||
export * from './query';
|
||||
@@ -0,0 +1 @@
|
||||
export const VIMP_CHANNELS_QUERY_KEY = 'vimp-channels';
|
||||
@@ -1,10 +1,10 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import type { VimpChannel, VimpStation } from '../apis';
|
||||
import type { VimpChannel, VimpSite } from '../apis';
|
||||
import { shallowRef } from 'vue';
|
||||
import type { AlarmMainAreaNodeOption, AlarmNodeOption, CodeLines, CodeSites, AlarmLineTabPane, AlarmSiteNodeOption, AlarmSubAreaNodeOption, CompiledCodeAreas } from '../types';
|
||||
|
||||
interface BuildLineTabPanesParams {
|
||||
sites: VimpStation[];
|
||||
sites: VimpSite[];
|
||||
siteCodeToAlarmsMap: Map<string, VimpChannel[]>;
|
||||
codeLines: CodeLines;
|
||||
codeSites: CodeSites;
|
||||
@@ -16,6 +16,7 @@ const buildSubAreaNodeKey = (siteCode: string, areaCode: string) => `${siteCode}
|
||||
|
||||
export const useAlarmStore = defineStore('vimp-alarm-store', () => {
|
||||
const lineTabPanes = shallowRef<AlarmLineTabPane[]>([]);
|
||||
const alarmRecord = shallowRef<Record<string, VimpChannel>>({});
|
||||
|
||||
const buildLineTabPanes = (params: BuildLineTabPanesParams) => {
|
||||
const { sites, siteCodeToAlarmsMap, codeLines, codeSites, compiledCodeAreas } = params;
|
||||
@@ -154,8 +155,21 @@ export const useAlarmStore = defineStore('vimp-alarm-store', () => {
|
||||
lineTabPanes.value = result;
|
||||
};
|
||||
|
||||
const buildAlarmRecord = (siteCodeToAlarmsMap: Map<string, VimpChannel[]>) => {
|
||||
const record: Record<string, VimpChannel> = {};
|
||||
for (const [, alarms] of siteCodeToAlarmsMap) {
|
||||
for (const alarm of alarms) {
|
||||
record[alarm.code] = alarm;
|
||||
}
|
||||
}
|
||||
alarmRecord.value = record;
|
||||
};
|
||||
|
||||
return {
|
||||
lineTabPanes,
|
||||
alarmRecord,
|
||||
|
||||
buildLineTabPanes,
|
||||
buildAlarmRecord,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import type { VimpChannel, VimpStation } from '../apis';
|
||||
import { shallowRef } from 'vue';
|
||||
import type { VimpChannel, VimpSite } from '../apis';
|
||||
import { ref, shallowRef } from 'vue';
|
||||
import type { CameraMainAreaNodeOption, CameraNodeOption, CodeLines, CodeSites, CameraLineTabPane, CameraSiteNodeOption, CameraSubAreaNodeOption, CompiledCodeAreas } from '../types';
|
||||
|
||||
interface BuildLineTabPanesParams {
|
||||
sites: VimpStation[];
|
||||
sites: VimpSite[];
|
||||
siteCodeToCamerasMap: Map<string, VimpChannel[]>;
|
||||
codeLines: CodeLines;
|
||||
codeSites: CodeSites;
|
||||
@@ -16,6 +16,7 @@ const buildSubAreaNodeKey = (siteCode: string, areaCode: string) => `${siteCode}
|
||||
|
||||
export const useCameraStore = defineStore('vimp-camera-store', () => {
|
||||
const lineTabPanes = shallowRef<CameraLineTabPane[]>([]);
|
||||
const cameraRecord = shallowRef<Record<string, VimpChannel>>({});
|
||||
|
||||
const buildLineTabPanes = (params: BuildLineTabPanesParams) => {
|
||||
const { sites, siteCodeToCamerasMap, codeLines, codeSites, compiledCodeAreas } = params;
|
||||
@@ -153,8 +154,21 @@ export const useCameraStore = defineStore('vimp-camera-store', () => {
|
||||
lineTabPanes.value = result;
|
||||
};
|
||||
|
||||
const buildCameraRecord = (siteCodeToCamerasMap: Map<string, VimpChannel[]>) => {
|
||||
const record: Record<string, VimpChannel> = {};
|
||||
for (const [, cameras] of siteCodeToCamerasMap) {
|
||||
for (const camera of cameras) {
|
||||
record[camera.code] = camera;
|
||||
}
|
||||
}
|
||||
cameraRecord.value = record;
|
||||
};
|
||||
|
||||
return {
|
||||
lineTabPanes,
|
||||
cameraRecord,
|
||||
|
||||
buildLineTabPanes,
|
||||
buildCameraRecord,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { TabPaneProps, TreeOption } from 'naive-ui';
|
||||
import type { VimpChannel, VimpStation } from '../apis/model';
|
||||
import type { VimpChannel, VimpSite } from '../apis/model';
|
||||
|
||||
export type SiteType = 'station' | 'parking' | 'occ' | 'train';
|
||||
export type CodeLines = Record<string, { name: string; color: string }>;
|
||||
@@ -57,20 +57,20 @@ export interface CountStats {
|
||||
export interface CameraNodeOption extends TreeOption {
|
||||
camera: VimpChannel;
|
||||
type: string;
|
||||
site: VimpStation;
|
||||
site: VimpSite;
|
||||
}
|
||||
|
||||
export interface CameraSubAreaNodeOption extends TreeOption {
|
||||
children?: CameraNodeOption[];
|
||||
stats: CountStats;
|
||||
site: VimpStation;
|
||||
site: VimpSite;
|
||||
areaLevel: 2;
|
||||
}
|
||||
|
||||
export interface CameraMainAreaNodeOption extends TreeOption {
|
||||
children?: CameraSubAreaNodeOption[];
|
||||
stats: CountStats;
|
||||
site: VimpStation;
|
||||
site: VimpSite;
|
||||
areaLevel: 1;
|
||||
}
|
||||
|
||||
@@ -104,20 +104,20 @@ export interface CameraLineTabPane extends TabPaneProps {
|
||||
export interface AlarmNodeOption extends TreeOption {
|
||||
alarm: VimpChannel;
|
||||
type: string;
|
||||
site: VimpStation;
|
||||
site: VimpSite;
|
||||
}
|
||||
|
||||
export interface AlarmSubAreaNodeOption extends TreeOption {
|
||||
children?: AlarmNodeOption[];
|
||||
stats: CountStats;
|
||||
site: VimpStation;
|
||||
site: VimpSite;
|
||||
areaLevel: 2;
|
||||
}
|
||||
|
||||
export interface AlarmMainAreaNodeOption extends TreeOption {
|
||||
children?: AlarmSubAreaNodeOption[];
|
||||
stats: CountStats;
|
||||
site: VimpStation;
|
||||
site: VimpSite;
|
||||
areaLevel: 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import ResourcePanel from './components/resource-pannel.vue';
|
||||
import ResourcePanel from './components/resource-panel.vue';
|
||||
|
||||
const onDragover = (event: DragEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
Reference in New Issue
Block a user