Răsfoiți Sursa

feat: 设备

lvkun996 2 ani în urmă
părinte
comite
cb0503f828

+ 10 - 0
src/api/cvs/operator.ts

@@ -0,0 +1,10 @@
+
+import request from '@/service/request'
+
+export const getOperatorPage = (params: COMMON.API.QueryParams) => {
+  return request<CVS.space[]>({
+    url: '/model/page',
+    method: 'GET',
+    params
+  })
+}

+ 30 - 2
src/api/cvs/video.ts

@@ -9,6 +9,13 @@ export const getSpace = (params: COMMON.API.QueryParams) => {
   })
 }
 
+export const getSpaceList = (spaceName: string, limit: number) => {
+  return request<CVS.space[]>({
+    url: `/space/list?spaceName=${spaceName}&limit=${limit}`,
+    method: 'GET'
+  })
+}
+
 export const addSpace = (data: Partial<CVS.space>) => {
   return request<string>({
     url: '/space',
@@ -19,18 +26,39 @@ export const addSpace = (data: Partial<CVS.space>) => {
 
 export const updateSpace = (data: CVS.space) => {
   return request<string>({
-    url: '/cvs/space',
+    url: '/spaceConfig',
     method: 'PUT',
     data
   })
 }
 
+export const getSpaceConfigById = (spaceId: string) => {
+  return request<string>({
+    url: `/spaceConfig/${spaceId}`,
+    method: 'GET'
+  })
+}
+
+export const getSpaceById = (spaceId: string) => {
+  return request<CVS.space>({
+    url: `/space/${spaceId}`,
+    method: 'GET'
+  })
+}
+
 export const delSpace = (id: string) => {
   return request<string>({
-    url: `/cvs/space?id=${id}`,
+    url: `/space?id=${id}`,
     method: 'DELETE'
   })
 }
+// /space/status?spaceId=xx&status=xxx
+export const updateStatus = (spaceId, status) => {
+  return request<string>({
+    url: `/space/status?spaceId=${spaceId}&status=${status}`,
+    method: 'PUT'
+  })
+}
 
 export const getDevicePage = (params: COMMON.API.QueryParams & {deviceName: string}) => {
   return request<CVS.device[]>({

+ 36 - 19
src/components/TableProV2/index.tsx

@@ -149,20 +149,19 @@ interface Props {
 
 // export default TablePro
 
-export default defineComponent({
+const TablePro = defineComponent({
   props: {
     columns: {
       type: Array as PropType<TableColumnProps[]>,
       require: true
     },
-    request: {
-      type: Object as PropType<{get: Function, params: any}>,
-      default: () => {
-        return {
-          get: () => {},
-          params: {}
-        }
-      }
+    service: {
+      type: Function,
+      default: () => {}
+    },
+    serviceParams: {
+      type: Object,
+      default: () => {}
     },
     easy: {
       type: Boolean,
@@ -175,7 +174,7 @@ export default defineComponent({
   },
   emits: ['add'],
   setup (props, ctx) {
-    const { columns, request, pagination } = props
+    const { columns, service, pagination, serviceParams } = props
 
     const loading = ref<boolean>(false)
 
@@ -187,18 +186,23 @@ export default defineComponent({
 
     const dataSource = ref([])
 
-    const reload = () => dispatchRequest()
-
+    const reload = (params?: {page: number}) => {
+      if (params?.page) {
+        paginationRef.value.current = params.page
+      }
+      dispatchRequest()
+    }
     const dispatchRequest = async () => {
-      // loading.value = true
-      const { data, sum } = await request.get({
-        ...request.params,
+      loading.value = true
+      const { data, sum } = await service({
+        ...serviceParams,
         page: paginationRef.value.current,
         pageSize: paginationRef.value.pageSize
       })
       loading.value = false
       paginationRef.value.total = sum
       dataSource.value = data
+      console.log('data:', data)
     }
 
     const onChangePage = (page: number, pageSize: number) => {
@@ -207,7 +211,7 @@ export default defineComponent({
     }
 
     onMounted(() => {
-      request.get && dispatchRequest()
+      service && dispatchRequest()
     })
 
     const opraMeun = (
@@ -216,24 +220,29 @@ export default defineComponent({
       </Menu>
     )
 
+    ctx.expose({ reload })
+
     return () => (
 
       <Row style={{ width: '100%' }} >
         <Col span={24} >
           <Row style={{ width: '100%' }} >
-            <Col span={12} ></Col>
+            <Col span={12} >
+              {ctx.slots.search?.()}
+            </Col>
             <Col span={12} style={{ display: 'flex', justifyContent: 'end' }} >
               <Space>
               <Dropdown
                   trigger={['click', 'hover']}
                   overlay={opraMeun}
                 >
-                  <Button onClick={reload} >刷新<DownOutlined /></Button>
+                  <Button onClick={() => reload()} >刷新<DownOutlined /></Button>
                 </Dropdown>
               </Space>
             </Col>
           </Row>
         </Col>
+
         <Col span={24} >
           <Table
             style={{ marginTop: '20px' }}
@@ -241,10 +250,18 @@ export default defineComponent({
             loading={loading.value}
             pagination={{ ...paginationRef.value, onChange: onChangePage }}
             dataSource={dataSource.value}
-          ></Table>
+            v-slots={{
+              bodyCell (scope) {
+                return ctx.slots.render?.({ column: scope.column, record: scope.record })
+              }
+            }}
+          >
+          </Table>
         </Col>
       </Row>
 
     )
   }
 })
+
+export default TablePro

+ 36 - 9
src/controller/cvs/spaceController.ts

@@ -1,4 +1,4 @@
-import { addDevice, addSpace, delSpace, getDeviceAiRetById, getDeviceById, getDevicePage, getDeviceRecordById, getDeviceThumbById, getSpace, updateSpace } from '@/api/cvs/video'
+import { addDevice, addSpace, delSpace, getDeviceAiRetById, getDeviceById, getDevicePage, getDeviceRecordById, getDeviceThumbById, getSpace, getSpaceById, getSpaceList, updateSpace, updateStatus } from '@/api/cvs/video'
 import { message } from 'ant-design-vue'
 
 export class SpaceController {
@@ -15,16 +15,32 @@ export class SpaceController {
     { key: '华中', value: '华中', label: '华中' }
   ]
 
+  static statusMap = new Map([
+    ['RUNNING', { key: 'RUNNING', label: '运行中', color: 'green' }],
+    ['STOPPED', { key: 'STOPPED', label: '已停止', color: 'red' }]
+  ])
+
+  static deviceStatusMap = new Map([
+    ['ONLINE', { key: 'ONLINE', label: '在线', color: 'green' }],
+    ['OFFLINE', { key: 'OFFLINE', label: '离线', color: 'grey' }],
+    ['ERROR', { key: 'ERROR', label: '异常', color: 'red' }],
+    ['PUSHING', { key: 'PUSHING', label: '推流中', color: 'blue' }],
+    ['PULLING', { key: 'PULLING', label: '拉流中', color: 'yellow' }]
+  ])
+
   static async page (params: COMMON.API.QueryParams) {
-    const { code, data } = await getSpace(params)
-    if (code === 200) {
-      return data
-    }
+    return await getSpace(params)
+  }
+
+  static async list () {
+    const { code, data } = await getSpaceList('', 200)
+    return data
   }
 
   static async add (data: Partial<CVS.space>) {
     const { code, msg } = await addSpace(data)
     code === 200 ? message.success('新增成功') : message.error(msg)
+    return code === 200
   }
 
   static async update (data: CVS.space) {
@@ -37,11 +53,22 @@ export class SpaceController {
     code === 200 ? message.success('删除成功') : message.error(msg)
   }
 
+  static async updateStatue (spaceId, status) {
+    const { code, msg } = await updateStatus(spaceId, status)
+    code === 200 ? message.success('修改成功') : message.error(msg)
+  }
+
+  static async byId (spaceId: string) {
+    const { code, data } = await getSpaceById(spaceId)
+    return data
+  }
+
+  static async spaceConfigById (spaceId: string) {
+
+  }
+
   static async devicePage (params: COMMON.API.QueryParams & {deviceName: string}) {
-    const { code, data } = await getDevicePage(params)
-    if (code === 200) {
-      return data
-    }
+    return await getDevicePage(params)
   }
 
   static async deviceById (id: string) {

+ 75 - 17
src/pages/cvs/video/device.vue

@@ -1,9 +1,35 @@
 <template>
-<a-card title="设备管理" >
-  <a-row justify="space-between" >
-    <a-col><a-space><InputTsx placeholder="请输入设备名称进行搜索" /> <a-button type="primary">搜索</a-button> </a-space></a-col>
-    <a-col><a-button type="primary"  @click="openModal">新增设备</a-button></a-col>
-  </a-row>
+<a-card>
+  <template #title >
+    <a-row>
+      <a-col style="font-size: 20px;margin-right: 10px;" >设备管理</a-col>
+      <a-col >
+        <a-select
+          :value="spaceId"
+          style="width: 170px;"
+          @change="onChangeSpace"
+        >
+          <a-select-option
+            :key="item.spaceId"
+            :value="item.spaceId"
+            v-for="(item, index) in state.spaceList "
+          >
+            {{item.spaceName}}
+          </a-select-option>
+        </a-select>
+    </a-col>
+    </a-row>
+  </template>
+  <table-pro
+    ref="tableProDom"
+    :service="SpaceController.devicePage"
+    :columns="columns"
+    @add="openModal"
+  >
+    <template #search >
+      <a-space><InputTsx placeholder="请输入设备名称进行搜索" /> <a-button type="primary">搜索</a-button> </a-space>
+    </template>
+  </table-pro>
 
   <modal-pro
     style="width: 1000px;"
@@ -14,23 +40,23 @@
   >
   <a-form  style="width: 100%;" :labelCol="{span: 3}" :wrapperCol="{span: 14}" >
       <a-form-item label="设备名称" v-bind="validateInfos.deviceName"  >
-        <InputTsx allowClear placeholder="请输入设备名称" />
+        <InputTsx allowClear placeholder="请输入设备名称" v-model:value="deviceState.deviceName" />
       </a-form-item>
       <a-form-item  label="app" v-bind="validateInfos.app"  >
-        <InputTsx allowClear placeholder="请输入英文、数字组成的app" />
+        <InputTsx allowClear placeholder="请输入英文、数字组成的app" v-model:value="deviceState.app" />
       </a-form-item>
       <a-form-item  label="stream" v-bind="validateInfos.stream"  >
-        <InputTsx allowClear placeholder="请输入英文、数字组成的stream" />
+        <InputTsx allowClear placeholder="请输入英文、数字组成的stream" v-model:value="deviceState.stream" />
       </a-form-item>
       <a-form-item  label="设备类型"  >
         {{deviceState.type}}
       </a-form-item>
       <a-form-item  label="设备地址"  >
-        <InputTsx allowClear placeholder="请输入设备地址" />
+        <InputTsx allowClear placeholder="请输入设备地址" v-model:value="deviceState.gisName" />
       </a-form-item>
-      <a-form-item  label="设备经纬度"  >
+      <!-- <a-form-item  label="设备经纬度"  >
         <InputTsx allowClear placeholder="输入地址后自动填入经纬度,例如(116.404,39.915)" />
-      </a-form-item>
+      </a-form-item> -->
       <a-form-item  label="设备描述"  >
         <a-textarea v-model:value="deviceState.description" placeholder="请输入设备描述" :rows="4" />
       </a-form-item>
@@ -40,8 +66,10 @@
 </template>
 <script lang='ts' setup >
 import { InputTsx } from '@/components/MicroComponents/index'
-import { reactive } from 'vue'
+import { reactive, onMounted, nextTick, ref, getCurrentInstance } from 'vue'
 import { Form } from 'ant-design-vue'
+import { SpaceController } from '@/controller'
+import { useRoute } from 'vue-router'
 
 const columns = [
   {
@@ -78,12 +106,20 @@ const columns = [
 
 const useForm = Form.useForm
 
-const state = reactive({
-  visible: false
+const spaceId = ref(useRoute().query.spaceId as string)
+
+const tableProDom = ref()
+
+const state = reactive<{
+  visible: boolean,
+  spaceList: CVS.space[]
+}>({
+  visible: false,
+  spaceList: []
 })
 
 const deviceState = reactive({
-  spaceId: '',
+  spaceId: undefined,
   deviceName: '',
   app: '',
   stream: '',
@@ -102,12 +138,34 @@ const { resetFields, validate, validateInfos } = useForm(deviceState, {
 })
 
 const ok = () => {
-  validate().then(() => {
-
+  validate().then(async () => {
+    deviceState.deviceStreamId = deviceState.app + '/' + deviceState.stream
+    await SpaceController.addDevice(deviceState as unknown as CVS.device)
+    closeModal()
+    tableProDom.value.reload()
   }).catch(() => {})
 }
 
+const onChangeSpace = (spaceId: string) => {
+  deviceState.spaceId = spaceId
+  tableProDom.value.reload({ page: 1 })
+}
+
+const closeModal = () => state.visible = false
+
 const openModal = () => state.visible = true
+
+const getSpaceList = async () => {
+  state.spaceList = await SpaceController.list()
+  nextTick(() => {
+    deviceState.spaceId = spaceId as any
+    getCurrentInstance()?.proxy?.$forceUpdate()
+  })
+}
+
+onMounted(() => {
+  getSpaceList()
+})
 </script>
 <style lang='less' scoped >
 </style>

+ 54 - 29
src/pages/cvs/video/space.vue

@@ -1,20 +1,31 @@
 <template>
 <a-card title="空间管理" >
-  <a-row justify="space-between" >
-    <a-col><a-space><InputTsx placeholder="请输入空间名称进行搜索" /> <a-button type="primary">搜索</a-button> </a-space></a-col>
-    <a-col><a-button type="primary"  @click="openModal">创建空间</a-button></a-col>
-  </a-row>
 
-  <table-pro
-    :request="{
-      get: SpaceController.page
-    }"
+ <table-pro
+    :service="SpaceController.page"
     :columns="columns"
-    :easy="true"
     @add="openModal"
+    ref="tableProDom"
   >
-
-  </table-pro>
+    <template v-slot:search >
+      <a-space><InputTsx placeholder="请输入空间名称进行搜索" /> <a-button type="primary">搜索</a-button> </a-space>
+    </template>
+    <template v-slot:render="{column, record}" >
+      <template v-if="column.key === 'status'" >
+        <div style="display: flex;align-items: center;" >
+          <div :style="{width: '8px',height: '8px',borderRadius: '50%', backgroundColor:SpaceController.statusMap.get(record.status)?.color, marginRight: '5px' }" ></div>
+          <div>{{SpaceController.statusMap.get(record.status)?.label}}</div>
+        </div>
+      </template>
+      <template v-if="column.key === 'action'"  >
+        <a-space>
+          <a @click="openModal(record)" >配置管理</a>
+          <a @click="pushCvsDevicePage(record)" >设备管理</a>
+          <a @click="changeStatus(record)" >{{record.status === 'RUNNING' ? '停用' : "启用"}}</a>
+        </a-space>
+      </template>
+    </template>
+ </table-pro>
 
  <StepModal
     :step="step"
@@ -27,8 +38,8 @@
   >
   <a-form  style="width: 100%;display: block;" :labelCol="{span: 2}" :wrapperCol="{span: 14}" >
     <div v-show="step === 0" >
-      <a-form-item label="空间名称" v-bind="validateInfos.edgeName"  >
-        <InputTsx allowClear placeholder="请输入空间名称" v-model:value="spaceState.edgeName" />
+      <a-form-item label="空间名称" v-bind="validateInfos.spaceName"  >
+        <InputTsx allowClear placeholder="请输入空间名称" v-model:value="spaceState.spaceName" />
       </a-form-item>
       <a-form-item  label="空间类型" v-bind="validateInfos.type"  >
         <a-radio-group v-model:value="spaceState.type" button-style="solid">
@@ -161,6 +172,7 @@
     </div>
   </a-form>
  </StepModal>
+
 </a-card>
 </template>
 <script lang='ts' setup >
@@ -170,15 +182,20 @@ import { InputTsx } from '@/components/MicroComponents/index'
 import { ref, reactive } from 'vue'
 import { Form } from 'ant-design-vue'
 import { SpaceController } from '@/controller'
+import { useRouter } from 'vue-router'
 
 const useForm = Form.useForm
 
+const router = useRouter()
+
 const steps = [{ title: '空间基本信息' }, { title: '空间配置' }]
 
 const step = ref(0)
 
 const visible = ref<boolean>(false)
 
+const tableProDom = ref()
+
 const columns = [
   {
     title: '空间ID',
@@ -195,11 +212,6 @@ const columns = [
     dataIndex: 'type',
     key: 'type'
   },
-  {
-    title: '接入节点',
-    dataIndex: 'edgeName',
-    key: 'edgeName'
-  },
   {
     title: '设备数量',
     dataIndex: 'deviceCount',
@@ -224,14 +236,9 @@ const columns = [
 
 const spaceState = reactive<CVS.space>({
   description: '',
-  deviceCount: null,
-  deviceMode: '',
-  edgeId: null,
-  edgeName: '',
-  serialId: '',
-  spaceId: null,
-  status: '',
+  spaceName: '',
   type: 'RTMP',
+  spaceId: '',
   upstreamAuth: {
     enabled: false,
     key: '',
@@ -263,16 +270,34 @@ const spaceState = reactive<CVS.space>({
 })
 
 const { resetFields, validate, validateInfos } = useForm(spaceState, {
-  edgeName: [{ required: true, message: '请填写空间名称' }],
+  spaceName: [{ required: true, message: '请填写空间名称' }],
   type: [{ required: true }]
 })
 
-const openModal = () => visible.value = true
+const openModal = async (record = { spaceId: '' }) => {
+  if (record.spaceId) {
+    spaceState.spaceId = record.spaceId
+    resetFields(await SpaceController.byId(record.spaceId))
+  } else {
+    resetFields({})
+  }
+  visible.value = true
+}
 
 const closeModal = () => visible.value = false
 
-const submit = () => {
-  SpaceController.add(spaceState)
+const submit = async () => {
+  spaceState.spaceId ? await SpaceController.update(spaceState) : await SpaceController.add(spaceState)
+  closeModal()
+}
+
+const changeStatus = async (record: CVS.space) => {
+  await SpaceController.updateStatue(record.spaceId, record.status === 'RUNNING' ? 'STOPPED' : 'RUNNING')
+  tableProDom.value.reload()
+}
+
+const pushCvsDevicePage = (record: CVS.space) => {
+  router.push({ path: '/cvs/video/device', query: { spaceId: record.spaceId } })
 }
 
 const stepNext = () => {

+ 5 - 8
src/type/cvs.d.ts

@@ -1,15 +1,12 @@
 declare namespace CVS {
   interface space {
     description: string
-    deviceCount: number | null
-    deviceMode: string | null
-    edgeId: number | null
-    edgeName: string | null
-    serialId: string | null
-    spaceId: number | null
-    status: 'RUNNING' | 'STOPPED' | 'OPERATING' | ''
+    deviceCount?: number | null
+    deviceMode?: string | null
+    spaceName: string
+    spaceId?: string,
+    status?: 'RUNNING' | 'STOPPED' | 'OPERATING' | ''
     type: 'RTMP' | 'GB28181' | 'ONVIF' | 'BVCP' | 'RTSP' | 'JT808' | '',
-
       upstreamAuth: {
         enabled: boolean
         key: string

+ 1 - 1
src/utils/UsePro.ts

@@ -11,7 +11,7 @@ import { App } from 'vue'
 export default function (app: App) {
   app.component('modal-pro', ModalPro)
   app.component('sider-pro', SiderPro)
-  app.component('TablePro', TablePro)
+  app.component('table-pro', TablePro)
   app.component('form-pro', FormPro) // 别问问什么是oo, 问就是这样比较帅
   app.component('l-row', RowPro)
   app.component('l-col', ColPro)