Commit 6f565d8f5d251e2d426d663f108edbf180a36a7e

Authored by fengtao
1 parent b11b1e0c

feat:新增视频管理,明天完成

... ... @@ -13,7 +13,7 @@ VITE_PUBLIC_PATH = /
13 13 # 线上演示环境
14 14 # VITE_PROXY = [["/api","http://101.133.234.90:8080/api"]]
15 15 # 线上测试环境
16   -VITE_PROXY = [["/api","http://localhost:8080/api"]]
  16 +VITE_PROXY = [["/api","http://47.99.141.212:8080/api"]]
17 17
18 18 # 实时数据的ws地址
19 19 VITE_WEB_SOCKET = ws://localhost:8080/api/ws/plugins/telemetry?token=
... ...
  1 +import { defHttp } from '/@/utils/http/axios';
  2 +import { DeviceModel, DeviceQueryParam } from '/@/api/device/model/deviceModel';
  3 +import { ChildDeviceParams } from './model/cameraModel';
  4 +
  5 +enum CameraManagerApi {
  6 + CAMERA_POST_URL = '/yt/video',
  7 + CAMERA_GET_URL = '/yt/video',
  8 + CAMERA_DELETE_URL = '/yt/video',
  9 + CAMERA_GET_DETAIL_URL = '/yt/video',
  10 +}
  11 +
  12 +export const cameraPage = (params: DeviceQueryParam) => {
  13 + return defHttp.get<DeviceModel>({
  14 + url: CameraManagerApi.CAMERA_GET_URL,
  15 + params,
  16 + });
  17 +};
  18 +
  19 +/**
  20 + * 删除视频
  21 + * @param ids 删除的ids
  22 + */
  23 +export const deleteCameraManage = (ids: string[]) => {
  24 + return defHttp.delete({
  25 + url: CameraManagerApi.CAMERA_DELETE_URL,
  26 + data: {
  27 + ids: ids,
  28 + },
  29 + });
  30 +};
  31 +
  32 +// 创建或编辑视频
  33 +export const createOrEditCameraManage = (data) => {
  34 + return defHttp.post({
  35 + url: CameraManagerApi.CAMERA_POST_URL,
  36 + data,
  37 + });
  38 +};
  39 +
  40 +// 查询视频详情
  41 +export const getCameraManageDetail = (id: string) => {
  42 + return defHttp.get({
  43 + url: CameraManagerApi.CAMERA_GET_DETAIL_URL + `/${id}`,
  44 + });
  45 +};
... ...
  1 +import { BasicPageParams } from '/@/api/model/baseModel';
  2 +export enum DeviceState {
  3 + INACTIVE = 'INACTIVE',
  4 + ONLINE = 'ONLINE',
  5 + OFFLINE = 'OFFLINE',
  6 +}
  7 +export enum DeviceTypeEnum {
  8 + GATEWAY = 'GATEWAY',
  9 + DIRECT_CONNECTION = 'DIRECT_CONNECTION',
  10 + SENSOR = 'SENSOR',
  11 +}
  12 +export type DeviceProfileQueryParam = BasicPageParams & DeviceProfileParam & DeviceId;
  13 +export type DeviceQueryParam = BasicPageParams & DeviceParam;
  14 +export type DeviceParam = {
  15 + name?: string;
  16 + deviceProfileId?: string;
  17 +};
  18 +export type DeviceProfileParam = {
  19 + name?: string;
  20 +};
  21 +export type DeviceId = {
  22 + id?: string;
  23 +};
  24 +
  25 +export interface DeviceModel {
  26 + id: string;
  27 + name: string;
  28 + deviceInfo: any;
  29 + activeTime: string;
  30 + deviceState: DeviceState;
  31 + alarmStatus: number;
  32 + profileId: string;
  33 + label: string;
  34 + lastConnectTime: string;
  35 + deviceType: DeviceTypeEnum;
  36 +}
  37 +
  38 +export interface DeviceProfileModel {
  39 + id: string;
  40 + name: string;
  41 + transportType: string;
  42 + createTime: string;
  43 + description: string;
  44 +}
  45 +
  46 +export type ChildDeviceParams = BasicPageParams & {
  47 + formId: string;
  48 +};
... ...
1 1 import { BasicPageParams } from '/@/api/model/baseModel';
2   -export enum DeviceState {
3   - INACTIVE = 'INACTIVE',
4   - ONLINE = 'ONLINE',
5   - OFFLINE = 'OFFLINE',
6   -}
7   -export enum DeviceTypeEnum {
8   - GATEWAY = 'GATEWAY',
9   - DIRECT_CONNECTION = 'DIRECT_CONNECTION',
10   - SENSOR = 'SENSOR',
11   -}
12 2 export type DeviceProfileQueryParam = BasicPageParams & DeviceProfileParam & DeviceId;
13 3 export type DeviceQueryParam = BasicPageParams & DeviceParam;
14 4 export type DeviceParam = {
... ... @@ -23,24 +13,11 @@ export type DeviceId = {
23 13 };
24 14
25 15 export interface DeviceModel {
26   - id: string;
27   - name: string;
28   - deviceInfo: any;
29   - activeTime: string;
30   - deviceState: DeviceState;
31   - alarmStatus: number;
32   - profileId: string;
33   - label: string;
34   - lastConnectTime: string;
35   - deviceType: DeviceTypeEnum;
36   -}
37   -
38   -export interface DeviceProfileModel {
39   - id: string;
  16 + status: true;
40 17 name: string;
41   - transportType: string;
42   - createTime: string;
43   - description: string;
  18 + organizationId: string;
  19 + orderFiled: string;
  20 + orderType: string;
44 21 }
45 22
46 23 export type ChildDeviceParams = BasicPageParams & {
... ...
  1 +<template>
  2 + <BasicDrawer
  3 + v-bind="$attrs"
  4 + @register="registerDrawer"
  5 + showFooter
  6 + :title="getTitle"
  7 + width="30%"
  8 + @ok="handleSubmit"
  9 + >
  10 + <BasicForm @register="registerForm" />
  11 + </BasicDrawer>
  12 +</template>
  13 +<script lang="ts">
  14 + import { defineComponent, ref, computed, unref } from 'vue';
  15 + import { BasicForm, useForm } from '/@/components/Form';
  16 + import { formSchema } from './config.data';
  17 + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  18 + import { saveOrEditAlarmContact } from '/@/api/alarm/contact/alarmContact';
  19 + import { useMessage } from '/@/hooks/web/useMessage';
  20 +
  21 + export default defineComponent({
  22 + name: 'ContactDrawer',
  23 + components: { BasicDrawer, BasicForm },
  24 + emits: ['success', 'register'],
  25 + setup(_, { emit }) {
  26 + const isUpdate = ref(true);
  27 +
  28 + const [registerForm, { validate, setFieldsValue, resetFields }] = useForm({
  29 + labelWidth: 120,
  30 + schemas: formSchema,
  31 + showActionButtonGroup: false,
  32 + });
  33 +
  34 + const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
  35 + await resetFields();
  36 + setDrawerProps({ confirmLoading: false });
  37 + isUpdate.value = !!data?.isUpdate;
  38 + if (unref(isUpdate)) {
  39 + if (data.record.organizationDTO) {
  40 + await setFieldsValue(data.record);
  41 + } else {
  42 + Reflect.deleteProperty(data.record, 'organizationId');
  43 + await setFieldsValue(data.record);
  44 + }
  45 + }
  46 + });
  47 +
  48 + const getTitle = computed(() => (!unref(isUpdate) ? '新增视频配置' : '编辑视频配置'));
  49 +
  50 + async function handleSubmit() {
  51 + try {
  52 + const { createMessage } = useMessage();
  53 + const values = await validate();
  54 + setDrawerProps({ confirmLoading: true });
  55 + let saveMessage = '添加成功';
  56 + let updateMessage = '修改成功';
  57 + await saveOrEditAlarmContact(values, unref(isUpdate));
  58 + closeDrawer();
  59 + emit('success');
  60 + createMessage.success(unref(isUpdate) ? updateMessage : saveMessage);
  61 + } finally {
  62 + setDrawerProps({ confirmLoading: false });
  63 + }
  64 + }
  65 +
  66 + return {
  67 + getTitle,
  68 + registerDrawer,
  69 + registerForm,
  70 + handleSubmit,
  71 + };
  72 + },
  73 + });
  74 +</script>
... ...
  1 +import { BasicColumn, FormSchema } from '/@/components/Table';
  2 +import { getOrganizationList } from '/@/api/system/system';
  3 +import { copyTransFun } from '/@/utils/fnUtils';
  4 +
  5 +// 表格列数据
  6 +export const columns: BasicColumn[] = [
  7 + {
  8 + title: '视频名字',
  9 + dataIndex: 'email',
  10 + width: 120,
  11 + },
  12 + {
  13 + title: '视频厂家',
  14 + dataIndex: 'remark',
  15 + width: 120,
  16 + },
  17 + {
  18 + title: '摄像头编号',
  19 + dataIndex: 'username',
  20 + width: 120,
  21 + },
  22 + {
  23 + title: '视频流',
  24 + dataIndex: 'wechat',
  25 + width: 120,
  26 + },
  27 + {
  28 + title: '所属组织',
  29 + dataIndex: 'organizationDTO.name',
  30 + width: 160,
  31 + },
  32 + {
  33 + title: '添加时间',
  34 + dataIndex: 'createTime',
  35 + width: 180,
  36 + },
  37 + {
  38 + title: '更新时间',
  39 + dataIndex: 'updateTime',
  40 + width: 180,
  41 + },
  42 +];
  43 +
  44 +// 查询字段
  45 +export const searchFormSchema: FormSchema[] = [
  46 + {
  47 + field: 'name',
  48 + label: '摄像头名字',
  49 + component: 'Input',
  50 + colProps: { span: 8 },
  51 + componentProps: {
  52 + maxLength: 36,
  53 + placeholder: '请输入摄像头名字',
  54 + },
  55 + },
  56 +];
  57 +
  58 +// 弹框配置项
  59 +export const formSchema: FormSchema[] = [
  60 + {
  61 + field: 'name',
  62 + label: '视频名字',
  63 + required: true,
  64 + component: 'Input',
  65 + componentProps: {
  66 + placeholder: '请输入视频名字',
  67 + maxLength: 30,
  68 + },
  69 + },
  70 + {
  71 + field: 'organizationId',
  72 + label: '所属组织',
  73 + required: true,
  74 + component: 'ApiTreeSelect',
  75 + componentProps: {
  76 + api: async () => {
  77 + const data = await getOrganizationList();
  78 + copyTransFun(data as any as any[]);
  79 + return data;
  80 + },
  81 + },
  82 + },
  83 + {
  84 + field: 'brand',
  85 + label: '视频厂家',
  86 + component: 'Input',
  87 + required: true,
  88 + componentProps: {
  89 + placeholder: '请输入视频厂家',
  90 + },
  91 + },
  92 + {
  93 + field: 'sn',
  94 + label: '摄像头编号',
  95 + component: 'Input',
  96 + required: true,
  97 + componentProps: {
  98 + placeholder: '请输入摄像头编号',
  99 + },
  100 + },
  101 + {
  102 + field: 'videoUrl',
  103 + label: '视频流',
  104 + required: true,
  105 + component: 'Input',
  106 + componentProps: {
  107 + placeholder: '请输入视频流',
  108 + maxLength: 255,
  109 + },
  110 + },
  111 +];
... ...
  1 +<template>
  2 + <div>
  3 + <PageWrapper dense contentFullHeight contentClass="flex">
  4 + <OrganizationIdTree
  5 + class="w-1/4 xl:w-1/5"
  6 + @select="handleSelect"
  7 + ref="organizationIdTreeRef"
  8 + />
  9 + <BasicTable
  10 + :clickToRowSelect="false"
  11 + @register="registerTable"
  12 + :searchInfo="searchInfo"
  13 + class="w-3/4 xl:w-4/5"
  14 + >
  15 + <template #toolbar>
  16 + <a-button type="primary" @click="handleCreateOrEdit(null)"> 新增视频 </a-button>
  17 + <a-button
  18 + type="primary"
  19 + color="error"
  20 + @click="handleDeleteOrBatchDelete(null)"
  21 + :disabled="hasBatchDelete"
  22 + >
  23 + 批量删除
  24 + </a-button>
  25 + </template>
  26 + <template #action="{ record }">
  27 + <TableAction
  28 + :actions="[
  29 + {
  30 + label: '编辑',
  31 + icon: 'clarity:note-edit-line',
  32 + onClick: handleCreateOrEdit.bind(null, record),
  33 + },
  34 + {
  35 + label: '删除',
  36 + icon: 'ant-design:delete-outlined',
  37 + color: 'error',
  38 + popConfirm: {
  39 + title: '是否确认删除',
  40 + confirm: handleDeleteOrBatchDelete.bind(null, record),
  41 + },
  42 + },
  43 + ]"
  44 + />
  45 + </template>
  46 + </BasicTable>
  47 + </PageWrapper>
  48 + <ContactDrawer @register="registerDrawer" @success="handleSuccess" />
  49 + </div>
  50 +</template>
  51 +
  52 +<script lang="ts">
  53 + import { defineComponent, reactive, ref, computed } from 'vue';
  54 + import { BasicTable, useTable, TableAction } from '/@/components/Table';
  55 + import { PageWrapper } from '/@/components/Page';
  56 + import { useMessage } from '/@/hooks/web/useMessage';
  57 + import { useDrawer } from '/@/components/Drawer';
  58 + import ContactDrawer from './ContactDrawer.vue';
  59 + import { useResetOrganizationTree, OrganizationIdTree } from '/@/views/common/organizationIdTree';
  60 + import { getAlarmContact, deleteAlarmContact } from '/@/api/alarm/contact/alarmContact';
  61 + import { searchFormSchema, columns } from './config.data';
  62 + export default defineComponent({
  63 + components: {
  64 + PageWrapper,
  65 + OrganizationIdTree,
  66 + BasicTable,
  67 + TableAction,
  68 + ContactDrawer,
  69 + },
  70 + setup() {
  71 + let selectedRowIds = ref<string[]>([]);
  72 + const hasBatchDelete = computed(() => selectedRowIds.value.length <= 0);
  73 + // 复选框事件
  74 + const onSelectRowChange = (selectedRowKeys: string[]) => {
  75 + selectedRowIds.value = selectedRowKeys;
  76 + };
  77 + const searchInfo = reactive<Recordable>({});
  78 + const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);
  79 + // 表格hooks
  80 + const [registerTable, { reload }] = useTable({
  81 + title: '视频列表',
  82 + api: getAlarmContact,
  83 + columns,
  84 + clickToRowSelect: false,
  85 + formConfig: {
  86 + labelWidth: 120,
  87 + schemas: searchFormSchema,
  88 + resetFunc: resetFn,
  89 + },
  90 + useSearchForm: true,
  91 + showTableSetting: true,
  92 + bordered: true,
  93 + rowSelection: {
  94 + onChange: onSelectRowChange,
  95 + type: 'checkbox',
  96 + },
  97 + rowKey: 'id',
  98 + actionColumn: {
  99 + width: 200,
  100 + title: '操作',
  101 + dataIndex: 'action',
  102 + slots: { customRender: 'action' },
  103 + fixed: 'right',
  104 + },
  105 + });
  106 + // 弹框
  107 + const [registerDrawer, { openDrawer }] = useDrawer();
  108 + const { createMessage } = useMessage();
  109 +
  110 + // 刷新
  111 + const handleSuccess = () => {
  112 + reload();
  113 + };
  114 + // 新增或编辑
  115 + const handleCreateOrEdit = (record: Recordable | null) => {
  116 + if (record) {
  117 + openDrawer(true, {
  118 + isUpdate: true,
  119 + record,
  120 + });
  121 + } else {
  122 + openDrawer(true, {
  123 + isUpdate: false,
  124 + });
  125 + }
  126 + };
  127 + // 删除或批量删除
  128 + const handleDeleteOrBatchDelete = async (record: Recordable | null) => {
  129 + if (record) {
  130 + try {
  131 + await deleteAlarmContact([record.id]);
  132 + createMessage.success('删除联系人成功');
  133 + handleSuccess();
  134 + } catch (e) {}
  135 + } else {
  136 + try {
  137 + await deleteAlarmContact(selectedRowIds.value);
  138 + createMessage.success('批量删除联系人成功');
  139 + selectedRowIds.value = [];
  140 + handleSuccess();
  141 + } catch (e) {}
  142 + }
  143 + };
  144 +
  145 + // 树形选择器
  146 + const handleSelect = (organizationId: string) => {
  147 + searchInfo.organizationId = organizationId;
  148 + handleSuccess();
  149 + };
  150 + return {
  151 + searchInfo,
  152 + hasBatchDelete,
  153 + handleCreateOrEdit,
  154 + handleDeleteOrBatchDelete,
  155 + handleSelect,
  156 + handleSuccess,
  157 + registerTable,
  158 + registerDrawer,
  159 + organizationIdTreeRef,
  160 + };
  161 + },
  162 + });
  163 +</script>
... ...