Commit 625fd627221b28510a87ca903c6a4ea0e6174797

Authored by ww
1 parent 76bbfae4

feat: camera add split mode && list mode && support full screen

@@ -2,10 +2,12 @@ import { defHttp } from '/@/utils/http/axios'; @@ -2,10 +2,12 @@ import { defHttp } from '/@/utils/http/axios';
2 import { 2 import {
3 CameraModel, 3 CameraModel,
4 CameraQueryParam, 4 CameraQueryParam,
  5 + CameraRecord,
5 StreamingMediaDeleteParam, 6 StreamingMediaDeleteParam,
6 StreamingQueryParam, 7 StreamingQueryParam,
7 StreamingSubmitParam, 8 StreamingSubmitParam,
8 } from './model/cameraModel'; 9 } from './model/cameraModel';
  10 +import { PaginationResult } from '/#/axios';
9 11
10 enum CameraManagerApi { 12 enum CameraManagerApi {
11 CAMERA_POST_URL = '/video', 13 CAMERA_POST_URL = '/video',
@@ -19,7 +21,7 @@ enum CameraManagerApi { @@ -19,7 +21,7 @@ enum CameraManagerApi {
19 } 21 }
20 22
21 export const cameraPage = (params: CameraQueryParam) => { 23 export const cameraPage = (params: CameraQueryParam) => {
22 - return defHttp.get<CameraQueryParam>({ 24 + return defHttp.get<PaginationResult<CameraRecord>>({
23 url: CameraManagerApi.CAMERA_GET_URL, 25 url: CameraManagerApi.CAMERA_GET_URL,
24 params, 26 params,
25 }); 27 });
@@ -2,11 +2,11 @@ import { BasicPageParams } from '/@/api/model/baseModel'; @@ -2,11 +2,11 @@ import { BasicPageParams } from '/@/api/model/baseModel';
2 export type CameraQueryParam = BasicPageParams & CameraParam; 2 export type CameraQueryParam = BasicPageParams & CameraParam;
3 3
4 export type CameraParam = { 4 export type CameraParam = {
5 - status: true;  
6 - name: string;  
7 - organizationId: string;  
8 - orderFiled: string;  
9 - orderType: string; 5 + // status: true;
  6 + name?: string;
  7 + organizationId?: string;
  8 + // orderFiled: string;
  9 + // orderType: string;
10 }; 10 };
11 11
12 export interface CameraModel { 12 export interface CameraModel {
@@ -82,3 +82,22 @@ export interface StreamingMediaDeleteParam { @@ -82,3 +82,22 @@ export interface StreamingMediaDeleteParam {
82 tenantId?: string; 82 tenantId?: string;
83 ids: string[]; 83 ids: string[];
84 } 84 }
  85 +
  86 +export interface CameraRecord {
  87 + id: string;
  88 + creator: string;
  89 + createTime: string;
  90 + updater: string;
  91 + updateTime: string;
  92 + name: string;
  93 + enabled: boolean;
  94 + tenantId: string;
  95 + videoUrl: string;
  96 + brand: string;
  97 + sn: string;
  98 + organizationId: string;
  99 + organizationName: string;
  100 + status: boolean;
  101 + accessMode: number;
  102 + playProtocol: number;
  103 +}
  1 +<template>
  2 + <div>
  3 + <PageWrapper dense contentFullHeight contentClass="flex">
  4 + <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" />
  5 + <BasicTable
  6 + style="flex: auto"
  7 + :clickToRowSelect="false"
  8 + @register="registerTable"
  9 + :searchInfo="searchInfo"
  10 + class="w-3/4 xl:w-4/5"
  11 + >
  12 + <template #toolbar>
  13 + <a-button type="primary" @click="handleSwitchMode">分屏模式</a-button>
  14 + <Authority value="api:yt:video:post">
  15 + <a-button type="primary" @click="handleCreateOrEdit(null)"> 新增视频 </a-button>
  16 + </Authority>
  17 + <Authority value="api:yt:video:delete">
  18 + <Popconfirm
  19 + title="您确定要批量删除数据"
  20 + ok-text="确定"
  21 + cancel-text="取消"
  22 + @confirm="handleDeleteOrBatchDelete(null)"
  23 + >
  24 + <a-button type="primary" color="error" :disabled="hasBatchDelete">
  25 + 批量删除
  26 + </a-button>
  27 + </Popconfirm>
  28 + </Authority>
  29 + </template>
  30 + <template #img="{ record }">
  31 + <TableImg
  32 + :size="30"
  33 + :showBadge="false"
  34 + :simpleShow="true"
  35 + :imgList="
  36 + typeof record.avatar !== 'undefined' && record.avatar !== '' && record.avatar != null
  37 + ? [record.avatar]
  38 + : null
  39 + "
  40 + />
  41 + </template>
  42 + <template #accessMode="{ record }">
  43 + <Tag :color="record.accessMode === AccessMode.ManuallyEnter ? 'cyan' : 'blue'">{{
  44 + record.accessMode === AccessMode.ManuallyEnter ? '手动输入' : '流媒体获取'
  45 + }}</Tag>
  46 + </template>
  47 + <template #action="{ record }">
  48 + <TableAction
  49 + :actions="[
  50 + {
  51 + label: '预览',
  52 + icon: 'clarity:note-edit-line',
  53 + auth: 'api:yt:video:get',
  54 + onClick: handleViewVideo.bind(null, record),
  55 + },
  56 + {
  57 + label: '编辑',
  58 + auth: 'api:yt:video:update',
  59 + icon: 'clarity:note-edit-line',
  60 + onClick: handleCreateOrEdit.bind(null, record),
  61 + },
  62 + {
  63 + label: '删除',
  64 + auth: 'api:yt:video:delete',
  65 + icon: 'ant-design:delete-outlined',
  66 + color: 'error',
  67 + popConfirm: {
  68 + title: '是否确认删除',
  69 + confirm: handleDeleteOrBatchDelete.bind(null, record),
  70 + },
  71 + },
  72 + ]"
  73 + />
  74 + </template>
  75 + </BasicTable>
  76 + </PageWrapper>
  77 + <CameraDrawer @register="registerDrawer" @success="handleSuccess" />
  78 + <VideoPreviewModal @register="registerModal" />
  79 + </div>
  80 +</template>
  81 +
  82 +<script lang="ts">
  83 + import { defineComponent, reactive, nextTick } from 'vue';
  84 + import { BasicTable, useTable, TableAction, TableImg } from '/@/components/Table';
  85 + import { PageWrapper } from '/@/components/Page';
  86 + import { useDrawer } from '/@/components/Drawer';
  87 + import CameraDrawer from './CameraDrawer.vue';
  88 + import { useResetOrganizationTree, OrganizationIdTree } from '/@/views/common/organizationIdTree';
  89 + import { cameraPage, deleteCameraManage } from '/@/api/camera/cameraManager';
  90 + import { searchFormSchema, columns, AccessMode, PageMode } from './config.data';
  91 + import VideoPreviewModal from './DialogPreviewVideo.vue';
  92 + import { useModal } from '/@/components/Modal';
  93 + import { Authority } from '/@/components/Authority';
  94 + import { useBatchDelete } from '/@/hooks/web/useBatchDelete';
  95 + import { Popconfirm } from 'ant-design-vue';
  96 + import { Tag } from 'ant-design-vue';
  97 +
  98 + export default defineComponent({
  99 + components: {
  100 + PageWrapper,
  101 + OrganizationIdTree,
  102 + BasicTable,
  103 + TableAction,
  104 + CameraDrawer,
  105 + VideoPreviewModal,
  106 + TableImg,
  107 + Authority,
  108 + Popconfirm,
  109 + Tag,
  110 + },
  111 + emits: ['switchMode'],
  112 + setup(_, { emit }) {
  113 + const searchInfo = reactive<Recordable>({});
  114 + const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);
  115 + const [registerModal, { openModal }] = useModal();
  116 + // 表格hooks
  117 + const [registerTable, { reload, setProps }] = useTable({
  118 + title: '视频列表',
  119 + api: cameraPage,
  120 + columns,
  121 + showIndexColumn: false,
  122 + clickToRowSelect: false,
  123 + formConfig: {
  124 + labelWidth: 120,
  125 + schemas: searchFormSchema,
  126 + resetFunc: resetFn,
  127 + },
  128 + useSearchForm: true,
  129 + showTableSetting: true,
  130 + bordered: true,
  131 + rowKey: 'id',
  132 + actionColumn: {
  133 + width: 200,
  134 + title: '操作',
  135 + dataIndex: 'action',
  136 + slots: { customRender: 'action' },
  137 + fixed: 'right',
  138 + },
  139 + });
  140 + // 弹框
  141 + const [registerDrawer, { openDrawer }] = useDrawer();
  142 +
  143 + // 刷新
  144 + const handleSuccess = () => {
  145 + reload();
  146 + };
  147 + const { hasBatchDelete, handleDeleteOrBatchDelete, selectionOptions } = useBatchDelete(
  148 + deleteCameraManage,
  149 + handleSuccess,
  150 + setProps
  151 + );
  152 +
  153 + nextTick(() => {
  154 + setProps(selectionOptions);
  155 + });
  156 + // 新增或编辑
  157 + const handleCreateOrEdit = (record: Recordable | null) => {
  158 + if (record) {
  159 + openDrawer(true, {
  160 + isUpdate: true,
  161 + record,
  162 + });
  163 + } else {
  164 + openDrawer(true, {
  165 + isUpdate: false,
  166 + });
  167 + }
  168 + };
  169 +
  170 + // 树形选择器
  171 + const handleSelect = (organizationId: string) => {
  172 + searchInfo.organizationId = organizationId;
  173 + handleSuccess();
  174 + };
  175 + const handleViewVideo = (record) => {
  176 + openModal(true, {
  177 + isUpdate: true,
  178 + record,
  179 + });
  180 + };
  181 +
  182 + const handleSwitchMode = () => {
  183 + emit('switchMode', PageMode.SPLIT_SCREEN_MODE);
  184 + };
  185 +
  186 + return {
  187 + searchInfo,
  188 + hasBatchDelete,
  189 + handleCreateOrEdit,
  190 + handleDeleteOrBatchDelete,
  191 + handleSelect,
  192 + handleSuccess,
  193 + registerTable,
  194 + registerDrawer,
  195 + organizationIdTreeRef,
  196 + handleViewVideo,
  197 + registerModal,
  198 + AccessMode,
  199 + handleSwitchMode,
  200 + };
  201 + },
  202 + });
  203 +</script>
  1 +<script setup lang="ts">
  2 + import { PageWrapper } from '/@/components/Page';
  3 + import OrganizationIdTree from '../../common/organizationIdTree/src/OrganizationIdTree.vue';
  4 + import { computed, onMounted, reactive, ref, unref } from 'vue';
  5 + import { Tabs, Row, Col, Spin, Button } from 'ant-design-vue';
  6 + import { cameraPage } from '/@/api/camera/cameraManager';
  7 + import { CameraRecord } from '/@/api/camera/model/cameraModel';
  8 + import { AndroidFilled } from '@ant-design/icons-vue';
  9 + import { videoPlay as VideoPlay } from 'vue3-video-play'; // 引入组件
  10 + import 'vue3-video-play/dist/style.css'; // 引入css
  11 + import { useFullscreen } from '@vueuse/core';
  12 + import CameraDrawer from './CameraDrawer.vue';
  13 + import { useDrawer } from '/@/components/Drawer';
  14 + import { PageMode } from './config.data';
  15 +
  16 + const emit = defineEmits(['switchMode']);
  17 + const organizationIdTreeRef = ref(null);
  18 + const videoContainer = ref<Nullable<HTMLDivElement>>(null);
  19 + const activeKey = ref(PageMode.SPLIT_SCREEN_MODE);
  20 + const cameraList = ref<CameraRecord[]>([]);
  21 + const organizationId = ref<Nullable<string>>(null);
  22 + const loading = ref(false);
  23 + const pagination = reactive({
  24 + page: 1,
  25 + pageSize: 4,
  26 + colNumber: 2,
  27 + });
  28 +
  29 + const options = reactive({
  30 + width: '800px',
  31 + height: '450px',
  32 + color: '#409eff',
  33 + muted: false, //静音
  34 + webFullScreen: false,
  35 + autoPlay: true, //自动播放
  36 + currentTime: 0,
  37 + loop: false, //循环播放
  38 + mirror: false, //镜像画面
  39 + ligthOff: false, //关灯模式
  40 + volume: 0.3, //默认音量大小
  41 + control: true, //是否显示控制器
  42 + title: '', //视频名称
  43 + type: 'm3u8',
  44 + src: '', //视频源
  45 + controlBtns: [
  46 + 'audioTrack',
  47 + 'quality',
  48 + 'speedRate',
  49 + 'volume',
  50 + 'setting',
  51 + 'pip',
  52 + 'pageFullScreen',
  53 + 'fullScreen',
  54 + ],
  55 + });
  56 +
  57 + // 树形选择器
  58 + const handleSelect = (orgId: string) => {
  59 + organizationId.value = orgId;
  60 + getCameraList();
  61 + };
  62 +
  63 + const getColLayout = computed(() => {
  64 + const totalSpan = 24;
  65 + return totalSpan / pagination.colNumber;
  66 + });
  67 +
  68 + const getCameraList = async () => {
  69 + try {
  70 + loading.value = true;
  71 + const { items } = await cameraPage({
  72 + page: 1,
  73 + pageSize: pagination.pageSize,
  74 + organizationId: unref(organizationId)!,
  75 + });
  76 + cameraList.value = items;
  77 + } catch (error) {
  78 + } finally {
  79 + loading.value = false;
  80 + }
  81 + };
  82 +
  83 + const handleSwitchLayoutWay = (pageSize: number, layout: number) => {
  84 + pagination.colNumber = layout;
  85 + pagination.pageSize = pageSize;
  86 + getCameraList();
  87 + };
  88 +
  89 + const { enter } = useFullscreen(videoContainer);
  90 +
  91 + const handleFullScreen = () => {
  92 + enter();
  93 + };
  94 +
  95 + const handleChangeMode = (activeKey: string) => {
  96 + if (activeKey === PageMode.FULL_SCREEN_MODE) {
  97 + handleFullScreen();
  98 + } else if (activeKey === PageMode.LIST_MODE) {
  99 + emit('switchMode', PageMode.LIST_MODE);
  100 + }
  101 + };
  102 +
  103 + const [registerDrawer, { openDrawer }] = useDrawer();
  104 +
  105 + const handleAddCamera = () => {
  106 + openDrawer(true, {
  107 + isUpdate: false,
  108 + });
  109 + };
  110 +
  111 + onMounted(() => {
  112 + getCameraList();
  113 + });
  114 +</script>
  115 +
  116 +<template>
  117 + <div>
  118 + <PageWrapper dense contentFullHeight contentClass="flex">
  119 + <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" />
  120 + <section class="p-4 pl-9 split-screen-mode flex flex-col flex-auto w-3/4 xl:w-4/5">
  121 + <div class="p-3 bg-light-50 flex justify-between mb-4">
  122 + <div class="flex gap-4 cursor-pointer items-center">
  123 + <div
  124 + class="w-8 h-8 flex justify-center items-center"
  125 + :style="{ color: pagination.colNumber === 1 ? '#1890ff' : '' }"
  126 + @click="handleSwitchLayoutWay(1, 1)"
  127 + >
  128 + <AndroidFilled class="text-2xl" />
  129 + </div>
  130 + <div
  131 + class="w-8 h-8 flex justify-center items-center"
  132 + :style="{ color: pagination.colNumber === 2 ? '#1890ff' : '' }"
  133 + @click="handleSwitchLayoutWay(4, 2)"
  134 + >
  135 + <AndroidFilled class="text-2xl" />
  136 + </div>
  137 + <div
  138 + class="w-8 h-8 flex justify-center items-center"
  139 + :style="{ color: pagination.colNumber === 3 ? '#1890ff' : '' }"
  140 + @click="handleSwitchLayoutWay(9, 3)"
  141 + >
  142 + <AndroidFilled class="text-2xl" />
  143 + </div>
  144 + </div>
  145 + <div class="flex items-center gap-4">
  146 + <div class="flex">
  147 + <Button type="primary" @click="handleAddCamera">新增视频</Button>
  148 + </div>
  149 + <Tabs type="card" v-model:activeKey="activeKey" @change="handleChangeMode">
  150 + <Tabs.TabPane :key="PageMode.SPLIT_SCREEN_MODE" tab="分屏模式" />
  151 + <Tabs.TabPane :key="PageMode.LIST_MODE" tab="列表模式" />
  152 + <Tabs.TabPane :key="PageMode.FULL_SCREEN_MODE" tab="全屏" />
  153 + </Tabs>
  154 + </div>
  155 + </div>
  156 + <section ref="videoContainer" class="bg-light-50 flex-auto">
  157 + <Spin :spinning="loading" class="h-full">
  158 + <Row :gutter="16" class="h-full mx-0">
  159 + <Col
  160 + v-for="item in cameraList"
  161 + :key="item.id"
  162 + :style="{ height: `${100 / pagination.colNumber}%` }"
  163 + class="h-1/2 !px-0 !flex justify-center items-center gap-2"
  164 + :span="getColLayout"
  165 + >
  166 + <div class="box-border w-full h-full p-3">
  167 + <div class="bg-yellow-50 w-full h-full overflow-hidden">
  168 + <VideoPlay v-bind="options" :src="item.videoUrl" />
  169 + </div>
  170 + </div>
  171 + </Col>
  172 + </Row>
  173 + </Spin>
  174 + </section>
  175 + </section>
  176 + </PageWrapper>
  177 + <CameraDrawer @register="registerDrawer" @success="getCameraList" />
  178 + </div>
  179 +</template>
  180 +
  181 +<style scoped lang="less">
  182 + .split-screen-mode:deep(.ant-tabs-bar) {
  183 + margin-bottom: 0;
  184 + }
  185 +
  186 + .split-screen-mode:deep(.ant-spin-nested-loading) {
  187 + height: 100%;
  188 + }
  189 +
  190 + .split-screen-mode:deep(.ant-spin-container) {
  191 + height: 100%;
  192 + }
  193 +
  194 + .split-screen-mode:deep(.d-player-wrap) {
  195 + width: 100%;
  196 + height: 100%;
  197 + }
  198 +
  199 + .split-screen-mode:deep(.ant-tabs-tab-active) {
  200 + border-bottom: 1px solid #eee;
  201 + }
  202 +</style>
@@ -23,6 +23,12 @@ export enum StreamType { @@ -23,6 +23,12 @@ export enum StreamType {
23 THIRD = 2, 23 THIRD = 2,
24 } 24 }
25 25
  26 +export enum PageMode {
  27 + SPLIT_SCREEN_MODE = 'splitScreen',
  28 + LIST_MODE = 'listMode',
  29 + FULL_SCREEN_MODE = 'fullScreenMode',
  30 +}
  31 +
26 // 表格列数据 32 // 表格列数据
27 export const columns: BasicColumn[] = [ 33 export const columns: BasicColumn[] = [
28 { 34 {
  1 +<script setup lang="ts">
  2 + import { ref } from 'vue';
  3 + import { PageMode } from './config.data';
  4 + import ListMode from './ListMode.vue';
  5 + import SplitScreenMode from './SplitScreenMode.vue';
  6 + const mode = ref<PageMode>(PageMode.SPLIT_SCREEN_MODE);
  7 +
  8 + const handleSwitchMode = (key: PageMode) => {
  9 + mode.value = key;
  10 + };
  11 +</script>
  12 +
1 <template> 13 <template>
2 <div> 14 <div>
3 - <PageWrapper dense contentFullHeight contentClass="flex">  
4 - <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" />  
5 - <BasicTable  
6 - style="flex: auto"  
7 - :clickToRowSelect="false"  
8 - @register="registerTable"  
9 - :searchInfo="searchInfo"  
10 - class="w-3/4 xl:w-4/5"  
11 - >  
12 - <template #toolbar>  
13 - <Authority value="api:yt:video:post">  
14 - <a-button type="primary" @click="handleCreateOrEdit(null)"> 新增视频 </a-button>  
15 - </Authority>  
16 - <Authority value="api:yt:video:delete">  
17 - <Popconfirm  
18 - title="您确定要批量删除数据"  
19 - ok-text="确定"  
20 - cancel-text="取消"  
21 - @confirm="handleDeleteOrBatchDelete(null)"  
22 - >  
23 - <a-button type="primary" color="error" :disabled="hasBatchDelete">  
24 - 批量删除  
25 - </a-button>  
26 - </Popconfirm>  
27 - </Authority>  
28 - </template>  
29 - <template #img="{ record }">  
30 - <TableImg  
31 - :size="30"  
32 - :showBadge="false"  
33 - :simpleShow="true"  
34 - :imgList="  
35 - typeof record.avatar !== 'undefined' && record.avatar !== '' && record.avatar != null  
36 - ? [record.avatar]  
37 - : null  
38 - "  
39 - />  
40 - </template>  
41 - <template #accessMode="{ record }">  
42 - <Tag :color="record.accessMode === AccessMode.ManuallyEnter ? 'cyan' : 'blue'">{{  
43 - record.accessMode === AccessMode.ManuallyEnter ? '手动输入' : '流媒体获取'  
44 - }}</Tag>  
45 - </template>  
46 - <template #action="{ record }">  
47 - <TableAction  
48 - :actions="[  
49 - {  
50 - label: '预览',  
51 - icon: 'clarity:note-edit-line',  
52 - auth: 'api:yt:video:get',  
53 - onClick: handleViewVideo.bind(null, record),  
54 - },  
55 - {  
56 - label: '编辑',  
57 - auth: 'api:yt:video:update',  
58 - icon: 'clarity:note-edit-line',  
59 - onClick: handleCreateOrEdit.bind(null, record),  
60 - },  
61 - {  
62 - label: '删除',  
63 - auth: 'api:yt:video:delete',  
64 - icon: 'ant-design:delete-outlined',  
65 - color: 'error',  
66 - popConfirm: {  
67 - title: '是否确认删除',  
68 - confirm: handleDeleteOrBatchDelete.bind(null, record),  
69 - },  
70 - },  
71 - ]"  
72 - />  
73 - </template>  
74 - </BasicTable>  
75 - </PageWrapper>  
76 - <CameraDrawer @register="registerDrawer" @success="handleSuccess" />  
77 - <VideoPreviewModal @register="registerModal" /> 15 + <SplitScreenMode v-if="mode == PageMode.SPLIT_SCREEN_MODE" @switchMode="handleSwitchMode" />
  16 + <ListMode v-if="mode === PageMode.LIST_MODE" @switchMode="handleSwitchMode" />
78 </div> 17 </div>
79 </template> 18 </template>
80 -  
81 -<script lang="ts">  
82 - import { defineComponent, reactive, nextTick } from 'vue';  
83 - import { BasicTable, useTable, TableAction, TableImg } from '/@/components/Table';  
84 - import { PageWrapper } from '/@/components/Page';  
85 - import { useDrawer } from '/@/components/Drawer';  
86 - import CameraDrawer from './CameraDrawer.vue';  
87 - import { useResetOrganizationTree, OrganizationIdTree } from '/@/views/common/organizationIdTree';  
88 - import { cameraPage, deleteCameraManage } from '/@/api/camera/cameraManager';  
89 - import { searchFormSchema, columns, AccessMode } from './config.data';  
90 - import VideoPreviewModal from './DialogPreviewVideo.vue';  
91 - import { useModal } from '/@/components/Modal';  
92 - import { Authority } from '/@/components/Authority';  
93 - import { useBatchDelete } from '/@/hooks/web/useBatchDelete';  
94 - import { Popconfirm } from 'ant-design-vue';  
95 - import { Tag } from 'ant-design-vue';  
96 -  
97 - export default defineComponent({  
98 - components: {  
99 - PageWrapper,  
100 - OrganizationIdTree,  
101 - BasicTable,  
102 - TableAction,  
103 - CameraDrawer,  
104 - VideoPreviewModal,  
105 - TableImg,  
106 - Authority,  
107 - Popconfirm,  
108 - Tag,  
109 - },  
110 - setup() {  
111 - const searchInfo = reactive<Recordable>({});  
112 - const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);  
113 - const [registerModal, { openModal }] = useModal();  
114 - // 表格hooks  
115 - const [registerTable, { reload, setProps }] = useTable({  
116 - title: '视频列表',  
117 - api: cameraPage,  
118 - columns,  
119 - showIndexColumn: false,  
120 - clickToRowSelect: false,  
121 - formConfig: {  
122 - labelWidth: 120,  
123 - schemas: searchFormSchema,  
124 - resetFunc: resetFn,  
125 - },  
126 - useSearchForm: true,  
127 - showTableSetting: true,  
128 - bordered: true,  
129 - rowKey: 'id',  
130 - actionColumn: {  
131 - width: 200,  
132 - title: '操作',  
133 - dataIndex: 'action',  
134 - slots: { customRender: 'action' },  
135 - fixed: 'right',  
136 - },  
137 - });  
138 - // 弹框  
139 - const [registerDrawer, { openDrawer }] = useDrawer();  
140 -  
141 - // 刷新  
142 - const handleSuccess = () => {  
143 - reload();  
144 - };  
145 - const { hasBatchDelete, handleDeleteOrBatchDelete, selectionOptions } = useBatchDelete(  
146 - deleteCameraManage,  
147 - handleSuccess,  
148 - setProps  
149 - );  
150 -  
151 - nextTick(() => {  
152 - setProps(selectionOptions);  
153 - });  
154 - // 新增或编辑  
155 - const handleCreateOrEdit = (record: Recordable | null) => {  
156 - if (record) {  
157 - openDrawer(true, {  
158 - isUpdate: true,  
159 - record,  
160 - });  
161 - } else {  
162 - openDrawer(true, {  
163 - isUpdate: false,  
164 - });  
165 - }  
166 - };  
167 -  
168 - // 树形选择器  
169 - const handleSelect = (organizationId: string) => {  
170 - searchInfo.organizationId = organizationId;  
171 - handleSuccess();  
172 - };  
173 - const handleViewVideo = (record) => {  
174 - openModal(true, {  
175 - isUpdate: true,  
176 - record,  
177 - });  
178 - };  
179 - return {  
180 - searchInfo,  
181 - hasBatchDelete,  
182 - handleCreateOrEdit,  
183 - handleDeleteOrBatchDelete,  
184 - handleSelect,  
185 - handleSuccess,  
186 - registerTable,  
187 - registerDrawer,  
188 - organizationIdTreeRef,  
189 - handleViewVideo,  
190 - registerModal,  
191 - AccessMode,  
192 - };  
193 - },  
194 - });  
195 -</script>