Compare commits
10 Commits
309f83cf1f
...
341bcb314f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
341bcb314f | ||
|
|
bef02fe538 | ||
|
|
a5327427a7 | ||
|
|
cbb83cbe6b | ||
|
|
a19e63ad18 | ||
|
|
cb015bae30 | ||
|
|
5cc7417981 | ||
|
|
36bfb7b7ca | ||
|
|
aee45ca461 | ||
|
|
01ebcfa57d |
@@ -3,4 +3,5 @@ export interface Station {
|
|||||||
name: string;
|
name: string;
|
||||||
online: boolean;
|
online: boolean;
|
||||||
ip: string;
|
ip: string;
|
||||||
|
occ?: boolean; // 是否为控制中心
|
||||||
}
|
}
|
||||||
|
|||||||
21
src/apis/model/base/base-employee.ts
Normal file
21
src/apis/model/base/base-employee.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import type { BaseModel, ReduceForPageQuery, ReduceForSaveVO, ReduceForUpdateVO } from '@/apis';
|
||||||
|
import type { Nullable, Optional } from '@/types';
|
||||||
|
|
||||||
|
export interface BaseEmployee extends BaseModel {
|
||||||
|
userId: string;
|
||||||
|
realName: string;
|
||||||
|
defUser: Nullable<
|
||||||
|
{
|
||||||
|
username: string;
|
||||||
|
nickName: string;
|
||||||
|
} & BaseModel
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BaseEmployeeResultVO = Nullable<BaseEmployee>;
|
||||||
|
|
||||||
|
export type BaseEmployeeSaveVO = Partial<Omit<BaseEmployee, ReduceForSaveVO>>;
|
||||||
|
|
||||||
|
export type BaseEmployeeUpdateVO = Optional<Omit<BaseEmployee, ReduceForUpdateVO>>;
|
||||||
|
|
||||||
|
export type BaseEmployeePageQuery = Partial<Omit<BaseEmployee, ReduceForPageQuery>>;
|
||||||
@@ -1,3 +1 @@
|
|||||||
export * from './model';
|
export * from './base-employee';
|
||||||
export * from './page';
|
|
||||||
export * from './reduce';
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Nullable, Optional } from '@/types';
|
import type { Nullable, Optional } from '@/types';
|
||||||
import type { ReduceForPageQuery, ReduceForSaveVO, ReduceForUpdateVO } from '../../base';
|
import type { ReduceForPageQuery, ReduceForSaveVO, ReduceForUpdateVO } from '../../types';
|
||||||
import type { NdmAlarmHost } from './alarm';
|
import type { NdmAlarmHost } from './alarm';
|
||||||
import type { NdmSecurityBox, NdmSwitch } from './other';
|
import type { NdmSecurityBox, NdmSwitch } from './other';
|
||||||
import type { NdmNvr } from './storage';
|
import type { NdmNvr } from './storage';
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
|
export * from './ndm-permission';
|
||||||
export * from './ndm-security-box';
|
export * from './ndm-security-box';
|
||||||
export * from './ndm-switch';
|
export * from './ndm-switch';
|
||||||
|
|||||||
34
src/apis/model/biz/entity/other/ndm-permission.ts
Normal file
34
src/apis/model/biz/entity/other/ndm-permission.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import type { BaseModel, ReduceForPageQuery, ReduceForSaveVO, ReduceForUpdateVO, Station } from '@/apis';
|
||||||
|
import type { PermissionType } from '@/enums';
|
||||||
|
import type { Nullable, Optional } from '@/types';
|
||||||
|
|
||||||
|
export interface NdmPermission extends BaseModel {
|
||||||
|
/**
|
||||||
|
* 员工ID
|
||||||
|
*/
|
||||||
|
employeeId: string;
|
||||||
|
/**
|
||||||
|
* 服务器IP地址
|
||||||
|
*/
|
||||||
|
ipAddress: string;
|
||||||
|
/**
|
||||||
|
* 站号
|
||||||
|
*/
|
||||||
|
stationCode: Station['code'];
|
||||||
|
/**
|
||||||
|
* 站名
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* 权限类型
|
||||||
|
*/
|
||||||
|
type: PermissionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NdmPermissionResultVO = Nullable<NdmPermission>;
|
||||||
|
|
||||||
|
export type NdmPermissionSaveVO = Partial<Omit<NdmPermission, ReduceForSaveVO>>;
|
||||||
|
|
||||||
|
export type NdmPermissionUpdateVO = Optional<Omit<NdmPermission, ReduceForUpdateVO>>;
|
||||||
|
|
||||||
|
export type NdmPermissionPageQuery = Partial<Omit<NdmPermission, ReduceForPageQuery>>;
|
||||||
@@ -2,3 +2,4 @@ export * from './base';
|
|||||||
export * from './biz';
|
export * from './biz';
|
||||||
export * from './common';
|
export * from './common';
|
||||||
export * from './system';
|
export * from './system';
|
||||||
|
export * from './types';
|
||||||
|
|||||||
3
src/apis/model/types/index.ts
Normal file
3
src/apis/model/types/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './model';
|
||||||
|
export * from './page';
|
||||||
|
export * from './reduce';
|
||||||
21
src/apis/request/base/base-employee.ts
Normal file
21
src/apis/request/base/base-employee.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import type { BaseEmployeePageQuery, BaseEmployeeResultVO, PageParams, PageResult } from '@/apis';
|
||||||
|
import { userClient } from '@/apis/client';
|
||||||
|
import { unwrapResponse } from '@/utils';
|
||||||
|
|
||||||
|
export const pageBaseEmployeeApi = async (pageQuery: PageParams<BaseEmployeePageQuery>, options?: { signal?: AbortSignal }) => {
|
||||||
|
const { signal } = options ?? {};
|
||||||
|
const client = userClient;
|
||||||
|
const endpoint = '/api/base/baseEmployee/page';
|
||||||
|
const resp = await client.post<PageResult<BaseEmployeeResultVO>>(endpoint, pageQuery, { signal });
|
||||||
|
const data = unwrapResponse(resp);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const detailBaseEmployeeApi = async (id: string, options?: { signal?: AbortSignal }) => {
|
||||||
|
const { signal } = options ?? {};
|
||||||
|
const client = userClient;
|
||||||
|
const endpoint = `/api/base/baseEmployee/detail`;
|
||||||
|
const resp = await client.get<BaseEmployeeResultVO>(endpoint, { params: { id }, signal });
|
||||||
|
const data = unwrapResponse(resp);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
1
src/apis/request/base/index.ts
Normal file
1
src/apis/request/base/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './base-employee';
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
export * from './ndm-permission';
|
||||||
export * from './ndm-security-box';
|
export * from './ndm-security-box';
|
||||||
export * from './ndm-service-available';
|
export * from './ndm-service-available';
|
||||||
export * from './ndm-switch';
|
export * from './ndm-switch';
|
||||||
|
|||||||
73
src/apis/request/biz/other/ndm-permission.ts
Normal file
73
src/apis/request/biz/other/ndm-permission.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import {
|
||||||
|
ndmClient,
|
||||||
|
userClient,
|
||||||
|
type NdmPermissionPageQuery,
|
||||||
|
type NdmPermissionResultVO,
|
||||||
|
type NdmPermissionSaveVO,
|
||||||
|
type NdmPermissionUpdateVO,
|
||||||
|
type PageParams,
|
||||||
|
type PageResult,
|
||||||
|
type Station,
|
||||||
|
} from '@/apis';
|
||||||
|
import type { PermissionTypeEnum } from '@/enums';
|
||||||
|
import { unwrapResponse } from '@/utils';
|
||||||
|
|
||||||
|
export const permissionTypesApi = async (options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||||
|
const { stationCode, signal } = options ?? {};
|
||||||
|
const client = stationCode ? ndmClient : userClient;
|
||||||
|
const prefix = stationCode ? `/${stationCode}` : '';
|
||||||
|
const endpoint = `${prefix}/api/ndm/ndmPermission/types`;
|
||||||
|
const resp = await client.get<PermissionTypeEnum>(endpoint, { signal });
|
||||||
|
const data = unwrapResponse(resp);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pagePermissionApi = async (pageQuery: PageParams<NdmPermissionPageQuery>, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||||
|
const { stationCode, signal } = options ?? {};
|
||||||
|
const client = stationCode ? ndmClient : userClient;
|
||||||
|
const prefix = stationCode ? `/${stationCode}` : '';
|
||||||
|
const endpoint = `${prefix}/api/ndm/ndmPermission/page`;
|
||||||
|
const resp = await client.post<PageResult<NdmPermissionResultVO>>(endpoint, pageQuery, { signal });
|
||||||
|
const data = unwrapResponse(resp);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const detailPermissionApi = async (id: string, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||||
|
const { stationCode, signal } = options ?? {};
|
||||||
|
const client = stationCode ? ndmClient : userClient;
|
||||||
|
const prefix = stationCode ? `/${stationCode}` : '';
|
||||||
|
const endpoint = `${prefix}/api/ndm/ndmPermission/detail`;
|
||||||
|
const resp = await client.get<NdmPermissionResultVO>(endpoint, { params: { id }, signal });
|
||||||
|
const data = unwrapResponse(resp);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const savePermissionApi = async (saveVO: NdmPermissionSaveVO, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||||
|
const { stationCode, signal } = options ?? {};
|
||||||
|
const client = stationCode ? ndmClient : userClient;
|
||||||
|
const prefix = stationCode ? `/${stationCode}` : '';
|
||||||
|
const endpoint = `${prefix}/api/ndm/ndmPermission`;
|
||||||
|
const resp = await client.post<NdmPermissionResultVO>(endpoint, saveVO, { signal });
|
||||||
|
const result = unwrapResponse(resp);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updatePermissionApi = async (updateVO: NdmPermissionUpdateVO, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||||
|
const { stationCode, signal } = options ?? {};
|
||||||
|
const client = stationCode ? ndmClient : userClient;
|
||||||
|
const prefix = stationCode ? `/${stationCode}` : '';
|
||||||
|
const endpoint = `${prefix}/api/ndm/ndmPermission`;
|
||||||
|
const resp = await client.put<NdmPermissionResultVO>(endpoint, updateVO, { signal });
|
||||||
|
const result = unwrapResponse(resp);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deletePermissionApi = async (ids: string[], options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||||
|
const { stationCode, signal } = options ?? {};
|
||||||
|
const client = stationCode ? ndmClient : userClient;
|
||||||
|
const prefix = stationCode ? `/${stationCode}` : '';
|
||||||
|
const endpoint = `${prefix}/api/ndm/ndmPermission`;
|
||||||
|
const resp = await client.delete<boolean>(endpoint, ids, { signal });
|
||||||
|
const result = unwrapResponse(resp);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
|
export * from './base';
|
||||||
export * from './biz';
|
export * from './biz';
|
||||||
export * from './system';
|
export * from './system';
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { NdmAlarmHostResultVO, Station } from '@/apis';
|
import type { NdmAlarmHostResultVO, Station } from '@/apis';
|
||||||
import { AlarmHostCurrentDiag, AlarmHostHistoryDiag, AlarmHostUpdate, DeviceRawCard } from '@/components';
|
import { AlarmHostCurrentDiag, AlarmHostHistoryDiag, AlarmHostUpdate, DeviceRawCard } from '@/components';
|
||||||
|
import { usePermission } from '@/composables';
|
||||||
|
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||||
import { useSettingStore } from '@/stores';
|
import { useSettingStore } from '@/stores';
|
||||||
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
@@ -18,6 +20,8 @@ const router = useRouter();
|
|||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
const { debugModeEnabled } = storeToRefs(settingStore);
|
const { debugModeEnabled } = storeToRefs(settingStore);
|
||||||
|
|
||||||
|
const { hasPermission } = usePermission();
|
||||||
|
|
||||||
const { ndmDevice, station } = toRefs(props);
|
const { ndmDevice, station } = toRefs(props);
|
||||||
|
|
||||||
const showPageHeader = computed(() => {
|
const showPageHeader = computed(() => {
|
||||||
@@ -45,7 +49,7 @@ watch([ndmDevice, debugModeEnabled], ([newDevice, enabled], [oldDevice]) => {
|
|||||||
<NTabs :value="activeTabName" @update:value="onTabChange">
|
<NTabs :value="activeTabName" @update:value="onTabChange">
|
||||||
<NTab name="当前诊断">当前诊断</NTab>
|
<NTab name="当前诊断">当前诊断</NTab>
|
||||||
<NTab name="历史诊断">历史诊断</NTab>
|
<NTab name="历史诊断">历史诊断</NTab>
|
||||||
<NTab name="修改设备">修改设备</NTab>
|
<NTab v-if="hasPermission(station.code, PERMISSION_TYPE_LITERALS.OPERATION)" name="修改设备">修改设备</NTab>
|
||||||
<NTab v-if="debugModeEnabled" name="原始数据">原始数据</NTab>
|
<NTab v-if="debugModeEnabled" name="原始数据">原始数据</NTab>
|
||||||
</NTabs>
|
</NTabs>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { NdmCameraResultVO, Station } from '@/apis';
|
import type { NdmCameraResultVO, Station } from '@/apis';
|
||||||
import { CameraCurrentDiag, CameraHistoryDiag, CameraUpdate, DeviceRawCard } from '@/components';
|
import { CameraCurrentDiag, CameraHistoryDiag, CameraUpdate, DeviceRawCard } from '@/components';
|
||||||
|
import { usePermission } from '@/composables';
|
||||||
|
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||||
import { useSettingStore } from '@/stores';
|
import { useSettingStore } from '@/stores';
|
||||||
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
@@ -18,6 +20,8 @@ const router = useRouter();
|
|||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
const { debugModeEnabled } = storeToRefs(settingStore);
|
const { debugModeEnabled } = storeToRefs(settingStore);
|
||||||
|
|
||||||
|
const { hasPermission } = usePermission();
|
||||||
|
|
||||||
const { ndmDevice, station } = toRefs(props);
|
const { ndmDevice, station } = toRefs(props);
|
||||||
|
|
||||||
const showPageHeader = computed(() => {
|
const showPageHeader = computed(() => {
|
||||||
@@ -45,7 +49,7 @@ watch([ndmDevice, debugModeEnabled], ([newDevice, enabled], [oldDevice]) => {
|
|||||||
<NTabs :value="activeTabName" @update:value="onTabChange">
|
<NTabs :value="activeTabName" @update:value="onTabChange">
|
||||||
<NTab name="当前诊断">当前诊断</NTab>
|
<NTab name="当前诊断">当前诊断</NTab>
|
||||||
<NTab name="历史诊断">历史诊断</NTab>
|
<NTab name="历史诊断">历史诊断</NTab>
|
||||||
<NTab name="修改设备">修改设备</NTab>
|
<NTab v-if="hasPermission(station.code, PERMISSION_TYPE_LITERALS.OPERATION)" name="修改设备">修改设备</NTab>
|
||||||
<NTab v-if="debugModeEnabled" name="原始数据">原始数据</NTab>
|
<NTab v-if="debugModeEnabled" name="原始数据">原始数据</NTab>
|
||||||
</NTabs>
|
</NTabs>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { NdmDecoderResultVO, Station } from '@/apis';
|
import type { NdmDecoderResultVO, Station } from '@/apis';
|
||||||
import { DecoderCurrentDiag, DecoderHistoryDiag, DecoderUpdate, DeviceRawCard } from '@/components';
|
import { DecoderCurrentDiag, DecoderHistoryDiag, DecoderUpdate, DeviceRawCard } from '@/components';
|
||||||
|
import { usePermission } from '@/composables';
|
||||||
|
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||||
import { useSettingStore } from '@/stores';
|
import { useSettingStore } from '@/stores';
|
||||||
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
@@ -18,6 +20,8 @@ const router = useRouter();
|
|||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
const { debugModeEnabled } = storeToRefs(settingStore);
|
const { debugModeEnabled } = storeToRefs(settingStore);
|
||||||
|
|
||||||
|
const { hasPermission } = usePermission();
|
||||||
|
|
||||||
const { ndmDevice, station } = toRefs(props);
|
const { ndmDevice, station } = toRefs(props);
|
||||||
|
|
||||||
const showPageHeader = computed(() => {
|
const showPageHeader = computed(() => {
|
||||||
@@ -45,7 +49,7 @@ watch([ndmDevice, debugModeEnabled], ([newDevice, enabled], [oldDevice]) => {
|
|||||||
<NTabs :value="activeTabName" @update:value="onTabChange">
|
<NTabs :value="activeTabName" @update:value="onTabChange">
|
||||||
<NTab name="当前诊断">当前诊断</NTab>
|
<NTab name="当前诊断">当前诊断</NTab>
|
||||||
<NTab name="历史诊断">历史诊断</NTab>
|
<NTab name="历史诊断">历史诊断</NTab>
|
||||||
<NTab name="修改设备">修改设备</NTab>
|
<NTab v-if="hasPermission(station.code, PERMISSION_TYPE_LITERALS.OPERATION)" name="修改设备">修改设备</NTab>
|
||||||
<NTab v-if="debugModeEnabled" name="原始数据">原始数据</NTab>
|
<NTab v-if="debugModeEnabled" name="原始数据">原始数据</NTab>
|
||||||
</NTabs>
|
</NTabs>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { NdmKeyboardResultVO, Station } from '@/apis';
|
import type { NdmKeyboardResultVO, Station } from '@/apis';
|
||||||
import { DeviceRawCard, KeyboardCurrentDiag, KeyboardHistoryDiag, KeyboardUpdate } from '@/components';
|
import { DeviceRawCard, KeyboardCurrentDiag, KeyboardHistoryDiag, KeyboardUpdate } from '@/components';
|
||||||
|
import { usePermission } from '@/composables';
|
||||||
|
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||||
import { useSettingStore } from '@/stores';
|
import { useSettingStore } from '@/stores';
|
||||||
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
@@ -18,6 +20,8 @@ const router = useRouter();
|
|||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
const { debugModeEnabled } = storeToRefs(settingStore);
|
const { debugModeEnabled } = storeToRefs(settingStore);
|
||||||
|
|
||||||
|
const { hasPermission } = usePermission();
|
||||||
|
|
||||||
const { ndmDevice, station } = toRefs(props);
|
const { ndmDevice, station } = toRefs(props);
|
||||||
|
|
||||||
const showPageHeader = computed(() => {
|
const showPageHeader = computed(() => {
|
||||||
@@ -45,7 +49,7 @@ watch([ndmDevice, debugModeEnabled], ([newDevice, enabled], [oldDevice]) => {
|
|||||||
<NTabs :value="activeTabName" @update:value="onTabChange">
|
<NTabs :value="activeTabName" @update:value="onTabChange">
|
||||||
<NTab name="当前诊断">当前诊断</NTab>
|
<NTab name="当前诊断">当前诊断</NTab>
|
||||||
<NTab name="历史诊断">历史诊断</NTab>
|
<NTab name="历史诊断">历史诊断</NTab>
|
||||||
<NTab name="修改设备">修改设备</NTab>
|
<NTab v-if="hasPermission(station.code, PERMISSION_TYPE_LITERALS.OPERATION)" name="修改设备">修改设备</NTab>
|
||||||
<NTab v-if="debugModeEnabled" name="原始数据">原始数据</NTab>
|
<NTab v-if="debugModeEnabled" name="原始数据">原始数据</NTab>
|
||||||
</NTabs>
|
</NTabs>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { NdmNvrResultVO, Station } from '@/apis';
|
import type { NdmNvrResultVO, Station } from '@/apis';
|
||||||
import { DeviceRawCard, NvrCurrentDiag, NvrHistoryDiag, NvrUpdate } from '@/components';
|
import { DeviceRawCard, NvrCurrentDiag, NvrHistoryDiag, NvrUpdate } from '@/components';
|
||||||
|
import { usePermission } from '@/composables';
|
||||||
|
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||||
import { useSettingStore } from '@/stores';
|
import { useSettingStore } from '@/stores';
|
||||||
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
@@ -18,6 +20,8 @@ const router = useRouter();
|
|||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
const { debugModeEnabled } = storeToRefs(settingStore);
|
const { debugModeEnabled } = storeToRefs(settingStore);
|
||||||
|
|
||||||
|
const { hasPermission } = usePermission();
|
||||||
|
|
||||||
const { ndmDevice, station } = toRefs(props);
|
const { ndmDevice, station } = toRefs(props);
|
||||||
|
|
||||||
const showPageHeader = computed(() => {
|
const showPageHeader = computed(() => {
|
||||||
@@ -45,7 +49,7 @@ watch([ndmDevice, debugModeEnabled], ([newDevice, enabled], [oldDevice]) => {
|
|||||||
<NTabs :value="activeTabName" @update:value="onTabChange">
|
<NTabs :value="activeTabName" @update:value="onTabChange">
|
||||||
<NTab name="当前诊断">当前诊断</NTab>
|
<NTab name="当前诊断">当前诊断</NTab>
|
||||||
<NTab name="历史诊断">历史诊断</NTab>
|
<NTab name="历史诊断">历史诊断</NTab>
|
||||||
<NTab name="修改设备">修改设备</NTab>
|
<NTab v-if="hasPermission(station.code, PERMISSION_TYPE_LITERALS.OPERATION)" name="修改设备">修改设备</NTab>
|
||||||
<NTab v-if="debugModeEnabled" name="原始数据">原始数据</NTab>
|
<NTab v-if="debugModeEnabled" name="原始数据">原始数据</NTab>
|
||||||
</NTabs>
|
</NTabs>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const diskArray = computed(() => lastDiagInfo.value?.info?.groupInfoList);
|
|||||||
<NFlex vertical>
|
<NFlex vertical>
|
||||||
<DeviceHeaderCard :ndm-device="ndmDevice" :station="station" />
|
<DeviceHeaderCard :ndm-device="ndmDevice" :station="station" />
|
||||||
<DeviceCommonCard :common-info="commonInfo" />
|
<DeviceCommonCard :common-info="commonInfo" />
|
||||||
<DeviceHardwareCard :cpu-usage="cpuUsage" :mem-usage="memUsage" />
|
<DeviceHardwareCard v-if="!isNvrCluster(ndmDevice)" :cpu-usage="cpuUsage" :mem-usage="memUsage" />
|
||||||
<NvrDiskCard :disk-health="diskHealth" :disk-array="diskArray" />
|
<NvrDiskCard :disk-health="diskHealth" :disk-array="diskArray" />
|
||||||
<NvrRecordCard v-if="isNvrCluster(ndmDevice)" :ndm-device="ndmDevice" :station="station" />
|
<NvrRecordCard v-if="isNvrCluster(ndmDevice)" :ndm-device="ndmDevice" :station="station" />
|
||||||
</NFlex>
|
</NFlex>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { NdmSecurityBoxResultVO, Station } from '@/apis';
|
import type { NdmSecurityBoxResultVO, Station } from '@/apis';
|
||||||
import { DeviceRawCard, SecurityBoxCurrentDiag, SecurityBoxHistoryDiag, SecurityBoxUpdate } from '@/components';
|
import { DeviceRawCard, SecurityBoxCurrentDiag, SecurityBoxHistoryDiag, SecurityBoxUpdate } from '@/components';
|
||||||
|
import { usePermission } from '@/composables';
|
||||||
|
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||||
import { useSettingStore } from '@/stores';
|
import { useSettingStore } from '@/stores';
|
||||||
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
@@ -18,6 +20,8 @@ const router = useRouter();
|
|||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
const { debugModeEnabled } = storeToRefs(settingStore);
|
const { debugModeEnabled } = storeToRefs(settingStore);
|
||||||
|
|
||||||
|
const { hasPermission } = usePermission();
|
||||||
|
|
||||||
const { ndmDevice, station } = toRefs(props);
|
const { ndmDevice, station } = toRefs(props);
|
||||||
|
|
||||||
const showPageHeader = computed(() => {
|
const showPageHeader = computed(() => {
|
||||||
@@ -45,7 +49,7 @@ watch([ndmDevice, debugModeEnabled], ([newDevice, enabled], [oldDevice]) => {
|
|||||||
<NTabs :value="activeTabName" @update:value="onTabChange">
|
<NTabs :value="activeTabName" @update:value="onTabChange">
|
||||||
<NTab name="当前诊断">当前诊断</NTab>
|
<NTab name="当前诊断">当前诊断</NTab>
|
||||||
<NTab name="历史诊断">历史诊断</NTab>
|
<NTab name="历史诊断">历史诊断</NTab>
|
||||||
<NTab name="修改设备">修改设备</NTab>
|
<NTab v-if="hasPermission(station.code, PERMISSION_TYPE_LITERALS.OPERATION)" name="修改设备">修改设备</NTab>
|
||||||
<NTab v-if="debugModeEnabled" name="原始数据">原始数据</NTab>
|
<NTab v-if="debugModeEnabled" name="原始数据">原始数据</NTab>
|
||||||
</NTabs>
|
</NTabs>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { NdmServerResultVO, Station } from '@/apis';
|
import type { NdmServerResultVO, Station } from '@/apis';
|
||||||
import { DeviceRawCard, ServerCurrentDiag, ServerHistoryDiag, ServerUpdate } from '@/components';
|
import { DeviceRawCard, ServerCurrentDiag, ServerHistoryDiag, ServerUpdate } from '@/components';
|
||||||
|
import { usePermission } from '@/composables';
|
||||||
|
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||||
import { useSettingStore } from '@/stores';
|
import { useSettingStore } from '@/stores';
|
||||||
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
@@ -18,6 +20,8 @@ const router = useRouter();
|
|||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
const { debugModeEnabled } = storeToRefs(settingStore);
|
const { debugModeEnabled } = storeToRefs(settingStore);
|
||||||
|
|
||||||
|
const { hasPermission } = usePermission();
|
||||||
|
|
||||||
const { ndmDevice, station } = toRefs(props);
|
const { ndmDevice, station } = toRefs(props);
|
||||||
|
|
||||||
const showPageHeader = computed(() => {
|
const showPageHeader = computed(() => {
|
||||||
@@ -45,7 +49,7 @@ watch([ndmDevice, debugModeEnabled], ([newDevice, enabled], [oldDevice]) => {
|
|||||||
<NTabs :value="activeTabName" @update:value="onTabChange">
|
<NTabs :value="activeTabName" @update:value="onTabChange">
|
||||||
<NTab name="当前诊断">当前诊断</NTab>
|
<NTab name="当前诊断">当前诊断</NTab>
|
||||||
<NTab name="历史诊断">历史诊断</NTab>
|
<NTab name="历史诊断">历史诊断</NTab>
|
||||||
<NTab name="修改设备">修改设备</NTab>
|
<NTab v-if="hasPermission(station.code, PERMISSION_TYPE_LITERALS.OPERATION)" name="修改设备">修改设备</NTab>
|
||||||
<NTab v-if="debugModeEnabled" name="原始数据">原始数据</NTab>
|
<NTab v-if="debugModeEnabled" name="原始数据">原始数据</NTab>
|
||||||
</NTabs>
|
</NTabs>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { NdmSwitchResultVO, Station } from '@/apis';
|
import type { NdmSwitchResultVO, Station } from '@/apis';
|
||||||
import { DeviceRawCard, SwitchCurrentDiag, SwitchHistoryDiag, SwitchUpdate } from '@/components';
|
import { DeviceRawCard, SwitchCurrentDiag, SwitchHistoryDiag, SwitchUpdate } from '@/components';
|
||||||
|
import { usePermission } from '@/composables';
|
||||||
|
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||||
import { useSettingStore } from '@/stores';
|
import { useSettingStore } from '@/stores';
|
||||||
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
@@ -17,6 +19,7 @@ const router = useRouter();
|
|||||||
|
|
||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
const { debugModeEnabled } = storeToRefs(settingStore);
|
const { debugModeEnabled } = storeToRefs(settingStore);
|
||||||
|
const { hasPermission } = usePermission();
|
||||||
|
|
||||||
const { ndmDevice, station } = toRefs(props);
|
const { ndmDevice, station } = toRefs(props);
|
||||||
|
|
||||||
@@ -45,7 +48,7 @@ watch([ndmDevice, debugModeEnabled], ([newDevice, enabled], [oldDevice]) => {
|
|||||||
<NTabs :value="activeTabName" @update:value="onTabChange">
|
<NTabs :value="activeTabName" @update:value="onTabChange">
|
||||||
<NTab name="当前诊断">当前诊断</NTab>
|
<NTab name="当前诊断">当前诊断</NTab>
|
||||||
<NTab name="历史诊断">历史诊断</NTab>
|
<NTab name="历史诊断">历史诊断</NTab>
|
||||||
<NTab name="修改设备">修改设备</NTab>
|
<NTab v-if="hasPermission(station.code, PERMISSION_TYPE_LITERALS.OPERATION)" name="修改设备">修改设备</NTab>
|
||||||
<NTab v-if="debugModeEnabled" name="原始数据">原始数据</NTab>
|
<NTab v-if="debugModeEnabled" name="原始数据">原始数据</NTab>
|
||||||
</NTabs>
|
</NTabs>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { initStationDevices, type NdmDeviceResultVO, type NdmNvrResultVO, type Station } from '@/apis';
|
import { initStationDevices, type NdmDeviceResultVO, type NdmNvrResultVO, type Station } from '@/apis';
|
||||||
import { useDeviceTree, type UseDeviceTreeReturn } from '@/composables';
|
import { useDeviceTree, usePermission, type UseDeviceTreeReturn } from '@/composables';
|
||||||
import { DEVICE_TYPE_NAMES, DEVICE_TYPE_LITERALS, tryGetDeviceType, type DeviceType } from '@/enums';
|
import { DEVICE_TYPE_NAMES, DEVICE_TYPE_LITERALS, tryGetDeviceType, type DeviceType, PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||||
import { isNvrCluster } from '@/helpers';
|
import { isNvrCluster } from '@/helpers';
|
||||||
import { useDeviceStore, useStationStore } from '@/stores';
|
import { useDeviceStore, usePermissionStore } from '@/stores';
|
||||||
import { watchImmediate } from '@vueuse/core';
|
import { watchImmediate } from '@vueuse/core';
|
||||||
import destr from 'destr';
|
import destr from 'destr';
|
||||||
import { isFunction } from 'es-toolkit';
|
import { isFunction } from 'es-toolkit';
|
||||||
@@ -60,6 +60,8 @@ const { station, events, syncRoute, devicePrefixLabel } = toRefs(props);
|
|||||||
|
|
||||||
const themeVars = useThemeVars();
|
const themeVars = useThemeVars();
|
||||||
|
|
||||||
|
const { hasPermission } = usePermission();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
// 设备选择
|
// 设备选择
|
||||||
selectedStationCode,
|
selectedStationCode,
|
||||||
@@ -87,8 +89,8 @@ const onSelectDevice = (device: NdmDeviceResultVO, stationCode: Station['code'])
|
|||||||
emit('afterSelectDevice', device, stationCode);
|
emit('afterSelectDevice', device, stationCode);
|
||||||
};
|
};
|
||||||
|
|
||||||
const stationStore = useStationStore();
|
const permissionStore = usePermissionStore();
|
||||||
const { stations } = storeToRefs(stationStore);
|
const stations = computed(() => permissionStore.stations.VIEW ?? []);
|
||||||
const deviceStore = useDeviceStore();
|
const deviceStore = useDeviceStore();
|
||||||
const { lineDevices } = storeToRefs(deviceStore);
|
const { lineDevices } = storeToRefs(deviceStore);
|
||||||
|
|
||||||
@@ -220,13 +222,17 @@ const nodeProps: TreeProps['nodeProps'] = ({ option }) => {
|
|||||||
payload.stopPropagation();
|
payload.stopPropagation();
|
||||||
payload.preventDefault();
|
payload.preventDefault();
|
||||||
|
|
||||||
// 仅当事件列表包含 `manage` 时才显示右键菜单
|
// 如果事件列表不包含 `manage`,则直接结束逻辑
|
||||||
if (!events.value?.includes('manage')) return;
|
if (!events.value?.includes('manage')) return;
|
||||||
|
|
||||||
const { clientX, clientY } = payload;
|
|
||||||
const stationCode = option['stationCode'] as Station['code'];
|
const stationCode = option['stationCode'] as Station['code'];
|
||||||
|
|
||||||
|
// 仅当用户在该车站拥有操作权限时才显示右键菜单
|
||||||
|
if (!hasPermission(stationCode, PERMISSION_TYPE_LITERALS.OPERATION)) return;
|
||||||
|
|
||||||
const deviceType = option['deviceType'] as DeviceType | undefined;
|
const deviceType = option['deviceType'] as DeviceType | undefined;
|
||||||
const device = option['device'] as NdmDeviceResultVO | undefined;
|
const device = option['device'] as NdmDeviceResultVO | undefined;
|
||||||
|
const { clientX, clientY } = payload;
|
||||||
contextmenu.value = { x: clientX, y: clientY, stationCode, deviceType, device };
|
contextmenu.value = { x: clientX, y: clientY, stationCode, deviceType, device };
|
||||||
showContextmenu.value = true;
|
showContextmenu.value = true;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { retentionDaysApi, snapStatusApi, type LineAlarms, type LineDevices, type Station, type VersionInfo } from '@/apis';
|
import { retentionDaysApi, snapStatusApi, type LineAlarms, type LineDevices, type Station, type VersionInfo } from '@/apis';
|
||||||
import { ThemeSwitch } from '@/components';
|
import { ThemeSwitch } from '@/components';
|
||||||
|
import { usePermission } from '@/composables';
|
||||||
import { NDM_ALARM_STORE_ID, NDM_DEVICE_STORE_ID, NDM_STATION_STORE_ID } from '@/constants';
|
import { NDM_ALARM_STORE_ID, NDM_DEVICE_STORE_ID, NDM_STATION_STORE_ID } from '@/constants';
|
||||||
import { usePollingStore, useSettingStore } from '@/stores';
|
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||||
|
import { usePollingStore, useSettingStore, useStationStore } from '@/stores';
|
||||||
import { downloadByData, getAppEnvConfig, parseErrorFeedback, sleep } from '@/utils';
|
import { downloadByData, getAppEnvConfig, parseErrorFeedback, sleep } from '@/utils';
|
||||||
import { useMutation } from '@tanstack/vue-query';
|
import { useMutation } from '@tanstack/vue-query';
|
||||||
import { useEventListener } from '@vueuse/core';
|
import { useEventListener } from '@vueuse/core';
|
||||||
@@ -13,13 +15,20 @@ import localforage from 'localforage';
|
|||||||
import { DownloadIcon, Trash2Icon, UploadIcon } from 'lucide-vue-next';
|
import { DownloadIcon, Trash2Icon, UploadIcon } from 'lucide-vue-next';
|
||||||
import { NButton, NButtonGroup, NDivider, NDrawer, NDrawerContent, NDropdown, NFlex, NFormItem, NIcon, NInput, NInputNumber, NModal, NSwitch, NText, type DropdownOption } from 'naive-ui';
|
import { NButton, NButtonGroup, NDivider, NDrawer, NDrawerContent, NDropdown, NFlex, NFormItem, NIcon, NInput, NInputNumber, NModal, NSwitch, NText, type DropdownOption } from 'naive-ui';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
const show = defineModel<boolean>('show', { default: false });
|
const show = defineModel<boolean>('show', { default: false });
|
||||||
|
|
||||||
|
const stationStore = useStationStore();
|
||||||
|
const { stations } = storeToRefs(stationStore);
|
||||||
|
|
||||||
|
const occStation = computed(() => stations.value.find((station) => !!station.occ));
|
||||||
|
|
||||||
const settingsStore = useSettingStore();
|
const settingsStore = useSettingStore();
|
||||||
const { menuCollpased, stationGridCols, debugModeEnabled, offlineDev } = storeToRefs(settingsStore);
|
const { menuCollpased, stationGridCols, debugModeEnabled, offlineDev } = storeToRefs(settingsStore);
|
||||||
|
|
||||||
|
const { hasPermission } = usePermission();
|
||||||
|
|
||||||
const versionInfo = ref<VersionInfo>({ version: '', buildTime: '' });
|
const versionInfo = ref<VersionInfo>({ version: '', buildTime: '' });
|
||||||
|
|
||||||
const { mutate: getVersionInfo } = useMutation({
|
const { mutate: getVersionInfo } = useMutation({
|
||||||
@@ -303,6 +312,7 @@ const onDrawerAfterLeave = () => {
|
|||||||
<NInputNumber v-model:value="stationGridCols" :min="1" :max="10" />
|
<NInputNumber v-model:value="stationGridCols" :min="1" :max="10" />
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
|
|
||||||
|
<template v-if="!!occStation && hasPermission(occStation.code, PERMISSION_TYPE_LITERALS.OPERATION)">
|
||||||
<NDivider>告警</NDivider>
|
<NDivider>告警</NDivider>
|
||||||
<NFormItem label="告警画面截图保留天数" label-placement="left">
|
<NFormItem label="告警画面截图保留天数" label-placement="left">
|
||||||
<NFlex justify="space-between" align="center" style="width: 100%">
|
<NFlex justify="space-between" align="center" style="width: 100%">
|
||||||
@@ -322,6 +332,7 @@ const onDrawerAfterLeave = () => {
|
|||||||
</NButtonGroup>
|
</NButtonGroup>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template v-if="debugModeEnabled">
|
<template v-if="debugModeEnabled">
|
||||||
<NDivider title-placement="center">调试</NDivider>
|
<NDivider title-placement="center">调试</NDivider>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export * from './device';
|
export * from './device';
|
||||||
export * from './global';
|
export * from './global';
|
||||||
|
export * from './permission';
|
||||||
export * from './station';
|
export * from './station';
|
||||||
|
|||||||
1
src/components/permission/index.ts
Normal file
1
src/components/permission/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './permission-config-modal';
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import type { ComponentInstance } from 'vue';
|
||||||
|
import PermissionConfigModal from './permission-config-modal.vue';
|
||||||
|
|
||||||
|
export type PermissionConfigModalProps = ComponentInstance<typeof PermissionConfigModal>['$props'];
|
||||||
|
|
||||||
|
export { PermissionConfigModal };
|
||||||
@@ -0,0 +1,263 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
deletePermissionApi,
|
||||||
|
detailBaseEmployeeApi,
|
||||||
|
pagePermissionApi,
|
||||||
|
savePermissionApi,
|
||||||
|
type BaseEmployeeResultVO,
|
||||||
|
type NdmPermissionResultVO,
|
||||||
|
type NdmPermissionSaveVO,
|
||||||
|
type Station,
|
||||||
|
} from '@/apis';
|
||||||
|
import { PERMISSION_TYPE_NAMES, type PermissionType } from '@/enums';
|
||||||
|
import { useStationStore } from '@/stores';
|
||||||
|
import { parseErrorFeedback } from '@/utils';
|
||||||
|
import { useMutation } from '@tanstack/vue-query';
|
||||||
|
import { objectEntries } from '@vueuse/core';
|
||||||
|
import { isCancel } from 'axios';
|
||||||
|
import { cloneDeep, groupBy } from 'es-toolkit';
|
||||||
|
import { NButton, NCheckbox, NDataTable, NFlex, NModal, NText, type DataTableColumn, type DataTableColumns } from 'naive-ui';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { computed, h, ref, toRefs } from 'vue';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为避免修改权限记录而带来复杂的状态管理,引入 `action` 字段
|
||||||
|
*
|
||||||
|
* - `save`:新增权限
|
||||||
|
* - `delete`:删除权限
|
||||||
|
*
|
||||||
|
* 修改权限的过程中会对本地的权限列表进行修改:
|
||||||
|
* - 当勾选权限时,在本地新增一条权限,`action` 为 `save`
|
||||||
|
* - 当取消勾选权限时,如果该权限来自服务器,则将`action` 改为 `delete`,否则直接删除权限记录
|
||||||
|
*
|
||||||
|
* 最后,保存权限时,只需要遍历本地权限列表:
|
||||||
|
* - 如果 `action` 为 `save`,则调用 `savePermissionApi` 新增权限
|
||||||
|
* - 如果 `action` 为 `delete`,则调用 `deletePermissionApi` 删除权限
|
||||||
|
*/
|
||||||
|
type PermissionAction = 'save' | 'delete';
|
||||||
|
|
||||||
|
type NdmPermissionSaveOrResultVO = NdmPermissionSaveVO | NdmPermissionResultVO;
|
||||||
|
|
||||||
|
type NdmPermissionExtendedSaveVO = NdmPermissionSaveVO & { action?: PermissionAction };
|
||||||
|
|
||||||
|
type NdmPermissionExtendedResultVO = NdmPermissionResultVO & { action?: PermissionAction };
|
||||||
|
|
||||||
|
type NdmPermissionExtendedSaveOrResultVO = NdmPermissionExtendedSaveVO | NdmPermissionExtendedResultVO;
|
||||||
|
|
||||||
|
const omitActionField = (extendedSaveOrResultVO: NdmPermissionExtendedSaveOrResultVO) => {
|
||||||
|
const copy = cloneDeep(extendedSaveOrResultVO);
|
||||||
|
delete copy.action;
|
||||||
|
const saveOrResultVO = copy as NdmPermissionSaveOrResultVO;
|
||||||
|
return saveOrResultVO;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isSaveVO = (saveOrResultVO: NdmPermissionSaveOrResultVO): saveOrResultVO is NdmPermissionSaveVO => {
|
||||||
|
return !('id' in saveOrResultVO);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isResultVO = (saveOrResultVO: NdmPermissionSaveOrResultVO): saveOrResultVO is NdmPermissionResultVO => {
|
||||||
|
return 'id' in saveOrResultVO;
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
employeeId?: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const stationStore = useStationStore();
|
||||||
|
const { stations } = storeToRefs(stationStore);
|
||||||
|
|
||||||
|
const show = defineModel<boolean>('show', { default: false });
|
||||||
|
|
||||||
|
const { employeeId } = toRefs(props);
|
||||||
|
|
||||||
|
const abortController = ref<AbortController>(new AbortController());
|
||||||
|
|
||||||
|
const employee = ref<BaseEmployeeResultVO>();
|
||||||
|
const { mutateAsync: getEmployeeAsync } = useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
abortController.value.abort();
|
||||||
|
abortController.value = new AbortController();
|
||||||
|
|
||||||
|
if (!employeeId.value) return;
|
||||||
|
|
||||||
|
const signal = abortController.value.signal;
|
||||||
|
|
||||||
|
const data = await detailBaseEmployeeApi(employeeId.value, { signal });
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: (data) => {
|
||||||
|
if (!data) return;
|
||||||
|
employee.value = data;
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
if (isCancel(error)) return;
|
||||||
|
console.error(error);
|
||||||
|
const errorFeedback = parseErrorFeedback(error);
|
||||||
|
window.$message.error(errorFeedback);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const permissions = ref<Record<Station['code'], NdmPermissionExtendedSaveOrResultVO[]>>({});
|
||||||
|
const { mutate: getPermissions, isPending: permissionsLoading } = useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
abortController.value.abort();
|
||||||
|
abortController.value = new AbortController();
|
||||||
|
|
||||||
|
if (!employeeId.value) return;
|
||||||
|
|
||||||
|
const signal = abortController.value.signal;
|
||||||
|
|
||||||
|
const data = await pagePermissionApi(
|
||||||
|
{
|
||||||
|
model: {
|
||||||
|
employeeId: employeeId.value,
|
||||||
|
},
|
||||||
|
extra: {},
|
||||||
|
current: 1,
|
||||||
|
size: Object.keys(PERMISSION_TYPE_NAMES).length * stations.value.length,
|
||||||
|
sort: 'id',
|
||||||
|
order: 'ascending',
|
||||||
|
},
|
||||||
|
{ signal },
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: (data) => {
|
||||||
|
if (!data) return;
|
||||||
|
const { records } = data;
|
||||||
|
permissions.value = groupBy(records, (record) => record.stationCode ?? '');
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
if (isCancel(error)) return;
|
||||||
|
console.error(error);
|
||||||
|
const errorFeedback = parseErrorFeedback(error);
|
||||||
|
window.$message.error(errorFeedback);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const onUpdatePermissionChecked = (checked: boolean, stationCode: Station['code'], permissionType: PermissionType) => {
|
||||||
|
if (!employeeId.value) return;
|
||||||
|
if (checked) {
|
||||||
|
// 勾选时,新增一条权限记录,`action` 为 `save`
|
||||||
|
if (!permissions.value[stationCode]) permissions.value[stationCode] = [];
|
||||||
|
permissions.value[stationCode].push({ employeeId: employeeId.value, stationCode, type: permissionType, action: 'save' });
|
||||||
|
} else {
|
||||||
|
if (!permissions.value[stationCode]) return;
|
||||||
|
// 取消勾选时,先找到所有该类型的权限并遍历,
|
||||||
|
// 如果权限来自数据库(即有 `id` 字段),则将其 `action` 设为 `delete`,
|
||||||
|
// 否则直接从权限记录中移除
|
||||||
|
const targets = permissions.value[stationCode].filter((permission) => permission.type === permissionType);
|
||||||
|
for (const permission of targets) {
|
||||||
|
if (isResultVO(permission)) {
|
||||||
|
permission.action = 'delete';
|
||||||
|
} else {
|
||||||
|
permissions.value[stationCode].splice(permissions.value[stationCode].indexOf(permission), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const tableColumns = computed<DataTableColumns<Station>>(() => {
|
||||||
|
return [
|
||||||
|
{ title: '车站编号', key: 'code', align: 'center', width: 120 },
|
||||||
|
{ title: '车站名称', key: 'name', align: 'center', width: 360 },
|
||||||
|
// 「权限」列
|
||||||
|
...objectEntries(PERMISSION_TYPE_NAMES).map<DataTableColumn<Station>>(([permissionType, title]) => ({
|
||||||
|
title: title,
|
||||||
|
key: permissionType,
|
||||||
|
align: 'center',
|
||||||
|
render: (rowData) => {
|
||||||
|
const { code: stationCode } = rowData;
|
||||||
|
return h(NCheckbox, {
|
||||||
|
// 如果权限记录中存在该权限且 `action` 不为 `delete`,则处于勾选状态
|
||||||
|
checked: !!(permissions.value[stationCode] ?? []).find((permission) => permission.type === permissionType && permission.action !== 'delete'),
|
||||||
|
// 改变勾选状态
|
||||||
|
onUpdateChecked: (checked) => onUpdatePermissionChecked(checked, stationCode, permissionType),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const { mutate: savePermissions, isPending: permissionsSaving } = useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
abortController.value.abort();
|
||||||
|
abortController.value = new AbortController();
|
||||||
|
|
||||||
|
const signal = abortController.value.signal;
|
||||||
|
|
||||||
|
// 遍历所有的权限记录,根据 `action` 进行保存或删除
|
||||||
|
for (const stationPermissions of Object.values(permissions.value)) {
|
||||||
|
for (const permission of stationPermissions) {
|
||||||
|
const saveOrResultVO = omitActionField(permission);
|
||||||
|
if (permission.action === 'save' && isSaveVO(saveOrResultVO)) {
|
||||||
|
await savePermissionApi(saveOrResultVO, { signal });
|
||||||
|
}
|
||||||
|
if (permission.action === 'delete' && isResultVO(saveOrResultVO)) {
|
||||||
|
const id = saveOrResultVO.id;
|
||||||
|
if (!id) continue;
|
||||||
|
await deletePermissionApi([id], { signal });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
window.$message.success('权限配置保存成功');
|
||||||
|
getPermissions();
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
if (isCancel(error)) return;
|
||||||
|
console.error(error);
|
||||||
|
const errorFeedback = parseErrorFeedback(error);
|
||||||
|
window.$message.error(errorFeedback);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const onAfterEnter = () => {
|
||||||
|
getEmployeeAsync().then(() => getPermissions());
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAfterLeave = () => {
|
||||||
|
employee.value = undefined;
|
||||||
|
permissions.value = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClose = () => {
|
||||||
|
abortController.value.abort();
|
||||||
|
show.value = false;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NModal
|
||||||
|
v-model:show="show"
|
||||||
|
preset="card"
|
||||||
|
style="width: 100vw; height: 100vh"
|
||||||
|
:content-style="{ height: '100%', overflow: 'hidden' }"
|
||||||
|
:close-on-esc="false"
|
||||||
|
:mask-closable="false"
|
||||||
|
:auto-focus="false"
|
||||||
|
@after-enter="onAfterEnter"
|
||||||
|
@after-leave="onAfterLeave"
|
||||||
|
@close="onClose"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<span>{{ `配置权限 - ${employee?.realName ?? ''}` }}</span>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<NDataTable flex-height style="height: 100%" :columns="tableColumns" :data="stations" :loading="permissionsLoading" :single-line="false" />
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<NText depth="3" style="font-size: smaller">*未勾选任何权限的用户将被认为拥有所有权限</NText>
|
||||||
|
</template>
|
||||||
|
<template #action>
|
||||||
|
<NFlex justify="end">
|
||||||
|
<NButton size="small" @click="onClose">取消</NButton>
|
||||||
|
<NButton type="primary" size="small" :loading="permissionsSaving" @click="() => savePermissions()">保存</NButton>
|
||||||
|
</NFlex>
|
||||||
|
</template>
|
||||||
|
</NModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Station, StationAlarms, StationDevices } from '@/apis';
|
import type { Station, StationAlarms, StationDevices } from '@/apis';
|
||||||
import { DEVICE_TYPE_LITERALS } from '@/enums';
|
import { usePermission } from '@/composables';
|
||||||
|
import { DEVICE_TYPE_LITERALS, PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { isFunction } from 'es-toolkit';
|
import { isFunction } from 'es-toolkit';
|
||||||
@@ -24,6 +25,8 @@ const emit = defineEmits<{
|
|||||||
clickConfig: [station: Station];
|
clickConfig: [station: Station];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const { hasPermission } = usePermission();
|
||||||
|
|
||||||
const { station, devices, alarms, selectable } = toRefs(props);
|
const { station, devices, alarms, selectable } = toRefs(props);
|
||||||
|
|
||||||
const onlineDeviceCount = computed(() => {
|
const onlineDeviceCount = computed(() => {
|
||||||
@@ -71,7 +74,7 @@ const openDeviceConfigModal = () => {
|
|||||||
emit('clickConfig', station.value);
|
emit('clickConfig', station.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const dropdownOptions: DropdownOption[] = [
|
const dropdownOptions = computed<DropdownOption[]>(() => [
|
||||||
{
|
{
|
||||||
label: '视频平台',
|
label: '视频平台',
|
||||||
key: 'video-platform',
|
key: 'video-platform',
|
||||||
@@ -80,9 +83,10 @@ const dropdownOptions: DropdownOption[] = [
|
|||||||
{
|
{
|
||||||
label: '设备配置',
|
label: '设备配置',
|
||||||
key: 'device-config',
|
key: 'device-config',
|
||||||
|
show: hasPermission(station.value.code, PERMISSION_TYPE_LITERALS.OPERATION),
|
||||||
onSelect: openDeviceConfigModal,
|
onSelect: openDeviceConfigModal,
|
||||||
},
|
},
|
||||||
];
|
]);
|
||||||
|
|
||||||
const onSelectDropdownOption = (key: string, option: DropdownOption) => {
|
const onSelectDropdownOption = (key: string, option: DropdownOption) => {
|
||||||
const onSelect = option['onSelect'];
|
const onSelect = option['onSelect'];
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
|
import { usePermission } from '../permission';
|
||||||
import { deleteCameraIgnoreApi, pageCameraIgnoreApi, saveCameraIgnoreApi, updateDeviceAlarmLogApi, type NdmDeviceAlarmLogResultVO } from '@/apis';
|
import { deleteCameraIgnoreApi, pageCameraIgnoreApi, saveCameraIgnoreApi, updateDeviceAlarmLogApi, type NdmDeviceAlarmLogResultVO } from '@/apis';
|
||||||
import { DEVICE_TYPE_LITERALS, tryGetDeviceType } from '@/enums';
|
import { DEVICE_TYPE_LITERALS, PERMISSION_TYPE_LITERALS, tryGetDeviceType } from '@/enums';
|
||||||
import { parseErrorFeedback } from '@/utils';
|
import { parseErrorFeedback } from '@/utils';
|
||||||
import { useMutation } from '@tanstack/vue-query';
|
import { useMutation } from '@tanstack/vue-query';
|
||||||
import { NButton, NFlex, NPopconfirm, type DataTableColumn, type DataTableRowData } from 'naive-ui';
|
import { NButton, NFlex, NPopconfirm, type DataTableColumn, type DataTableRowData } from 'naive-ui';
|
||||||
import { h, type Ref } from 'vue';
|
import { h, type Ref } from 'vue';
|
||||||
|
|
||||||
export const useAlarmActionColumn = (tableData: Ref<DataTableRowData[]>) => {
|
export const useAlarmActionColumn = (tableData: Ref<DataTableRowData[]>) => {
|
||||||
|
const { hasPermission } = usePermission();
|
||||||
|
|
||||||
const { mutate: confirmAlarm } = useMutation({
|
const { mutate: confirmAlarm } = useMutation({
|
||||||
mutationFn: async (params: { id: string | null }) => {
|
mutationFn: async (params: { id: string | null }) => {
|
||||||
const { id } = params;
|
const { id } = params;
|
||||||
@@ -115,7 +118,9 @@ export const useAlarmActionColumn = (tableData: Ref<DataTableRowData[]>) => {
|
|||||||
default: () => '确认告警?',
|
default: () => '确认告警?',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
tryGetDeviceType(rowData.deviceType) === DEVICE_TYPE_LITERALS.ndmCamera && [
|
tryGetDeviceType(rowData.deviceType) === DEVICE_TYPE_LITERALS.ndmCamera &&
|
||||||
|
rowData.stationCode &&
|
||||||
|
hasPermission(rowData.stationCode, PERMISSION_TYPE_LITERALS.OPERATION) && [
|
||||||
h(
|
h(
|
||||||
NPopconfirm,
|
NPopconfirm,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
export * from './alarm';
|
export * from './alarm';
|
||||||
export * from './device';
|
export * from './device';
|
||||||
|
export * from './permission';
|
||||||
export * from './query';
|
export * from './query';
|
||||||
|
export * from './station';
|
||||||
export * from './stomp';
|
export * from './stomp';
|
||||||
|
|||||||
1
src/composables/permission/index.ts
Normal file
1
src/composables/permission/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './use-permission';
|
||||||
15
src/composables/permission/use-permission.ts
Normal file
15
src/composables/permission/use-permission.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import type { Station } from '@/apis';
|
||||||
|
import type { PermissionType } from '@/enums';
|
||||||
|
import { usePermissionStore } from '@/stores';
|
||||||
|
|
||||||
|
export const usePermission = () => {
|
||||||
|
const permissionStore = usePermissionStore();
|
||||||
|
|
||||||
|
const hasPermission = (stationCode: Station['code'], permissionType: PermissionType) => {
|
||||||
|
return !!permissionStore.permissions[stationCode]?.includes(permissionType);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasPermission,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
export * from './use-line-alarms-query';
|
export * from './use-line-alarms-query';
|
||||||
export * from './use-line-devices-query';
|
export * from './use-line-devices-query';
|
||||||
export * from './use-line-stations-query';
|
export * from './use-line-stations-query';
|
||||||
|
export * from './use-user-permissions-query';
|
||||||
export * from './use-verify-user-query';
|
export * from './use-verify-user-query';
|
||||||
export * from './use-version-check-query';
|
export * from './use-version-check-query';
|
||||||
|
|||||||
@@ -17,12 +17,13 @@ export const useLineStationsMutation = () => {
|
|||||||
mutationKey: [LINE_STATIONS_MUTATION_KEY],
|
mutationKey: [LINE_STATIONS_MUTATION_KEY],
|
||||||
mutationFn: async (params: { signal?: AbortSignal }) => {
|
mutationFn: async (params: { signal?: AbortSignal }) => {
|
||||||
const { signal } = params;
|
const { signal } = params;
|
||||||
const { data: ndmStationList } = await axios.get<{ code: string; name: string }[]>(`/minio/ndm/ndm-stations.json?_t=${dayjs().unix()}`, { signal });
|
const { data: ndmStationList } = await axios.get<Omit<Station, 'online' | 'ip'>[]>(`/minio/ndm/ndm-stations.json?_t=${dayjs().unix()}`, { signal });
|
||||||
const stations = ndmStationList.map<Station>((station) => ({
|
const stations = ndmStationList.map<Station>((station) => ({
|
||||||
code: station.code ?? '',
|
code: station.code ?? '',
|
||||||
name: station.name ?? '',
|
name: station.name ?? '',
|
||||||
online: false,
|
online: false,
|
||||||
ip: '',
|
ip: '',
|
||||||
|
occ: station.occ,
|
||||||
}));
|
}));
|
||||||
const verifyList = await batchVerifyApi({ signal });
|
const verifyList = await batchVerifyApi({ signal });
|
||||||
return stations.map((station) => ({
|
return stations.map((station) => ({
|
||||||
|
|||||||
55
src/composables/query/use-user-permissions-query.ts
Normal file
55
src/composables/query/use-user-permissions-query.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { pagePermissionApi } from '@/apis';
|
||||||
|
import { USER_PERMISSIONS_QUERY_KEY } from '@/constants';
|
||||||
|
import { PERMISSION_TYPE_NAMES } from '@/enums';
|
||||||
|
import { usePermissionStore, useSettingStore, useStationStore, useUserStore } from '@/stores';
|
||||||
|
import { useQuery, useQueryClient } from '@tanstack/vue-query';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { computed, watch } from 'vue';
|
||||||
|
|
||||||
|
export const useUserPermissionsQuery = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const settingStore = useSettingStore();
|
||||||
|
const { offlineDev } = storeToRefs(settingStore);
|
||||||
|
|
||||||
|
const stationStore = useStationStore();
|
||||||
|
const { stations } = storeToRefs(stationStore);
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const { userInfo } = storeToRefs(userStore);
|
||||||
|
const permissionStore = usePermissionStore();
|
||||||
|
|
||||||
|
watch(offlineDev, (offline) => {
|
||||||
|
if (offline) {
|
||||||
|
queryClient.cancelQueries({ queryKey: [USER_PERMISSIONS_QUERY_KEY] });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return useQuery({
|
||||||
|
queryKey: computed(() => [USER_PERMISSIONS_QUERY_KEY]),
|
||||||
|
enabled: computed(() => !offlineDev.value),
|
||||||
|
refetchInterval: 10 * 1000,
|
||||||
|
queryFn: async ({ signal }) => {
|
||||||
|
const { employeeId } = userInfo.value ?? {};
|
||||||
|
if (!employeeId) return null;
|
||||||
|
|
||||||
|
const { records } = await pagePermissionApi(
|
||||||
|
{
|
||||||
|
model: {
|
||||||
|
employeeId,
|
||||||
|
},
|
||||||
|
extra: {},
|
||||||
|
current: 1,
|
||||||
|
size: Object.keys(PERMISSION_TYPE_NAMES).length * stations.value.length,
|
||||||
|
sort: 'id',
|
||||||
|
order: 'ascending',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
signal,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
permissionStore.setPermRecords(records);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
1
src/composables/station/index.ts
Normal file
1
src/composables/station/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './use-batch-actions';
|
||||||
120
src/composables/station/use-batch-actions.ts
Normal file
120
src/composables/station/use-batch-actions.ts
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import { usePermission } from '../permission';
|
||||||
|
import { type Station } from '@/apis';
|
||||||
|
import { PERMISSION_TYPE_LITERALS, type PermissionType } from '@/enums';
|
||||||
|
import { computed, ref, watch, type Ref } from 'vue';
|
||||||
|
|
||||||
|
type BatchActionKey = 'export-icmp' | 'export-record' | 'sync-camera' | 'sync-nvr';
|
||||||
|
|
||||||
|
type BatchAction = {
|
||||||
|
label: string;
|
||||||
|
key: BatchActionKey;
|
||||||
|
permission: PermissionType;
|
||||||
|
active: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useBatchActions = (stations: Ref<Station[]>, abortController?: Ref<AbortController | undefined>) => {
|
||||||
|
const { hasPermission } = usePermission();
|
||||||
|
|
||||||
|
const batchActions = ref<BatchAction[]>([
|
||||||
|
{
|
||||||
|
label: '导出设备状态',
|
||||||
|
key: 'export-icmp',
|
||||||
|
permission: PERMISSION_TYPE_LITERALS.VIEW,
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '导出录像诊断',
|
||||||
|
key: 'export-record',
|
||||||
|
permission: PERMISSION_TYPE_LITERALS.VIEW,
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '同步摄像机',
|
||||||
|
key: 'sync-camera',
|
||||||
|
permission: PERMISSION_TYPE_LITERALS.OPERATION,
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '同步录像机通道',
|
||||||
|
key: 'sync-nvr',
|
||||||
|
permission: PERMISSION_TYPE_LITERALS.OPERATION,
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const selectedAction = computed(() => {
|
||||||
|
return batchActions.value.find((action) => action.active);
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectableStations = computed(() => {
|
||||||
|
if (!selectedAction.value) return [];
|
||||||
|
const result: Station[] = [];
|
||||||
|
if (selectedAction.value.permission === PERMISSION_TYPE_LITERALS.VIEW) {
|
||||||
|
result.push(...stations.value.filter((station) => hasPermission(station.code, PERMISSION_TYPE_LITERALS.VIEW)));
|
||||||
|
}
|
||||||
|
if (selectedAction.value.permission === PERMISSION_TYPE_LITERALS.OPERATION) {
|
||||||
|
result.push(...stations.value.filter((station) => hasPermission(station.code, PERMISSION_TYPE_LITERALS.OPERATION)));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
const stationSelection = ref<Record<Station['code'], boolean>>({});
|
||||||
|
|
||||||
|
const toggleSelectAction = (action: BatchAction) => {
|
||||||
|
batchActions.value.forEach((batchAction) => {
|
||||||
|
if (batchAction.key === action.key) {
|
||||||
|
if (batchAction.active) stationSelection.value = {};
|
||||||
|
batchAction.active = !batchAction.active;
|
||||||
|
} else {
|
||||||
|
batchAction.active = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleSelectAllStations = (checked: boolean) => {
|
||||||
|
if (!checked) {
|
||||||
|
stationSelection.value = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectableStations.value.forEach((station) => {
|
||||||
|
stationSelection.value[station.code] = checked;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(selectedAction, () => {
|
||||||
|
toggleSelectAllStations(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
const confirmAction = (callbacks: Record<BatchActionKey, () => void>) => {
|
||||||
|
const { key } = selectedAction.value ?? {};
|
||||||
|
if (!key) return;
|
||||||
|
const noStationSeleted = !Object.values(stationSelection.value).some((selected) => selected);
|
||||||
|
if (noStationSeleted) {
|
||||||
|
window.$message.warning('请选择车站');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callbacks[key]();
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelAction = () => {
|
||||||
|
abortController?.value?.abort();
|
||||||
|
stationSelection.value = {};
|
||||||
|
batchActions.value.forEach((batchAction) => {
|
||||||
|
batchAction.active = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
batchActions,
|
||||||
|
selectedAction,
|
||||||
|
|
||||||
|
selectableStations,
|
||||||
|
stationSelection,
|
||||||
|
|
||||||
|
toggleSelectAction,
|
||||||
|
toggleSelectAllStations,
|
||||||
|
|
||||||
|
confirmAction,
|
||||||
|
cancelAction,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
export const LINE_ALARMS_QUERY_KEY = 'line-alarms';
|
export const LINE_ALARMS_QUERY_KEY = 'line-alarms';
|
||||||
export const LINE_DEVICES_QUERY_KEY = 'line-devices';
|
export const LINE_DEVICES_QUERY_KEY = 'line-devices';
|
||||||
export const LINE_STATIONS_QUERY_KEY = 'line-stations';
|
export const LINE_STATIONS_QUERY_KEY = 'line-stations';
|
||||||
|
export const USER_PERMISSIONS_QUERY_KEY = 'user-permissions';
|
||||||
export const VERIFY_USER_QUERY_KEY = 'verify-user';
|
export const VERIFY_USER_QUERY_KEY = 'verify-user';
|
||||||
export const VERSION_CHECK_QUERY_KEY = 'version-check';
|
export const VERSION_CHECK_QUERY_KEY = 'version-check';
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export const NDM_ALARM_STORE_ID = 'ndm-alarm-store';
|
export const NDM_ALARM_STORE_ID = 'ndm-alarm-store';
|
||||||
export const NDM_DEVICE_STORE_ID = 'ndm-device-store';
|
export const NDM_DEVICE_STORE_ID = 'ndm-device-store';
|
||||||
|
export const NDM_PERMISSION_STORE_ID = 'ndm-permission-store';
|
||||||
export const NDM_POLLIING_STORE_ID = 'ndm-polling-store';
|
export const NDM_POLLIING_STORE_ID = 'ndm-polling-store';
|
||||||
export const NDM_SETTING_STORE_ID = 'ndm-setting-store';
|
export const NDM_SETTING_STORE_ID = 'ndm-setting-store';
|
||||||
export const NDM_STATION_STORE_ID = 'ndm-station-store';
|
export const NDM_STATION_STORE_ID = 'ndm-station-store';
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export * from './alarm-type';
|
export * from './alarm-type';
|
||||||
export * from './device-type';
|
export * from './device-type';
|
||||||
export * from './fault-level';
|
export * from './fault-level';
|
||||||
|
export * from './permission-type';
|
||||||
|
|||||||
13
src/enums/permission-type.ts
Normal file
13
src/enums/permission-type.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export const PERMISSION_TYPE_LITERALS = {
|
||||||
|
VIEW: 'VIEW',
|
||||||
|
OPERATION: 'OPERATION',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type PermissionType = keyof typeof PERMISSION_TYPE_LITERALS;
|
||||||
|
|
||||||
|
export const PERMISSION_TYPE_NAMES = {
|
||||||
|
[PERMISSION_TYPE_LITERALS.VIEW]: '查看',
|
||||||
|
[PERMISSION_TYPE_LITERALS.OPERATION]: '操作',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type PermissionTypeEnum = typeof PERMISSION_TYPE_NAMES;
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { SettingsDrawer, SyncCameraResultModal } from '@/components';
|
import { SettingsDrawer, SyncCameraResultModal } from '@/components';
|
||||||
import { useLineStationsQuery, useStompClient, useVerifyUserQuery } from '@/composables';
|
import { useLineStationsQuery, useStompClient, useUserPermissionsQuery, useVerifyUserQuery } from '@/composables';
|
||||||
import { LINE_ALARMS_QUERY_KEY, LINE_DEVICES_QUERY_KEY, LINE_STATIONS_MUTATION_KEY, LINE_STATIONS_QUERY_KEY, STATION_ALARMS_MUTATION_KEY, STATION_DEVICES_MUTATION_KEY } from '@/constants';
|
import { LINE_ALARMS_QUERY_KEY, LINE_DEVICES_QUERY_KEY, LINE_STATIONS_MUTATION_KEY, LINE_STATIONS_QUERY_KEY, STATION_ALARMS_MUTATION_KEY, STATION_DEVICES_MUTATION_KEY } from '@/constants';
|
||||||
import { useAlarmStore, useSettingStore, useUserStore } from '@/stores';
|
import { useAlarmStore, useSettingStore, useUserStore } from '@/stores';
|
||||||
import { parseErrorFeedback } from '@/utils';
|
import { parseErrorFeedback } from '@/utils';
|
||||||
import { useIsFetching, useIsMutating, useMutation } from '@tanstack/vue-query';
|
import { useIsFetching, useIsMutating, useMutation } from '@tanstack/vue-query';
|
||||||
import { isCancel } from 'axios';
|
import { isCancel } from 'axios';
|
||||||
import { ChevronDownIcon, ChevronsLeftIcon, ChevronsRightIcon, ComputerIcon, LogOutIcon, LogsIcon, MapPinIcon, SettingsIcon, SirenIcon } from 'lucide-vue-next';
|
import { ChevronDownIcon, ChevronsLeftIcon, ChevronsRightIcon, ComputerIcon, KeyIcon, LogOutIcon, LogsIcon, MapPinIcon, SettingsIcon, SirenIcon } from 'lucide-vue-next';
|
||||||
import {
|
import {
|
||||||
NBadge,
|
NBadge,
|
||||||
NButton,
|
NButton,
|
||||||
@@ -43,6 +43,7 @@ const { syncCameraResult, afterCheckSyncCameraResult } = useStompClient();
|
|||||||
|
|
||||||
useVerifyUserQuery();
|
useVerifyUserQuery();
|
||||||
useLineStationsQuery();
|
useLineStationsQuery();
|
||||||
|
useUserPermissionsQuery();
|
||||||
|
|
||||||
// 全局loading状态依赖于轮询query的queryKey以及相关的mutationKey
|
// 全局loading状态依赖于轮询query的queryKey以及相关的mutationKey
|
||||||
const queryingCount = useIsFetching({
|
const queryingCount = useIsFetching({
|
||||||
@@ -106,6 +107,12 @@ const menuOptions: MenuOption[] = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: () => h(RouterLink, { to: '/permission' }, { default: () => '权限管理' }),
|
||||||
|
key: '/permission',
|
||||||
|
show: userStore.isLamp,
|
||||||
|
icon: renderIcon(KeyIcon),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const dropdownOptions: DropdownOption[] = [
|
const dropdownOptions: DropdownOption[] = [
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ const NDM_TYPES: Record<string, DeviceType> = {
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { deleteCameraIgnoreApi, pageCameraIgnoreApi, type NdmCameraIgnore, type NdmCameraIgnoreResultVO, type PageQueryExtra, type Station } from '@/apis';
|
import { deleteCameraIgnoreApi, pageCameraIgnoreApi, type NdmCameraIgnore, type NdmCameraIgnoreResultVO, type PageQueryExtra, type Station } from '@/apis';
|
||||||
import { DEVICE_TYPE_LITERALS, DEVICE_TYPE_NAMES, type DeviceType } from '@/enums';
|
import { usePermission } from '@/composables';
|
||||||
import { useDeviceStore, useStationStore } from '@/stores';
|
import { DEVICE_TYPE_LITERALS, DEVICE_TYPE_NAMES, PERMISSION_TYPE_LITERALS, type DeviceType } from '@/enums';
|
||||||
|
import { useDeviceStore, usePermissionStore } from '@/stores';
|
||||||
import { useMutation } from '@tanstack/vue-query';
|
import { useMutation } from '@tanstack/vue-query';
|
||||||
import { isCancel } from 'axios';
|
import { isCancel } from 'axios';
|
||||||
import {
|
import {
|
||||||
@@ -45,11 +46,14 @@ interface SearchFields extends PageQueryExtra<NdmCameraIgnore> {
|
|||||||
stationCode?: Station['code'];
|
stationCode?: Station['code'];
|
||||||
// deviceId_like?: string;
|
// deviceId_like?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stationStore = useStationStore();
|
|
||||||
const { stations } = storeToRefs(stationStore);
|
|
||||||
const deviceStore = useDeviceStore();
|
const deviceStore = useDeviceStore();
|
||||||
const { lineDevices } = storeToRefs(deviceStore);
|
const { lineDevices } = storeToRefs(deviceStore);
|
||||||
|
const permissionStore = usePermissionStore();
|
||||||
|
const { permissions } = storeToRefs(permissionStore);
|
||||||
|
|
||||||
|
const { hasPermission } = usePermission();
|
||||||
|
|
||||||
|
const stations = computed(() => permissionStore.stations.VIEW ?? []);
|
||||||
|
|
||||||
const stationSelectOptions = computed<SelectOption[]>(() => {
|
const stationSelectOptions = computed<SelectOption[]>(() => {
|
||||||
return stations.value.map((station) => ({
|
return stations.value.map((station) => ({
|
||||||
@@ -64,6 +68,14 @@ const stationSelectOptions = computed<SelectOption[]>(() => {
|
|||||||
// }));
|
// }));
|
||||||
// });
|
// });
|
||||||
|
|
||||||
|
// 权限变化时,需要刷新表格数据
|
||||||
|
watch(permissions, (newPermissions, oldPermissions) => {
|
||||||
|
const oldPermissionsJson = JSON.stringify(oldPermissions);
|
||||||
|
const newPermissionsJson = JSON.stringify(newPermissions);
|
||||||
|
if (oldPermissionsJson === newPermissionsJson) return;
|
||||||
|
onClickReset();
|
||||||
|
});
|
||||||
|
|
||||||
const searchFields = ref<SearchFields>({});
|
const searchFields = ref<SearchFields>({});
|
||||||
const resetSearchFields = () => {
|
const resetSearchFields = () => {
|
||||||
searchFields.value = {};
|
searchFields.value = {};
|
||||||
@@ -84,7 +96,7 @@ watch(searchFields, () => {
|
|||||||
searchFieldsChanged.value = true;
|
searchFieldsChanged.value = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
const tableColumns: DataTableColumns<NdmCameraIgnoreResultVO> = [
|
const tableColumns = computed<DataTableColumns<NdmCameraIgnoreResultVO>>(() => [
|
||||||
{ title: '忽略时间', key: 'createdTime', align: 'center' },
|
{ title: '忽略时间', key: 'createdTime', align: 'center' },
|
||||||
// { title: '更新时间', key: 'updatedTime' },
|
// { title: '更新时间', key: 'updatedTime' },
|
||||||
{
|
{
|
||||||
@@ -142,6 +154,11 @@ const tableColumns: DataTableColumns<NdmCameraIgnoreResultVO> = [
|
|||||||
align: 'center',
|
align: 'center',
|
||||||
width: 120,
|
width: 120,
|
||||||
render: (rowData) => {
|
render: (rowData) => {
|
||||||
|
const { deviceId } = rowData;
|
||||||
|
if (!deviceId) return null;
|
||||||
|
const stationCode = deviceId.slice(0, 4);
|
||||||
|
if (!stationCode) return null;
|
||||||
|
if (!hasPermission(stationCode, PERMISSION_TYPE_LITERALS.OPERATION)) return null;
|
||||||
return h(
|
return h(
|
||||||
NPopconfirm,
|
NPopconfirm,
|
||||||
{
|
{
|
||||||
@@ -167,7 +184,7 @@ const tableColumns: DataTableColumns<NdmCameraIgnoreResultVO> = [
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
]);
|
||||||
|
|
||||||
const { mutate: cancelIgnore } = useMutation({
|
const { mutate: cancelIgnore } = useMutation({
|
||||||
mutationFn: async (params: { id?: string; signal?: AbortSignal }) => {
|
mutationFn: async (params: { id?: string; signal?: AbortSignal }) => {
|
||||||
@@ -3,7 +3,7 @@ import { exportDeviceAlarmLogApi, pageDeviceAlarmLogApi, type NdmDeviceAlarmLog,
|
|||||||
import { useAlarmActionColumn, useCameraSnapColumn } from '@/composables';
|
import { useAlarmActionColumn, useCameraSnapColumn } from '@/composables';
|
||||||
import { ALARM_TYPES, DEVICE_TYPE_CODES, DEVICE_TYPE_LITERALS, DEVICE_TYPE_NAMES, FAULT_LEVELS, tryGetDeviceType, type DeviceType } from '@/enums';
|
import { ALARM_TYPES, DEVICE_TYPE_CODES, DEVICE_TYPE_LITERALS, DEVICE_TYPE_NAMES, FAULT_LEVELS, tryGetDeviceType, type DeviceType } from '@/enums';
|
||||||
import { renderAlarmDateCell, renderAlarmTypeCell, renderDeviceTypeCell, renderFaultLevelCell } from '@/helpers';
|
import { renderAlarmDateCell, renderAlarmTypeCell, renderDeviceTypeCell, renderFaultLevelCell } from '@/helpers';
|
||||||
import { useAlarmStore, useDeviceStore, useStationStore } from '@/stores';
|
import { useAlarmStore, useDeviceStore, usePermissionStore } from '@/stores';
|
||||||
import { downloadByData, parseErrorFeedback } from '@/utils';
|
import { downloadByData, parseErrorFeedback } from '@/utils';
|
||||||
import { useMutation } from '@tanstack/vue-query';
|
import { useMutation } from '@tanstack/vue-query';
|
||||||
import { watchDebounced } from '@vueuse/core';
|
import { watchDebounced } from '@vueuse/core';
|
||||||
@@ -42,12 +42,14 @@ interface SearchFields extends PageQueryExtra<NdmDeviceAlarmLog> {
|
|||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const stationStore = useStationStore();
|
|
||||||
const { stations } = storeToRefs(stationStore);
|
|
||||||
const deviceStore = useDeviceStore();
|
const deviceStore = useDeviceStore();
|
||||||
const { lineDevices } = storeToRefs(deviceStore);
|
const { lineDevices } = storeToRefs(deviceStore);
|
||||||
const alarmStore = useAlarmStore();
|
const alarmStore = useAlarmStore();
|
||||||
const { unreadAlarmCount } = storeToRefs(alarmStore);
|
const { unreadAlarmCount } = storeToRefs(alarmStore);
|
||||||
|
const permissionStore = usePermissionStore();
|
||||||
|
const { permissions } = storeToRefs(permissionStore);
|
||||||
|
|
||||||
|
const stations = computed(() => permissionStore.stations.VIEW ?? []);
|
||||||
|
|
||||||
const stationSelectOptions = computed<SelectOption[]>(() => {
|
const stationSelectOptions = computed<SelectOption[]>(() => {
|
||||||
return stations.value.map((station) => ({
|
return stations.value.map((station) => ({
|
||||||
@@ -74,6 +76,14 @@ const faultLevelSelectOptions = computed<SelectOption[]>(() => {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 权限变化时,需要刷新表格数据
|
||||||
|
watch(permissions, (newPermissions, oldPermissions) => {
|
||||||
|
const oldPermissionsJson = JSON.stringify(oldPermissions);
|
||||||
|
const newPermissionsJson = JSON.stringify(newPermissions);
|
||||||
|
if (oldPermissionsJson === newPermissionsJson) return;
|
||||||
|
onClickReset();
|
||||||
|
});
|
||||||
|
|
||||||
// 未读告警数量被清零时,代表从别的页面跳转过来,需要刷新告警表格数据
|
// 未读告警数量被清零时,代表从别的页面跳转过来,需要刷新告警表格数据
|
||||||
const unreadCountCleared = computed(() => unreadAlarmCount.value === 0);
|
const unreadCountCleared = computed(() => unreadAlarmCount.value === 0);
|
||||||
watch(unreadCountCleared, (newValue, oldValue) => {
|
watch(unreadCountCleared, (newValue, oldValue) => {
|
||||||
@@ -117,14 +127,17 @@ const resetSearchFields = () => {
|
|||||||
const getExtraFields = (): PageQueryExtra<NdmDeviceAlarmLog> => {
|
const getExtraFields = (): PageQueryExtra<NdmDeviceAlarmLog> => {
|
||||||
const stationCodeIn = searchFields.value.stationCode_in;
|
const stationCodeIn = searchFields.value.stationCode_in;
|
||||||
const deviceTypeIn = searchFields.value.deviceType_in.flatMap((deviceType) => DEVICE_TYPE_CODES[deviceType as DeviceType]);
|
const deviceTypeIn = searchFields.value.deviceType_in.flatMap((deviceType) => DEVICE_TYPE_CODES[deviceType as DeviceType]);
|
||||||
|
const deviceNameLike = searchFields.value.deviceName_like;
|
||||||
|
const alarmTypeIn = searchFields.value.alarmType_in;
|
||||||
|
const faultLevelIn = searchFields.value.faultLevel_in;
|
||||||
const alarmDateGe = searchFields.value.alarmDate[0];
|
const alarmDateGe = searchFields.value.alarmDate[0];
|
||||||
const alarmDateLe = searchFields.value.alarmDate[1];
|
const alarmDateLe = searchFields.value.alarmDate[1];
|
||||||
return {
|
return {
|
||||||
stationCode_in: stationCodeIn ? (stationCodeIn.length > 0 ? [...stationCodeIn] : undefined) : undefined,
|
stationCode_in: stationCodeIn.length > 0 ? [...stationCodeIn] : stations.value.map((station) => station.code),
|
||||||
deviceType_in: deviceTypeIn ? (deviceTypeIn.length > 0 ? [...deviceTypeIn] : undefined) : undefined,
|
deviceType_in: deviceTypeIn.length > 0 ? [...deviceTypeIn] : undefined,
|
||||||
deviceName_like: !!searchFields.value.deviceName_like ? searchFields.value.deviceName_like : undefined,
|
deviceName_like: deviceNameLike.length > 0 ? deviceNameLike : undefined,
|
||||||
alarmType_in: searchFields.value.alarmType_in.length > 0 ? [...searchFields.value.alarmType_in] : undefined,
|
alarmType_in: alarmTypeIn.length > 0 ? [...alarmTypeIn] : undefined,
|
||||||
faultLevel_in: searchFields.value.faultLevel_in.length > 0 ? [...searchFields.value.faultLevel_in] : undefined,
|
faultLevel_in: faultLevelIn.length > 0 ? [...faultLevelIn] : undefined,
|
||||||
alarmDate_ge: alarmDateGe,
|
alarmDate_ge: alarmDateGe,
|
||||||
alarmDate_le: alarmDateLe,
|
alarmDate_le: alarmDateLe,
|
||||||
};
|
};
|
||||||
@@ -3,13 +3,13 @@ import type { NdmDeviceResultVO, Station } from '@/apis';
|
|||||||
import { DeviceRenderer, DeviceTree, type DeviceTreeProps } from '@/components';
|
import { DeviceRenderer, DeviceTree, type DeviceTreeProps } from '@/components';
|
||||||
import type { UseDeviceSelectionReturn } from '@/composables';
|
import type { UseDeviceSelectionReturn } from '@/composables';
|
||||||
import { SELECT_DEVICE_FN_INJECTION_KEY } from '@/constants';
|
import { SELECT_DEVICE_FN_INJECTION_KEY } from '@/constants';
|
||||||
import { useStationStore } from '@/stores';
|
import { usePermissionStore } from '@/stores';
|
||||||
import { NLayout, NLayoutContent, NLayoutSider } from 'naive-ui';
|
import { NLayout, NLayoutContent, NLayoutSider } from 'naive-ui';
|
||||||
import { storeToRefs } from 'pinia';
|
import { computed, provide, ref } from 'vue';
|
||||||
import { provide, ref } from 'vue';
|
|
||||||
|
|
||||||
const stationStore = useStationStore();
|
const permissionStore = usePermissionStore();
|
||||||
const { stations } = storeToRefs(stationStore);
|
|
||||||
|
const stations = computed(() => permissionStore.stations.VIEW ?? []);
|
||||||
|
|
||||||
const selectedStation = ref<Station>();
|
const selectedStation = ref<Station>();
|
||||||
const selectedDevice = ref<NdmDeviceResultVO>();
|
const selectedDevice = ref<NdmDeviceResultVO>();
|
||||||
@@ -32,7 +32,7 @@ const callLogTypeOptions: SelectOption[] = [
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { exportCallLogApi, pageCallLogApi, type NdmCallLog, type NdmCallLogResultVO, type PageQueryExtra, type Station } from '@/apis';
|
import { exportCallLogApi, pageCallLogApi, type NdmCallLog, type NdmCallLogResultVO, type PageQueryExtra, type Station } from '@/apis';
|
||||||
import { useStationStore } from '@/stores';
|
import { usePermissionStore } from '@/stores';
|
||||||
import { downloadByData, parseErrorFeedback } from '@/utils';
|
import { downloadByData, parseErrorFeedback } from '@/utils';
|
||||||
import { useMutation } from '@tanstack/vue-query';
|
import { useMutation } from '@tanstack/vue-query';
|
||||||
import { isCancel } from 'axios';
|
import { isCancel } from 'axios';
|
||||||
@@ -63,8 +63,11 @@ interface SearchFields extends PageQueryExtra<NdmCallLog> {
|
|||||||
createdTime: [string, string];
|
createdTime: [string, string];
|
||||||
}
|
}
|
||||||
|
|
||||||
const stationStore = useStationStore();
|
const permissionStore = usePermissionStore();
|
||||||
const { stations, onlineStations } = storeToRefs(stationStore);
|
const { permissions } = storeToRefs(permissionStore);
|
||||||
|
|
||||||
|
const stations = computed(() => permissionStore.stations.VIEW ?? []);
|
||||||
|
const onlineStations = computed(() => stations.value.filter((station) => station.online));
|
||||||
|
|
||||||
const stationSelectOptions = computed(() => {
|
const stationSelectOptions = computed(() => {
|
||||||
return stations.value.map<SelectOption>((station) => ({
|
return stations.value.map<SelectOption>((station) => ({
|
||||||
@@ -74,6 +77,14 @@ const stationSelectOptions = computed(() => {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 权限变化时,需要刷新表格数据
|
||||||
|
watch(permissions, (newPermissions, oldPermissions) => {
|
||||||
|
const oldPermissionsJson = JSON.stringify(oldPermissions);
|
||||||
|
const newPermissionsJson = JSON.stringify(newPermissions);
|
||||||
|
if (oldPermissionsJson === newPermissionsJson) return;
|
||||||
|
onClickReset();
|
||||||
|
});
|
||||||
|
|
||||||
const searchFields = ref<SearchFields>({
|
const searchFields = ref<SearchFields>({
|
||||||
logType_in: [],
|
logType_in: [],
|
||||||
createdTime: [dayjs().startOf('date').subtract(1, 'week').format('YYYY-MM-DD HH:mm:ss'), dayjs().endOf('date').format('YYYY-MM-DD HH:mm:ss')],
|
createdTime: [dayjs().startOf('date').subtract(1, 'week').format('YYYY-MM-DD HH:mm:ss'), dayjs().endOf('date').format('YYYY-MM-DD HH:mm:ss')],
|
||||||
@@ -29,7 +29,7 @@ const vimpLogTypeOptions: SelectOption[] = [
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { exportVimpLogApi, pageVimpLogApi, type NdmVimpLog, type NdmVimpLogResultVO, type PageQueryExtra, type Station } from '@/apis';
|
import { exportVimpLogApi, pageVimpLogApi, type NdmVimpLog, type NdmVimpLogResultVO, type PageQueryExtra, type Station } from '@/apis';
|
||||||
import { useStationStore } from '@/stores';
|
import { usePermissionStore } from '@/stores';
|
||||||
import { downloadByData, parseErrorFeedback } from '@/utils';
|
import { downloadByData, parseErrorFeedback } from '@/utils';
|
||||||
import { useMutation } from '@tanstack/vue-query';
|
import { useMutation } from '@tanstack/vue-query';
|
||||||
import { isCancel } from 'axios';
|
import { isCancel } from 'axios';
|
||||||
@@ -59,8 +59,11 @@ interface SearchFields extends PageQueryExtra<NdmVimpLog> {
|
|||||||
createdTime: [string, string];
|
createdTime: [string, string];
|
||||||
}
|
}
|
||||||
|
|
||||||
const stationStore = useStationStore();
|
const permissionStore = usePermissionStore();
|
||||||
const { stations, onlineStations } = storeToRefs(stationStore);
|
const { permissions } = storeToRefs(permissionStore);
|
||||||
|
|
||||||
|
const stations = computed(() => permissionStore.stations.VIEW ?? []);
|
||||||
|
const onlineStations = computed(() => stations.value.filter((station) => station.online));
|
||||||
|
|
||||||
const stationSelectOptions = computed(() => {
|
const stationSelectOptions = computed(() => {
|
||||||
return stations.value.map<SelectOption>((station) => ({
|
return stations.value.map<SelectOption>((station) => ({
|
||||||
@@ -70,6 +73,14 @@ const stationSelectOptions = computed(() => {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 权限变化时,需要刷新表格数据
|
||||||
|
watch(permissions, (newPermissions, oldPermissions) => {
|
||||||
|
const oldPermissionsJson = JSON.stringify(oldPermissions);
|
||||||
|
const newPermissionsJson = JSON.stringify(newPermissions);
|
||||||
|
if (oldPermissionsJson === newPermissionsJson) return;
|
||||||
|
onClickReset();
|
||||||
|
});
|
||||||
|
|
||||||
const searchFields = ref<SearchFields>({
|
const searchFields = ref<SearchFields>({
|
||||||
logType_in: [],
|
logType_in: [],
|
||||||
createdTime: [dayjs().startOf('date').subtract(1, 'week').format('YYYY-MM-DD HH:mm:ss'), dayjs().endOf('date').format('YYYY-MM-DD HH:mm:ss')] as [string, string],
|
createdTime: [dayjs().startOf('date').subtract(1, 'week').format('YYYY-MM-DD HH:mm:ss'), dayjs().endOf('date').format('YYYY-MM-DD HH:mm:ss')] as [string, string],
|
||||||
184
src/pages/permission/permission-page.vue
Normal file
184
src/pages/permission/permission-page.vue
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { pageBaseEmployeeApi, type BaseEmployeePageQuery, type BaseEmployeeResultVO } from '@/apis';
|
||||||
|
import { PermissionConfigModal } from '@/components';
|
||||||
|
import { parseErrorFeedback } from '@/utils';
|
||||||
|
import { useMutation } from '@tanstack/vue-query';
|
||||||
|
import { isCancel } from 'axios';
|
||||||
|
import { KeyIcon } from 'lucide-vue-next';
|
||||||
|
import { NButton, NDataTable, NFlex, NForm, NFormItemGi, NGrid, NGridItem, NInput, type DataTableColumns, type DataTableRowData, type PaginationProps } from 'naive-ui';
|
||||||
|
import { h, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
interface SearchFields extends BaseEmployeePageQuery {}
|
||||||
|
|
||||||
|
const searchFields = ref<SearchFields>({});
|
||||||
|
const resetSearchFields = () => {
|
||||||
|
searchFields.value = {
|
||||||
|
realName: '',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const getModelFields = (): BaseEmployeePageQuery => {
|
||||||
|
return {
|
||||||
|
realName: searchFields.value.realName,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchFieldsChanged = ref(false);
|
||||||
|
watch(searchFields, () => {
|
||||||
|
searchFieldsChanged.value = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const showPermissionConfigModal = ref(false);
|
||||||
|
const selectedEmployeeId = ref('');
|
||||||
|
|
||||||
|
const tableColumns: DataTableColumns<BaseEmployeeResultVO> = [
|
||||||
|
{ title: '姓名', key: 'realName', align: 'center' },
|
||||||
|
{ title: '创建时间', key: 'createdTime', align: 'center' },
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
align: 'center',
|
||||||
|
width: 150,
|
||||||
|
render: (rowData) => {
|
||||||
|
return h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
secondary: true,
|
||||||
|
type: 'info',
|
||||||
|
size: 'small',
|
||||||
|
onClick: () => {
|
||||||
|
const { id } = rowData;
|
||||||
|
if (!id) return;
|
||||||
|
selectedEmployeeId.value = id;
|
||||||
|
showPermissionConfigModal.value = true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: () => h(KeyIcon),
|
||||||
|
default: () => '配置权限',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const tableData = ref<DataTableRowData[]>([]);
|
||||||
|
|
||||||
|
const DEFAULT_PAGE_SIZE = 10;
|
||||||
|
const pagination = reactive<PaginationProps>({
|
||||||
|
showSizePicker: true,
|
||||||
|
page: 1,
|
||||||
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
|
pageSizes: [5, 10, 20, 50, 80, 100],
|
||||||
|
itemCount: 0,
|
||||||
|
prefix: ({ itemCount }) => {
|
||||||
|
return h('div', {}, { default: () => `共${itemCount}条` });
|
||||||
|
},
|
||||||
|
onUpdatePage: (page: number) => {
|
||||||
|
pagination.page = page;
|
||||||
|
getTableData();
|
||||||
|
},
|
||||||
|
onUpdatePageSize: (pageSize: number) => {
|
||||||
|
pagination.pageSize = pageSize;
|
||||||
|
pagination.page = 1;
|
||||||
|
getTableData();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const abortController = ref(new AbortController());
|
||||||
|
|
||||||
|
const { mutate: getTableData, isPending: tableLoading } = useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
abortController.value.abort();
|
||||||
|
abortController.value = new AbortController();
|
||||||
|
|
||||||
|
const signal = abortController.value.signal;
|
||||||
|
|
||||||
|
const res = await pageBaseEmployeeApi(
|
||||||
|
{
|
||||||
|
model: getModelFields(),
|
||||||
|
extra: {},
|
||||||
|
current: pagination.page ?? 1,
|
||||||
|
size: pagination.pageSize ?? DEFAULT_PAGE_SIZE,
|
||||||
|
order: 'descending',
|
||||||
|
sort: 'id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
signal,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
onSuccess: (res) => {
|
||||||
|
const { records, size, total } = res;
|
||||||
|
pagination.pageSize = parseInt(size);
|
||||||
|
pagination.itemCount = parseInt(total);
|
||||||
|
tableData.value = records;
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
if (isCancel(error)) return;
|
||||||
|
console.error(error);
|
||||||
|
const errorFeedback = parseErrorFeedback(error);
|
||||||
|
window.$message.error(errorFeedback);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const onClickReset = () => {
|
||||||
|
resetSearchFields();
|
||||||
|
pagination.page = 1;
|
||||||
|
pagination.pageSize = DEFAULT_PAGE_SIZE;
|
||||||
|
pagination.itemCount = 0;
|
||||||
|
getTableData();
|
||||||
|
};
|
||||||
|
const onClickQuery = () => {
|
||||||
|
if (searchFieldsChanged.value) {
|
||||||
|
pagination.page = 1;
|
||||||
|
pagination.pageSize = DEFAULT_PAGE_SIZE;
|
||||||
|
searchFieldsChanged.value = false;
|
||||||
|
}
|
||||||
|
getTableData();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getTableData();
|
||||||
|
});
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
abortController.value.abort();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NFlex vertical :size="0" style="height: 100%">
|
||||||
|
<!-- 查询面板 -->
|
||||||
|
<NForm style="flex: 0 0 auto; padding: 8px">
|
||||||
|
<NGrid cols="3" :x-gap="24">
|
||||||
|
<NFormItemGi span="1" label="姓名" label-placement="left">
|
||||||
|
<NInput v-model:value="searchFields.realName" />
|
||||||
|
</NFormItemGi>
|
||||||
|
</NGrid>
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<NGrid :cols="1">
|
||||||
|
<NGridItem>
|
||||||
|
<NFlex>
|
||||||
|
<NButton @click="onClickReset">重置</NButton>
|
||||||
|
<NButton type="primary" :loading="tableLoading" @click="onClickQuery">查询</NButton>
|
||||||
|
</NFlex>
|
||||||
|
</NGridItem>
|
||||||
|
</NGrid>
|
||||||
|
</NForm>
|
||||||
|
|
||||||
|
<!-- 数据表格工具栏 -->
|
||||||
|
<NFlex align="center" style="padding: 8px; flex: 0 0 auto">
|
||||||
|
<div style="font-size: medium">用户权限列表</div>
|
||||||
|
<NFlex style="margin-left: auto">
|
||||||
|
<!-- <NButton type="primary" :loading="exporting" @click="() => exportTableData()">导出</NButton> -->
|
||||||
|
</NFlex>
|
||||||
|
</NFlex>
|
||||||
|
|
||||||
|
<!-- 数据表格 -->
|
||||||
|
<NDataTable remote :columns="tableColumns" :data="tableData" :pagination="pagination" :loading="tableLoading" :single-line="false" flex-height style="height: 100%; padding: 8px; flex: 1 1 auto" />
|
||||||
|
</NFlex>
|
||||||
|
|
||||||
|
<PermissionConfigModal v-model:show="showPermissionConfigModal" :employee-id="selectedEmployeeId" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { initStationAlarms, initStationDevices, syncCameraApi, syncNvrChannelsApi, type Station } from '@/apis';
|
|
||||||
import { AlarmDetailModal, DeviceDetailModal, DeviceParamConfigModal, IcmpExportModal, RecordCheckExportModal, StationCard, type StationCardProps } from '@/components';
|
|
||||||
import { useLineDevicesQuery } from '@/composables';
|
|
||||||
import { useAlarmStore, useDeviceStore, useSettingStore, useStationStore } from '@/stores';
|
|
||||||
import { parseErrorFeedback } from '@/utils';
|
|
||||||
import { useMutation } from '@tanstack/vue-query';
|
|
||||||
import { NButton, NButtonGroup, NCheckbox, NFlex, NGrid, NGridItem, NScrollbar } from 'naive-ui';
|
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
import { computed, ref } from 'vue';
|
|
||||||
|
|
||||||
const settingStore = useSettingStore();
|
|
||||||
const { stationGridCols: stationGridColumns } = storeToRefs(settingStore);
|
|
||||||
const stationStore = useStationStore();
|
|
||||||
const { stations } = storeToRefs(stationStore);
|
|
||||||
const deviceStore = useDeviceStore();
|
|
||||||
const { lineDevices } = storeToRefs(deviceStore);
|
|
||||||
const alarmStore = useAlarmStore();
|
|
||||||
const { lineAlarms } = storeToRefs(alarmStore);
|
|
||||||
|
|
||||||
const { refetch: refetchLineDevicesQuery } = useLineDevicesQuery();
|
|
||||||
|
|
||||||
// 操作栏
|
|
||||||
// 当点击操作栏中的一个按钮时,其他按钮会被禁用
|
|
||||||
type Action = 'export-icmp' | 'export-record' | 'sync-camera' | 'sync-nvr' | null;
|
|
||||||
const selectedAction = ref<Action>(null);
|
|
||||||
const showOperation = ref(false);
|
|
||||||
const stationSelectable = ref(false);
|
|
||||||
const stationSelection = ref<Record<Station['code'], boolean>>({});
|
|
||||||
const showIcmpExportModal = ref(false);
|
|
||||||
const showRecordCheckExportModal = ref(false);
|
|
||||||
|
|
||||||
const onToggleSelectAll = (checked: boolean) => {
|
|
||||||
if (!checked) {
|
|
||||||
stationSelection.value = {};
|
|
||||||
} else {
|
|
||||||
stationSelection.value = Object.fromEntries(stations.value.map((station) => [station.code, true]));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onAction = (action: Action) => {
|
|
||||||
selectedAction.value = action;
|
|
||||||
if (action === null) return;
|
|
||||||
showOperation.value = true;
|
|
||||||
stationSelectable.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onCancel = () => {
|
|
||||||
selectedAction.value = null;
|
|
||||||
showOperation.value = false;
|
|
||||||
stationSelectable.value = false;
|
|
||||||
stationSelection.value = {};
|
|
||||||
};
|
|
||||||
|
|
||||||
const onFinish = onCancel;
|
|
||||||
|
|
||||||
const { mutate: syncCamera, isPending: cameraSyncing } = useMutation({
|
|
||||||
mutationFn: async () => {
|
|
||||||
const stationCodes = Object.entries(stationSelection.value)
|
|
||||||
.filter(([, selected]) => selected)
|
|
||||||
.map(([code]) => code);
|
|
||||||
const results = await Promise.allSettled(stationCodes.map((stationCode) => syncCameraApi({ stationCode })));
|
|
||||||
return results.map((result, index) => ({ ...result, stationCode: stationCodes[index] }));
|
|
||||||
},
|
|
||||||
onSuccess: (results) => {
|
|
||||||
const successCount = results.filter((result) => result.status === 'fulfilled').length;
|
|
||||||
const failures = results.filter((result) => result.status === 'rejected');
|
|
||||||
const failureCount = failures.length;
|
|
||||||
if (failureCount > 0) {
|
|
||||||
const failedStations = failures.map((f) => stations.value.find((s) => s.code === f.stationCode)?.name).join('、');
|
|
||||||
if (successCount === 0) {
|
|
||||||
window.$message.error('摄像机同步全部失败');
|
|
||||||
window.$message.error(`${failedStations}`);
|
|
||||||
} else {
|
|
||||||
window.$message.warning(`摄像机同步完成:成功${successCount}个车站,失败${failureCount}个车站`);
|
|
||||||
window.$message.warning(`${failedStations}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
window.$message.success('摄像机同步成功');
|
|
||||||
}
|
|
||||||
if (successCount > 0) {
|
|
||||||
// 摄像机同步后,需要重新查询一次设备,待测试
|
|
||||||
refetchLineDevicesQuery();
|
|
||||||
}
|
|
||||||
onFinish();
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
console.error(error);
|
|
||||||
const errorFeedback = parseErrorFeedback(error);
|
|
||||||
window.$message.error(errorFeedback);
|
|
||||||
onCancel();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { mutate: syncNvrChannels, isPending: nvrChannelsSyncing } = useMutation({
|
|
||||||
mutationFn: async () => {
|
|
||||||
const stationCodes = Object.entries(stationSelection.value)
|
|
||||||
.filter(([, selected]) => selected)
|
|
||||||
.map(([code]) => code);
|
|
||||||
const results = await Promise.allSettled(stationCodes.map((stationCode) => syncNvrChannelsApi({ stationCode })));
|
|
||||||
return results.map((result, index) => ({ ...result, stationCode: stationCodes[index] }));
|
|
||||||
},
|
|
||||||
onSuccess: (results) => {
|
|
||||||
const successCount = results.filter((result) => result.status === 'fulfilled').length;
|
|
||||||
const failures = results.filter((result) => result.status === 'rejected');
|
|
||||||
const failureCount = failures.length;
|
|
||||||
if (failureCount > 0) {
|
|
||||||
const failedStations = failures.map((failure) => stations.value.find((station) => station.code === failure.stationCode)?.name).join('、');
|
|
||||||
if (successCount === 0) {
|
|
||||||
window.$message.error('录像机通道同步全部失败');
|
|
||||||
window.$message.error(`${failedStations}`);
|
|
||||||
} else {
|
|
||||||
window.$message.warning(`录像机通道同步完成:成功${successCount}个车站,失败${failureCount}个车站`);
|
|
||||||
window.$message.warning(`${failedStations}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
window.$message.success('录像机通道同步成功');
|
|
||||||
}
|
|
||||||
onFinish();
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
console.error(error);
|
|
||||||
const errorFeedback = parseErrorFeedback(error);
|
|
||||||
window.$message.error(errorFeedback);
|
|
||||||
onCancel();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const confirming = computed(() => cameraSyncing.value || nvrChannelsSyncing.value);
|
|
||||||
|
|
||||||
const onConfirm = async () => {
|
|
||||||
const noStationSelected = !Object.values(stationSelection.value).some((selected) => selected);
|
|
||||||
if (selectedAction.value === 'export-icmp') {
|
|
||||||
if (noStationSelected) {
|
|
||||||
window.$message.warning('请选择要导出设备状态的车站');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showIcmpExportModal.value = true;
|
|
||||||
} else if (selectedAction.value === 'export-record') {
|
|
||||||
if (noStationSelected) {
|
|
||||||
window.$message.warning('请选择要导出录像诊断的车站');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showRecordCheckExportModal.value = true;
|
|
||||||
} else if (selectedAction.value === 'sync-camera') {
|
|
||||||
if (noStationSelected) {
|
|
||||||
window.$message.warning('请选择要同步摄像机的车站');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
syncCamera();
|
|
||||||
} else if (selectedAction.value === 'sync-nvr') {
|
|
||||||
if (noStationSelected) {
|
|
||||||
window.$message.warning('请选择要同步录像机通道的车站');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
syncNvrChannels();
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 车站卡片的事件
|
|
||||||
const selectedStation = ref<Station>();
|
|
||||||
const showDeviceParamConfigModal = ref(false);
|
|
||||||
const showDeviceDetailModal = ref(false);
|
|
||||||
const showAlarmDetailModal = ref(false);
|
|
||||||
|
|
||||||
const onClickConfig: StationCardProps['onClickConfig'] = (station) => {
|
|
||||||
selectedStation.value = station;
|
|
||||||
showDeviceParamConfigModal.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onClickDetail: StationCardProps['onClickDetail'] = (type, station) => {
|
|
||||||
selectedStation.value = station;
|
|
||||||
if (type === 'device') {
|
|
||||||
showDeviceDetailModal.value = true;
|
|
||||||
}
|
|
||||||
if (type === 'alarm') {
|
|
||||||
showAlarmDetailModal.value = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<NScrollbar content-style="padding-right: 8px" style="width: 100%; height: 100%">
|
|
||||||
<!-- 工具栏 -->
|
|
||||||
<NFlex align="center" style="padding: 8px 8px 0 8px">
|
|
||||||
<NButtonGroup>
|
|
||||||
<NButton secondary :focusable="false" :disabled="!!selectedAction && selectedAction !== 'export-icmp'" @click="() => onAction('export-icmp')">导出设备状态</NButton>
|
|
||||||
<NButton secondary :focusable="false" :disabled="!!selectedAction && selectedAction !== 'export-record'" @click="() => onAction('export-record')">导出录像诊断</NButton>
|
|
||||||
<NButton secondary :focusable="false" :disabled="!!selectedAction && selectedAction !== 'sync-camera'" @click="() => onAction('sync-camera')">同步摄像机</NButton>
|
|
||||||
<NButton secondary :focusable="false" :disabled="!!selectedAction && selectedAction !== 'sync-nvr'" @click="() => onAction('sync-nvr')">同步录像机通道</NButton>
|
|
||||||
</NButtonGroup>
|
|
||||||
<template v-if="showOperation">
|
|
||||||
<NCheckbox label="全选" @update:checked="onToggleSelectAll" />
|
|
||||||
<NButton tertiary size="small" type="primary" :focusable="false" :loading="confirming" @click="onConfirm">确定</NButton>
|
|
||||||
<NButton tertiary size="small" type="tertiary" :focusable="false" :disabled="confirming" @click="onCancel">取消</NButton>
|
|
||||||
</template>
|
|
||||||
</NFlex>
|
|
||||||
|
|
||||||
<!-- 车站 -->
|
|
||||||
<NGrid :cols="stationGridColumns" :x-gap="6" :y-gap="6" style="padding: 8px">
|
|
||||||
<NGridItem v-for="station in stations" :key="station.code">
|
|
||||||
<StationCard
|
|
||||||
:station="station"
|
|
||||||
:devices="lineDevices[station.code] ?? initStationDevices()"
|
|
||||||
:alarms="lineAlarms[station.code] ?? initStationAlarms()"
|
|
||||||
:selectable="stationSelectable"
|
|
||||||
v-model:selected="stationSelection[station.code]"
|
|
||||||
@click-detail="onClickDetail"
|
|
||||||
@click-config="onClickConfig"
|
|
||||||
/>
|
|
||||||
</NGridItem>
|
|
||||||
</NGrid>
|
|
||||||
</NScrollbar>
|
|
||||||
|
|
||||||
<IcmpExportModal v-model:show="showIcmpExportModal" :stations="stations.filter((station) => stationSelection[station.code])" @after-leave="onFinish" />
|
|
||||||
<RecordCheckExportModal v-model:show="showRecordCheckExportModal" :stations="stations.filter((station) => stationSelection[station.code])" @after-leave="onFinish" />
|
|
||||||
|
|
||||||
<DeviceParamConfigModal v-model:show="showDeviceParamConfigModal" :station="selectedStation" />
|
|
||||||
<DeviceDetailModal v-model:show="showDeviceDetailModal" :station="selectedStation" />
|
|
||||||
<AlarmDetailModal v-model:show="showAlarmDetailModal" :station="selectedStation" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
|
||||||
192
src/pages/station/station-page.vue
Normal file
192
src/pages/station/station-page.vue
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { initStationAlarms, initStationDevices, syncCameraApi, syncNvrChannelsApi, type Station } from '@/apis';
|
||||||
|
import { AlarmDetailModal, DeviceDetailModal, DeviceParamConfigModal, IcmpExportModal, RecordCheckExportModal, StationCard, type StationCardProps } from '@/components';
|
||||||
|
import { useBatchActions, useLineDevicesQuery } from '@/composables';
|
||||||
|
import { useAlarmStore, useDeviceStore, usePermissionStore, useSettingStore } from '@/stores';
|
||||||
|
import { useMutation } from '@tanstack/vue-query';
|
||||||
|
import { isCancel } from 'axios';
|
||||||
|
import { NButton, NButtonGroup, NCheckbox, NFlex, NGrid, NGridItem, NScrollbar } from 'naive-ui';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
const settingStore = useSettingStore();
|
||||||
|
const { stationGridCols: stationGridColumns } = storeToRefs(settingStore);
|
||||||
|
const deviceStore = useDeviceStore();
|
||||||
|
const { lineDevices } = storeToRefs(deviceStore);
|
||||||
|
const alarmStore = useAlarmStore();
|
||||||
|
const { lineAlarms } = storeToRefs(alarmStore);
|
||||||
|
const permissionStore = usePermissionStore();
|
||||||
|
|
||||||
|
const stations = computed(() => permissionStore.stations.VIEW ?? []);
|
||||||
|
|
||||||
|
const showIcmpExportModal = ref(false);
|
||||||
|
const showRecordCheckExportModal = ref(false);
|
||||||
|
|
||||||
|
const abortController = ref(new AbortController());
|
||||||
|
|
||||||
|
const { batchActions, selectedAction, selectableStations, stationSelection, toggleSelectAction, toggleSelectAllStations, confirmAction, cancelAction } = useBatchActions(stations, abortController);
|
||||||
|
|
||||||
|
const { refetch: refetchLineDevicesQuery } = useLineDevicesQuery();
|
||||||
|
|
||||||
|
const { mutate: syncCamera, isPending: cameraSyncing } = useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
abortController.value.abort();
|
||||||
|
abortController.value = new AbortController();
|
||||||
|
|
||||||
|
const signal = abortController.value.signal;
|
||||||
|
|
||||||
|
const stationCodes = Object.entries(stationSelection.value)
|
||||||
|
.filter(([, selected]) => selected)
|
||||||
|
.map(([code]) => code);
|
||||||
|
const requests = await Promise.allSettled(stationCodes.map((stationCode) => syncCameraApi({ stationCode, signal })));
|
||||||
|
return requests.map((result, index) => ({ ...result, stationCode: stationCodes[index] }));
|
||||||
|
},
|
||||||
|
onSuccess: (requests) => {
|
||||||
|
type PromiseRequest = (typeof requests)[number];
|
||||||
|
const successRequests: PromiseRequest[] = [];
|
||||||
|
const failedRequests: PromiseRequest[] = [];
|
||||||
|
const canceledRequests: PromiseRequest[] = [];
|
||||||
|
for (const request of requests) {
|
||||||
|
if (request.status === 'fulfilled') {
|
||||||
|
successRequests.push(request);
|
||||||
|
} else if (isCancel(request.reason)) {
|
||||||
|
canceledRequests.push(request);
|
||||||
|
} else {
|
||||||
|
failedRequests.push(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const notices: string[] = [`成功 ${successRequests.length} 个车站`, `失败 ${failedRequests.length} 个车站`];
|
||||||
|
if (canceledRequests.length > 0) notices.push(`取消 ${canceledRequests.length} 个车站`);
|
||||||
|
window.$notification.info({
|
||||||
|
title: '摄像机同步结果',
|
||||||
|
content: notices.join(','),
|
||||||
|
duration: 3000,
|
||||||
|
});
|
||||||
|
if (successRequests.length > 0) {
|
||||||
|
// 摄像机同步后,需要重新查询一次设备
|
||||||
|
refetchLineDevicesQuery();
|
||||||
|
}
|
||||||
|
cancelAction();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { mutate: syncNvrChannels, isPending: nvrChannelsSyncing } = useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
abortController.value.abort();
|
||||||
|
abortController.value = new AbortController();
|
||||||
|
|
||||||
|
const signal = abortController.value.signal;
|
||||||
|
|
||||||
|
const stationCodes = Object.entries(stationSelection.value)
|
||||||
|
.filter(([, selected]) => selected)
|
||||||
|
.map(([code]) => code);
|
||||||
|
const requests = await Promise.allSettled(stationCodes.map((stationCode) => syncNvrChannelsApi({ stationCode, signal })));
|
||||||
|
return requests.map((result, index) => ({ ...result, stationCode: stationCodes[index] }));
|
||||||
|
},
|
||||||
|
onSuccess: (requests) => {
|
||||||
|
type PromiseRequest = (typeof requests)[number];
|
||||||
|
const successRequests: PromiseRequest[] = [];
|
||||||
|
const failedRequests: PromiseRequest[] = [];
|
||||||
|
const canceledRequests: PromiseRequest[] = [];
|
||||||
|
for (const request of requests) {
|
||||||
|
if (request.status === 'fulfilled') {
|
||||||
|
successRequests.push(request);
|
||||||
|
} else if (isCancel(request.reason)) {
|
||||||
|
canceledRequests.push(request);
|
||||||
|
} else {
|
||||||
|
failedRequests.push(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const notices: string[] = [`成功 ${successRequests.length} 个车站`, `失败 ${failedRequests.length} 个车站`];
|
||||||
|
if (canceledRequests.length > 0) notices.push(`取消 ${canceledRequests.length} 个车站`);
|
||||||
|
window.$notification.info({
|
||||||
|
title: '录像机通道同步结果',
|
||||||
|
content: notices.join(','),
|
||||||
|
duration: 3000,
|
||||||
|
});
|
||||||
|
cancelAction();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const confirming = computed(() => cameraSyncing.value || nvrChannelsSyncing.value);
|
||||||
|
|
||||||
|
const onClickConfirmAction = () => {
|
||||||
|
confirmAction({
|
||||||
|
'export-icmp': () => {
|
||||||
|
showIcmpExportModal.value = true;
|
||||||
|
},
|
||||||
|
'export-record': () => {
|
||||||
|
showRecordCheckExportModal.value = true;
|
||||||
|
},
|
||||||
|
'sync-camera': () => {
|
||||||
|
syncCamera();
|
||||||
|
},
|
||||||
|
'sync-nvr': () => {
|
||||||
|
syncNvrChannels();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 车站卡片的事件
|
||||||
|
const modalStation = ref<Station>();
|
||||||
|
const showDeviceParamConfigModal = ref(false);
|
||||||
|
const showDeviceDetailModal = ref(false);
|
||||||
|
const showAlarmDetailModal = ref(false);
|
||||||
|
|
||||||
|
const onClickConfig: StationCardProps['onClickConfig'] = (station) => {
|
||||||
|
modalStation.value = station;
|
||||||
|
showDeviceParamConfigModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClickDetail: StationCardProps['onClickDetail'] = (type, station) => {
|
||||||
|
modalStation.value = station;
|
||||||
|
if (type === 'device') {
|
||||||
|
showDeviceDetailModal.value = true;
|
||||||
|
}
|
||||||
|
if (type === 'alarm') {
|
||||||
|
showAlarmDetailModal.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NScrollbar content-style="padding-right: 8px" style="width: 100%; height: 100%">
|
||||||
|
<!-- 工具栏 -->
|
||||||
|
<NFlex align="center" style="padding: 8px 8px 0 8px">
|
||||||
|
<NButtonGroup>
|
||||||
|
<template v-for="batchAction in batchActions" :key="batchAction.key">
|
||||||
|
<NButton :secondary="!batchAction.active" :focusable="false" @click="() => toggleSelectAction(batchAction)">{{ batchAction.label }}</NButton>
|
||||||
|
</template>
|
||||||
|
</NButtonGroup>
|
||||||
|
<template v-if="selectedAction">
|
||||||
|
<NCheckbox label="全选" :checked="selectableStations.length === Object.keys(stationSelection).length" @update:checked="toggleSelectAllStations" />
|
||||||
|
<NButton tertiary size="small" type="primary" :focusable="false" :loading="confirming" @click="onClickConfirmAction">确定</NButton>
|
||||||
|
<NButton tertiary size="small" type="tertiary" :focusable="false" @click="cancelAction">取消</NButton>
|
||||||
|
</template>
|
||||||
|
</NFlex>
|
||||||
|
|
||||||
|
<!-- 车站 -->
|
||||||
|
<NGrid :cols="stationGridColumns" :x-gap="6" :y-gap="6" style="padding: 8px">
|
||||||
|
<NGridItem v-for="station in stations" :key="station.code">
|
||||||
|
<StationCard
|
||||||
|
:station="station"
|
||||||
|
:devices="lineDevices[station.code] ?? initStationDevices()"
|
||||||
|
:alarms="lineAlarms[station.code] ?? initStationAlarms()"
|
||||||
|
:selectable="!!selectableStations.find((selectable) => selectable.code === station.code)"
|
||||||
|
v-model:selected="stationSelection[station.code]"
|
||||||
|
@click-detail="onClickDetail"
|
||||||
|
@click-config="onClickConfig"
|
||||||
|
/>
|
||||||
|
</NGridItem>
|
||||||
|
</NGrid>
|
||||||
|
</NScrollbar>
|
||||||
|
|
||||||
|
<IcmpExportModal v-model:show="showIcmpExportModal" :stations="stations.filter((station) => stationSelection[station.code])" @after-leave="cancelAction" />
|
||||||
|
<RecordCheckExportModal v-model:show="showRecordCheckExportModal" :stations="stations.filter((station) => stationSelection[station.code])" @after-leave="cancelAction" />
|
||||||
|
|
||||||
|
<DeviceParamConfigModal v-model:show="showDeviceParamConfigModal" :station="modalStation" />
|
||||||
|
<DeviceDetailModal v-model:show="showDeviceDetailModal" :station="modalStation" />
|
||||||
|
<AlarmDetailModal v-model:show="showAlarmDetailModal" :station="modalStation" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
@@ -6,7 +6,7 @@ const router = createRouter({
|
|||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: '/login',
|
||||||
component: () => import('@/pages/login-page.vue'),
|
component: () => import('@/pages/login/login-page.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
@@ -15,22 +15,22 @@ const router = createRouter({
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'station',
|
path: 'station',
|
||||||
component: () => import('@/pages/station-page.vue'),
|
component: () => import('@/pages/station/station-page.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'device',
|
path: 'device',
|
||||||
component: () => import('@/pages/device-page.vue'),
|
component: () => import('@/pages/device/device-page.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'alarm',
|
path: 'alarm',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'alarm-log',
|
path: 'alarm-log',
|
||||||
component: () => import('@/pages/alarm-log-page.vue'),
|
component: () => import('@/pages/alarm/alarm-log-page.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'alarm-ignore',
|
path: 'alarm-ignore',
|
||||||
component: () => import('@/pages/alarm-ignore-page.vue'),
|
component: () => import('@/pages/alarm/alarm-ignore-page.vue'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -39,17 +39,26 @@ const router = createRouter({
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'vimp-log',
|
path: 'vimp-log',
|
||||||
component: () => import('@/pages/vimp-log-page.vue'),
|
component: () => import('@/pages/log/vimp-log-page.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'call-log',
|
path: 'call-log',
|
||||||
component: () => import('@/pages/call-log-page.vue'),
|
component: () => import('@/pages/log/call-log-page.vue'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'permission',
|
||||||
|
component: () => import('@/pages/permission/permission-page.vue'),
|
||||||
|
beforeEnter: () => {
|
||||||
|
const userStore = useUserStore();
|
||||||
|
if (userStore.isLamp) return true;
|
||||||
|
return { path: '/404' };
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/:pathMatch(.*)*',
|
path: '/:pathMatch(.*)*',
|
||||||
component: () => import('@/pages/not-found-page.vue'),
|
component: () => import('@/pages/error/not-found-page.vue'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export * from './alarm';
|
export * from './alarm';
|
||||||
export * from './device';
|
export * from './device';
|
||||||
|
export * from './permission';
|
||||||
export * from './polling';
|
export * from './polling';
|
||||||
export * from './setting';
|
export * from './setting';
|
||||||
export * from './station';
|
export * from './station';
|
||||||
|
|||||||
73
src/stores/permission.ts
Normal file
73
src/stores/permission.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import type { NdmPermissionResultVO, Station } from '@/apis';
|
||||||
|
import { NDM_PERMISSION_STORE_ID } from '@/constants';
|
||||||
|
import { PERMISSION_TYPE_NAMES, type PermissionType } from '@/enums';
|
||||||
|
import { useStationStore } from '@/stores';
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { objectEntries } from '@vueuse/core';
|
||||||
|
|
||||||
|
type Permissions = Record<Station['code'], PermissionType[]>;
|
||||||
|
|
||||||
|
export const usePermissionStore = defineStore(
|
||||||
|
NDM_PERMISSION_STORE_ID,
|
||||||
|
() => {
|
||||||
|
const permRecords = ref<NdmPermissionResultVO[]>([]);
|
||||||
|
|
||||||
|
const permissions = computed<Permissions>(() => {
|
||||||
|
const stationStore = useStationStore();
|
||||||
|
|
||||||
|
const result: Permissions = {};
|
||||||
|
|
||||||
|
// 如果该用户没有任何权限记录,则开放所有权限,否则根据记录配置权限
|
||||||
|
if (permRecords.value.length === 0) {
|
||||||
|
stationStore.stations.forEach((station) => {
|
||||||
|
result[station.code] = [...objectEntries(PERMISSION_TYPE_NAMES).map(([permType]) => permType)];
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
stationStore.stations.forEach((station) => {
|
||||||
|
result[station.code] = [];
|
||||||
|
const stationPermRecords = permRecords.value.filter((record) => record.stationCode === station.code);
|
||||||
|
if (stationPermRecords.length === 0) return;
|
||||||
|
stationPermRecords.forEach(({ type: permType }) => {
|
||||||
|
if (!permType) return;
|
||||||
|
result[station.code]?.push(permType);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 按权限对车站进行分类
|
||||||
|
const stations = computed(() => {
|
||||||
|
const stationStore = useStationStore();
|
||||||
|
const result: Partial<Record<PermissionType, Station[]>> = {};
|
||||||
|
// 按原始的车站顺序进行遍历,保持显示顺序不变
|
||||||
|
stationStore.stations.forEach((station) => {
|
||||||
|
const permissionTypes = permissions.value[station.code];
|
||||||
|
if (!permissionTypes) return;
|
||||||
|
permissionTypes.forEach((permissionType) => {
|
||||||
|
if (!result[permissionType]) result[permissionType] = [];
|
||||||
|
result[permissionType].push(station);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
const setPermRecords = (records: NdmPermissionResultVO[]) => {
|
||||||
|
permRecords.value = records;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
permRecords,
|
||||||
|
|
||||||
|
permissions,
|
||||||
|
stations,
|
||||||
|
|
||||||
|
setPermRecords,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
persist: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user